feat: option lab
This commit is contained in:
@@ -13,9 +13,10 @@ from typing import Any, Dict, List, Optional
|
||||
def get_chain_slice(
|
||||
symbol: str, target_days: int = 8, n_expiries: int = 3,
|
||||
dte_min: Optional[int] = None, dte_max: Optional[int] = None,
|
||||
as_of: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Builds a chain slice from the latest accumulated Saxo snapshot rows for `symbol`
|
||||
Builds a chain slice from the accumulated Saxo snapshot rows for `symbol`
|
||||
(services/database.get_latest_saxo_snapshot_rows). Returns the `n_expiries`
|
||||
expirations closest to target_days, each with calls/puts rows shaped
|
||||
{strike, bid, ask, mid, last, iv, open_interest, volume} — same shape regardless
|
||||
@@ -26,19 +27,27 @@ def get_chain_slice(
|
||||
scenario at a short horizon (e.g. target_days=8) while still building legs from
|
||||
longer-dated options (e.g. dte_min=20, dte_max=60), which target_days alone can't
|
||||
express since it drives both the evaluation date and (until now) the expiry pick.
|
||||
"""
|
||||
from services.database import get_latest_saxo_snapshot_rows
|
||||
|
||||
flat_rows = get_latest_saxo_snapshot_rows(symbol.upper())
|
||||
`as_of` (an ISO date/datetime string), when given, reconstructs the chain as it stood
|
||||
at or before that moment instead of "now" — services.database.get_snapshot_rows_asof,
|
||||
same row shape, just filtered by created_at. This is what powers the Portfolio
|
||||
retrospective comparison (services.strategy_comparison): it needs the chain as it
|
||||
really was on a position's entry_date, not today's. Every days-to-expiry figure is
|
||||
computed relative to `as_of` in that case, not date.today() — using today's date to
|
||||
size a historical chain would silently misdate every contract in it.
|
||||
"""
|
||||
from services.database import get_latest_saxo_snapshot_rows, get_snapshot_rows_asof
|
||||
|
||||
flat_rows = get_snapshot_rows_asof(symbol.upper(), as_of) if as_of else get_latest_saxo_snapshot_rows(symbol.upper())
|
||||
if not flat_rows:
|
||||
raise ValueError(
|
||||
f"Aucun historique Saxo pour '{symbol}' — ajoutez-le à la watchlist "
|
||||
f"(Config → Saxo) et attendez le prochain cycle de snapshot (~5 min)."
|
||||
f"Aucun historique Saxo pour '{symbol}'" + (f" à la date {as_of}" if as_of else "") +
|
||||
" — ajoutez-le à la watchlist (Config → Saxo) et attendez le prochain cycle de snapshot (~5 min)."
|
||||
)
|
||||
|
||||
spot = next((r["spot"] for r in flat_rows if r.get("spot") is not None), None)
|
||||
as_of = max((r["created_at"] for r in flat_rows if r.get("created_at")), default=None)
|
||||
today = date.today()
|
||||
snapshot_as_of = max((r["created_at"] for r in flat_rows if r.get("created_at")), default=None)
|
||||
reference_date = datetime.strptime(as_of[:10], "%Y-%m-%d").date() if as_of else date.today()
|
||||
|
||||
by_expiry: Dict[str, List[Dict[str, Any]]] = {}
|
||||
for r in flat_rows:
|
||||
@@ -46,7 +55,7 @@ def get_chain_slice(
|
||||
by_expiry.setdefault(r["expiry_date"], []).append(r)
|
||||
|
||||
def _days_to(expiry_date: str) -> int:
|
||||
return (datetime.strptime(expiry_date[:10], "%Y-%m-%d").date() - today).days
|
||||
return (datetime.strptime(expiry_date[:10], "%Y-%m-%d").date() - reference_date).days
|
||||
|
||||
candidates = list(by_expiry.keys())
|
||||
if dte_min is not None or dte_max is not None:
|
||||
@@ -98,7 +107,7 @@ def get_chain_slice(
|
||||
"symbol": symbol.upper(),
|
||||
"proxy": symbol.upper(),
|
||||
"spot": round(float(spot), 6) if spot is not None else None,
|
||||
"as_of": as_of,
|
||||
"as_of": snapshot_as_of,
|
||||
"expiries": expiries_out,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user