From 61e120a8c5a69127cc751d3fcbb38327236ae97d Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Tue, 28 Jul 2026 14:33:25 +0200 Subject: [PATCH] feat: option lab --- backend/services/pricing_check.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/services/pricing_check.py b/backend/services/pricing_check.py index 0876d33..3328734 100644 --- a/backend/services/pricing_check.py +++ b/backend/services/pricing_check.py @@ -77,9 +77,21 @@ def analyze_option_pricing(ticker: str, date_a: str, date_b: str, target_dte: Op expiry = chain_a["expiries"][0] expiry_date = expiry["expiry_date"] spot_a = chain_a["spot"] - strikes_a = sorted({row["strike"] for row in expiry["calls"]} | {row["strike"] for row in expiry["puts"]}) - if not strikes_a or spot_a is None: - return {"available": False, "reason": "Aucun strike/spot exploitable dans le chain à cette date."} + if spot_a is None: + return {"available": False, "reason": "Spot manquant dans l'historique Saxo à la date de départ."} + + # Many rows in the accumulated history are spot-tracking placeholders with no real + # bid/ask (Saxo doesn't actively quote every strike) — only strikes with an actual + # tradeable mid (call OR put) are candidates for the hindsight pick below, otherwise + # the "closest to the outcome" strike could be one that was never really quoted at all. + strikes_a = sorted({ + row["strike"] for row in (expiry["calls"] + expiry["puts"]) if row.get("mid", 0) > 0 + }) + if not strikes_a: + return { + "available": False, + "reason": f"Aucun strike avec une cotation réelle (bid/ask) dans l'historique Saxo au {date_a} pour l'échéance {expiry_date} — essayez un autre DTE cible ou une autre date.", + } rows_b = get_snapshot_rows_asof(saxo_symbol, date_b) if not rows_b: