feat: instrument model
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user