From 4be3ad9ce3a0f921d041b7d57bb305d0ff1dae99 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Mon, 29 Jun 2026 11:15:08 +0200 Subject: [PATCH] feat: market event --- backend/routers/causal_lab.py | 5 +- frontend/src/pages/MarketEvents.tsx | 80 ++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/backend/routers/causal_lab.py b/backend/routers/causal_lab.py index 24bec22..58073b0 100644 --- a/backend/routers/causal_lab.py +++ b/backend/routers/causal_lab.py @@ -940,6 +940,7 @@ def analyze_event(body: AnalyzeRequest): "yields": yields, "activation": activation, "drift": drift_by_inst.get(primary_inst, {}), + "drift_by_inst": drift_by_inst, "prices_mode": prices.get("mode", "none"), "effective_lag_min": effective_lag, "effective_lag_days": effective_lag_days, @@ -961,7 +962,7 @@ def analyze_event(body: AnalyzeRequest): """, ( primary_inst, json.dumps(inputs), json.dumps(body.coef_overrides), json.dumps(node_values), json.dumps(actual_moves), activation.get("score"), - json.dumps(drift_by_inst.get(primary_inst, {})), analyzed_at, + json.dumps(drift_by_inst), analyzed_at, body.market_event_id, body.template_id, )) else: @@ -976,7 +977,7 @@ def analyze_event(body: AnalyzeRequest): json.dumps(inputs), json.dumps(body.coef_overrides), json.dumps(node_values), json.dumps(actual_moves), activation.get("score"), - json.dumps(drift_by_inst.get(primary_inst, {})), analyzed_at, + json.dumps(drift_by_inst), analyzed_at, )) conn.commit() diff --git a/frontend/src/pages/MarketEvents.tsx b/frontend/src/pages/MarketEvents.tsx index 3dc8251..94aa497 100644 --- a/frontend/src/pages/MarketEvents.tsx +++ b/frontend/src/pages/MarketEvents.tsx @@ -62,6 +62,7 @@ interface CausalAnalysis { inputs_json: Record prediction_json: Record actual_json: Record + drift_json?: Record graph_json?: { nodes: GraphNode[]; edges: GraphEdge[] } } @@ -395,8 +396,12 @@ function EventDetail({ const [instMsg, setInstMsg] = useState(null) const [anResult, setAnResult] = useState<{ score: number | null - preds: Record - actuals: Record + preds: Record + actuals: Record + drift?: Record + pricesMode?: string + lagMin?: number + lagDays?: number } | null>(null) const [graphData, setGraphData] = useState(null) @@ -417,10 +422,19 @@ function EventDetail({ const latest = data[0] if (latest.template_id) setSelTmpl(latest.template_id) if (Object.keys(latest.inputs_json || {}).length > 0) setInstInputs(latest.inputs_json) + const driftRaw = latest.drift_json || {} + // drift_json peut être { EURUSD: {...}, XAUUSD: {...} } (nouveau) ou { post_pips: ..., ... } (ancien) + const isDriftByInst = driftRaw && !('post_pips' in driftRaw) && Object.keys(driftRaw).length > 0 + const driftByInst = isDriftByInst + ? driftRaw + : latest.instrument && Object.keys(driftRaw).length > 0 + ? { [latest.instrument]: driftRaw } + : undefined setAnResult({ - score: latest.activation_score, - preds: latest.prediction_json || {}, - actuals: latest.actual_json || {}, + score: latest.activation_score, + preds: latest.prediction_json || {}, + actuals: latest.actual_json || {}, + drift: driftByInst, }) if (latest.graph_json?.nodes?.length) { setGraphData(latest.graph_json as GraphData) @@ -535,7 +549,15 @@ function EventDetail({ }) const d = await r.json() if (!r.ok) throw new Error(d.detail || r.statusText) - setAnResult({ score: d.activation?.score ?? null, preds: d.node_values ?? {}, actuals: d.actual_moves ?? {} }) + setAnResult({ + score: d.activation?.score ?? null, + preds: d.node_values ?? {}, + actuals: d.actual_moves ?? {}, + drift: d.drift_by_inst ?? (d.drift ? { [d.instrument]: d.drift } : undefined), + pricesMode: d.prices_mode, + lagMin: d.effective_lag_min, + lagDays: d.effective_lag_days, + }) if (d.graph_json?.nodes?.length) setGraphData(d.graph_json) await loadAnalyses() } catch (e: any) { setAnResult({ score: null, preds: {}, actuals: {} }) } @@ -850,6 +872,52 @@ function EventDetail({ + {/* Bandeau de détail du calcul réel */} + {anResult && (anResult.pricesMode || anResult.drift) && (() => { + const mode = anResult.pricesMode + const lagMin = anResult.lagMin ?? 0 + const lagDays = anResult.lagDays ?? 0 + const driftEntries = Object.entries(anResult.drift ?? {}) + return ( +
+
+ + {mode === 'intraday_5m' ? '5m intraday' : mode === 'daily' ? 'journalier' : mode ?? '—'} + + {mode === 'intraday_5m' && lagMin > 0 && ( + lag {lagMin}min + )} + {mode !== 'intraday_5m' && lagDays > 0 && ( + lag {lagDays}j + )} + {mode !== 'intraday_5m' && lagDays === 0 && ( + lag J = 0 (jour J) + )} +
+ {driftEntries.map(([inst, d]) => ( +
+ {inst} + {d.pre_pips != null && ( + + pré: 0 ? 'text-emerald-500' : 'text-red-500'}>{d.pre_pips > 0 ? '+' : ''}{d.pre_pips}p + + )} + + post: 0 ? 'text-emerald-400' : 'text-red-400'}> + {d.post_pips == null ? '—' : `${d.post_pips > 0 ? '+' : ''}${d.post_pips}p`} + + + {d.post_pips == null && ( + données manquantes (yfinance) + )} +
+ ))} +
+ ) + })()} + {/* Inline result — full-width graph + left recap */} {anResult && graphData?.nodes?.length && (() => { const inputNodes = graphData.nodes.filter(n => n.type === 'input')