feat: instrument model

This commit is contained in:
OpenSquared
2026-07-03 09:59:26 +02:00
parent 980c797f53
commit f4e57e9d84
3 changed files with 139 additions and 180 deletions

View File

@@ -913,28 +913,22 @@ def get_model_state(conn, instrument: str, at_date: Optional[str] = None) -> Opt
(inst_upper,)
).fetchall()}
ev_by_cat = _compute_event_by_category(conn, inst_upper, ref_date)
# Machine structurelle pure — aucune injection automatique depuis market_events.
# Les events du CausalLab ne touchent plus le graphe; seuls les overrides manuels
# (Sync Marche + saisie) et les events virtuels (What-if) contribuent.
ev_by_cat: dict[str, float] = {}
# Phase 2 : détection régime + poids adaptatifs
regime_info = detect_regime(ev_by_cat)
regime_info = detect_regime(ev_by_cat) # toujours BALANCED hors What-if
regime_weights = regime_info["weights"]
# Inputs avec saturation tanh
inputs = _build_inputs(graph_def, overrides, ev_by_cat, saturation=True)
# DAG evaluation avec formule output pondérée par régime
from services.causal_graphs import evaluate_graph
inputs = _build_inputs(graph_def, overrides, ev_by_cat, saturation=True)
gj = _graph_json_for_eval(graph_def, regime_weights)
all_vals = evaluate_graph(gj, inputs)
output_id = graph_def["output_node"]
net_pips = round(float(all_vals.get(output_id, 0.0)), 1)
# Compute structural pips (manual inputs only, no events, BALANCED regime)
inputs_struct = _build_inputs(graph_def, overrides, {}, saturation=True)
gj_struct = _graph_json_for_eval(graph_def, {})
vals_struct = evaluate_graph(gj_struct, inputs_struct)
structural_pips = round(float(vals_struct.get(output_id, 0.0)), 1)
output_id = graph_def["output_node"]
net_pips = round(float(all_vals.get(output_id, 0.0)), 1)
structural_pips = net_pips # identique — pas d'events injectés
event_pips = round(net_pips - structural_pips, 1)
meta = INSTRUMENT_MODELS.get(inst_upper, {})
@@ -1049,64 +1043,10 @@ def simulate_timeline(
(inst_upper,)
).fetchall()}
# Load ALL relevant events (extended lookback for decay)
inst_lower = inst_upper.lower()
extended = date_from - timedelta(days=365)
rows = conn.execute("""
SELECT a.id as analysis_id,
a.prediction_json, e.start_date, e.end_date, e.sub_type,
t.category,
COALESCE(o.calibration_json, t.calibration_json) as calibration_json
FROM causal_event_analyses a
JOIN market_events e ON e.id = a.market_event_id
JOIN causal_graph_templates t ON t.id = a.template_id
LEFT JOIN event_calibration_overrides o ON o.analysis_id = a.id
WHERE a.instrument=? AND e.start_date>=? AND e.start_date<=?
""", (inst_upper, str(extended), str(today))).fetchall()
# Machine structurelle — seuls les events virtuels (What-if) entrent dans le calcul.
# La timeline de base ne dépend plus de causal_event_analyses.
events: list[dict] = []
# Pre-parse events
events = []
for r in rows:
r = dict(r)
try:
preds = json.loads(r["prediction_json"] or "{}")
calib = json.loads(r["calibration_json"] or "{}")
except Exception:
continue
pips: Optional[float] = None
if inst_lower in preds:
pips = float(preds[inst_lower])
else:
for k, v in preds.items():
if inst_lower in k.lower():
try: pips = float(v); break
except (TypeError, ValueError): pass
if not pips:
continue
absorption = max(1, int(calib.get("absorption_days", 7)))
dtype = str(calib.get("decay_type", "exp"))
rise = max(0, int(calib.get("rise_days", 0)))
plateau = max(0, int(calib.get("plateau_days", 0)))
ev_end = r.get("end_date")
if ev_end and str(r.get("sub_type","")).startswith("rate_guidance"):
try:
meeting = date_type.fromisoformat(ev_end[:10])
ev_start = date_type.fromisoformat(r["start_date"][:10])
absorption = max(1, (meeting - ev_start).days)
dtype = "linear"; rise = 0; plateau = 0
except ValueError:
pass
try:
ev_date = date_type.fromisoformat(r["start_date"][:10])
except ValueError:
continue
events.append({
"ev_date": ev_date, "category": r["category"], "pips": pips,
"rise": rise, "plateau": plateau, "absorption": absorption, "dtype": dtype,
"virtual": False,
})
# Inject virtual events
for ve in (virtual_events or []):
try:
ev_date = date_type.fromisoformat(str(ve["date"])[:10])
@@ -1130,9 +1070,17 @@ def simulate_timeline(
from services.causal_graphs import evaluate_graph
# Structural pips — calculé une seule fois (overrides statiques, pas d'events)
gj_struct = _graph_json_for_eval(graph_def, {})
inputs_struct = _build_inputs(graph_def, overrides, {}, saturation=True)
vals_struct = evaluate_graph(gj_struct, inputs_struct)
structural_pips = round(float(vals_struct.get(output_id, 0.0)), 1)
fundamental_level_base = round(price_intercept + structural_pips * pip_to_price, 6)
timeline = []
cur = date_from
while cur <= today:
# Accumule les events virtuels (What-if) actifs ce jour
ev_by_cat: dict[str, float] = {}
for ev in events:
if ev["ev_date"] > cur:
@@ -1144,27 +1092,28 @@ def simulate_timeline(
cat = ev["category"]
ev_by_cat[cat] = ev_by_cat.get(cat, 0.0) + round(ev["pips"] * df, 2)
# Phase 2 : régime du jour → poids adaptatifs dans la formule output
ri = detect_regime(ev_by_cat)
gj = _graph_json_for_eval(graph_def, ri["weights"])
inputs = _build_inputs(graph_def, overrides, ev_by_cat, saturation=True)
vals = evaluate_graph(gj, inputs)
net = round(float(vals.get(output_id, 0.0)), 1)
# Structural pips (manual only, no events)
inputs_struct = _build_inputs(graph_def, overrides, {}, saturation=True)
gj_struct = _graph_json_for_eval(graph_def, {})
vals_struct = evaluate_graph(gj_struct, inputs_struct)
structural_pips = round(float(vals_struct.get(output_id, 0.0)), 1)
if ev_by_cat:
# Des events virtuels sont actifs → recalcul complet avec régime
ri = detect_regime(ev_by_cat)
gj = _graph_json_for_eval(graph_def, ri["weights"])
inputs = _build_inputs(graph_def, overrides, ev_by_cat, saturation=True)
vals = evaluate_graph(gj, inputs)
net = round(float(vals.get(output_id, 0.0)), 1)
regime_label = ri["regime"]
else:
# Pas d'events — on réutilise les valeurs structurelles
vals = vals_struct
net = structural_pips
regime_label = "BALANCED"
timeline.append({
"date": str(cur),
"net_pips": net,
"structural_pips": structural_pips,
"event_pips": round(net - structural_pips, 1),
"fundamental_level": round(price_intercept + structural_pips * pip_to_price, 6),
"fundamental_level": fundamental_level_base,
"synthetic_price": round(price_intercept + net * pip_to_price, 6),
"regime": ri["regime"],
"regime": regime_label,
"nodes": {k: round(float(v), 1) for k, v in vals.items()},
})
cur += timedelta(days=1)