From 06db653c4615cadac41dd22160ada2e03ce741fe Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Tue, 28 Jul 2026 11:46:48 +0200 Subject: [PATCH] feat: option lab --- backend/services/database.py | 17 +++++++++++++++-- backend/services/option_chain.py | 3 +++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/services/database.py b/backend/services/database.py index 18199e5..c0953d3 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -6645,8 +6645,21 @@ def get_snapshot_rows_asof(symbol: Optional[str] = None, as_of: Optional[str] = """One row per (symbol, expiry_date, strike, option_type) — the freshest capture at or before `as_of` (an ISO datetime string), or simply the freshest capture overall when as_of is omitted. Powers the Saxo History page's default 'current chain' view and its - 'replay a past moment' filter — the full granular history stays in the table underneath - for later backtest use, this just picks one row per contract.""" + 'replay a past moment' filter, and services.option_chain.get_chain_slice's `as_of` + historical reconstruction (Portfolio retrospective comparison, Options Lab pricing + check) — the full granular history stays in the table underneath for later backtest + use, this just picks one row per contract. + + A bare date (no time component, e.g. "2026-07-21") is treated as the END of that day, + not the start. `created_at` is always a real capture timestamp like + "2026-07-21 13:59:43" — comparing that against the bare string "2026-07-21" with SQL's + `<=` is a plain string comparison, and a 19-char timestamp always sorts AFTER its own + 10-char date prefix, so every row captured that day would be silently excluded and a + date that genuinely has data would look like it has none (this was a real bug: querying + `as_of="2026-07-21"` against a symbol with rows *starting* on 2026-07-21 returned empty). + """ + if as_of and len(as_of) <= 10: + as_of = as_of + "T23:59:59" conn = get_conn() where_symbol = "AND symbol = ?" if symbol else "" where_asof = "AND created_at <= ?" if as_of else "" diff --git a/backend/services/option_chain.py b/backend/services/option_chain.py index f17e882..33ee838 100644 --- a/backend/services/option_chain.py +++ b/backend/services/option_chain.py @@ -35,6 +35,9 @@ def get_chain_slice( 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. + + A bare date (no time component) in `as_of` is handled by get_snapshot_rows_asof itself + (treated as end-of-day, not start — see its docstring for why that matters). """ from services.database import get_latest_saxo_snapshot_rows, get_snapshot_rows_asof