diff --git a/backend/routers/instrument_models.py b/backend/routers/instrument_models.py index fc7a71f..3946f68 100644 --- a/backend/routers/instrument_models.py +++ b/backend/routers/instrument_models.py @@ -200,8 +200,11 @@ def get_instrument_timeline( """Simulation jour par jour de tous les nœuds du modèle sur la période.""" from services.database import get_conn from services.instrument_models import simulate_timeline + from services.price_history import get_price_history conn = get_conn() try: + # Pré-peupler le cache prix pour que l'auto-anchor ait les données disponibles + get_price_history(conn, instrument.upper(), period) data = simulate_timeline(conn, instrument.upper(), period) if not data: raise HTTPException(status_code=404, detail=f"Modèle introuvable pour {instrument.upper()}") @@ -218,8 +221,11 @@ def timeline_whatif( """Simulation what-if avec events virtuels injectés dans la timeline.""" from services.database import get_conn from services.instrument_models import simulate_timeline + from services.price_history import get_price_history conn = get_conn() try: + # Garantir que le cache prix est disponible pour l'auto-anchor + get_price_history(conn, instrument.upper(), body.period) ve_list = [ve.dict() for ve in body.virtual_events] data = simulate_timeline(conn, instrument.upper(), body.period, virtual_events=ve_list) if not data: diff --git a/backend/services/instrument_models.py b/backend/services/instrument_models.py index 3e5d85d..fc4bf06 100644 --- a/backend/services/instrument_models.py +++ b/backend/services/instrument_models.py @@ -1077,6 +1077,28 @@ def simulate_timeline( structural_pips = round(float(vals_struct.get(output_id, 0.0)), 1) fundamental_level_base = round(price_intercept + structural_pips * pip_to_price, 6) + # Auto-anchor : caler le niveau fondamental sur le prix réel au début de la fenêtre. + # start_offset = prix_réel_début - niveau_fondamental_machine + # → synthetic_price(t) = prix_réel_début + event_pips(t) * pip_to_price + start_offset = 0.0 + try: + ph_row = conn.execute( + """SELECT close FROM price_history_cache + WHERE instrument=? AND date>=? ORDER BY date ASC LIMIT 1""", + (inst_upper, str(date_from)) + ).fetchone() + if ph_row is None: + # Weekends/jours fériés : on remonte jusqu'à 7 jours avant + ph_row = conn.execute( + """SELECT close FROM price_history_cache + WHERE instrument=? AND date>=? ORDER BY date ASC LIMIT 1""", + (inst_upper, str(date_from - timedelta(days=7))) + ).fetchone() + if ph_row: + start_offset = round(float(ph_row["close"]) - fundamental_level_base, 6) + except Exception: + pass + timeline = [] cur = date_from while cur <= today: @@ -1111,8 +1133,8 @@ def simulate_timeline( "net_pips": net, "structural_pips": structural_pips, "event_pips": round(net - structural_pips, 1), - "fundamental_level": fundamental_level_base, - "synthetic_price": round(price_intercept + net * pip_to_price, 6), + "fundamental_level": round(fundamental_level_base + start_offset, 6), + "synthetic_price": round(price_intercept + start_offset + net * pip_to_price, 6), "regime": regime_label, "nodes": {k: round(float(v), 1) for k, v in vals.items()}, })