feat: market event

This commit is contained in:
OpenSquared
2026-06-29 11:15:08 +02:00
parent 9f90c97461
commit 4be3ad9ce3
2 changed files with 77 additions and 8 deletions

View File

@@ -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()

View File

@@ -62,6 +62,7 @@ interface CausalAnalysis {
inputs_json: Record<string, number>
prediction_json: Record<string, number>
actual_json: Record<string, number>
drift_json?: Record<string, any>
graph_json?: { nodes: GraphNode[]; edges: GraphEdge[] }
}
@@ -395,8 +396,12 @@ function EventDetail({
const [instMsg, setInstMsg] = useState<string | null>(null)
const [anResult, setAnResult] = useState<{
score: number | null
preds: Record<string, number>
actuals: Record<string, number>
preds: Record<string, number>
actuals: Record<string, number>
drift?: Record<string, { pre_pips: number | null; post_pips: number | null; lag_min: number; lag_days: number }>
pricesMode?: string
lagMin?: number
lagDays?: number
} | null>(null)
const [graphData, setGraphData] = useState<GraphData | null>(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({
</button>
</div>
{/* 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 (
<div className="bg-slate-900/70 border border-slate-700/40 rounded px-3 py-2 space-y-1.5 text-[10px] font-mono">
<div className="flex items-center gap-2 flex-wrap">
<span className={clsx('px-1.5 py-0.5 rounded font-semibold',
mode === 'intraday_5m' ? 'bg-cyan-900/40 text-cyan-400 border border-cyan-700/40'
: 'bg-amber-900/40 text-amber-400 border border-amber-700/40')}>
{mode === 'intraday_5m' ? '5m intraday' : mode === 'daily' ? 'journalier' : mode ?? '—'}
</span>
{mode === 'intraday_5m' && lagMin > 0 && (
<span className="text-slate-400">lag <span className="text-cyan-300">{lagMin}min</span></span>
)}
{mode !== 'intraday_5m' && lagDays > 0 && (
<span className="text-slate-400">lag <span className="text-amber-300">{lagDays}j</span></span>
)}
{mode !== 'intraday_5m' && lagDays === 0 && (
<span className="text-slate-600 italic">lag J = 0 (jour J)</span>
)}
</div>
{driftEntries.map(([inst, d]) => (
<div key={inst} className="flex items-center gap-2 flex-wrap border-t border-slate-800/60 pt-1.5">
<span className="text-slate-400 w-16 truncate">{inst}</span>
{d.pre_pips != null && (
<span className="text-slate-500">
pré: <span className={d.pre_pips > 0 ? 'text-emerald-500' : 'text-red-500'}>{d.pre_pips > 0 ? '+' : ''}{d.pre_pips}p</span>
</span>
)}
<span className="text-slate-500">
post: <span className={d.post_pips == null ? 'text-slate-600' : d.post_pips > 0 ? 'text-emerald-400' : 'text-red-400'}>
{d.post_pips == null ? '—' : `${d.post_pips > 0 ? '+' : ''}${d.post_pips}p`}
</span>
</span>
{d.post_pips == null && (
<span className="text-red-500 italic">données manquantes (yfinance)</span>
)}
</div>
))}
</div>
)
})()}
{/* Inline result — full-width graph + left recap */}
{anResult && graphData?.nodes?.length && (() => {
const inputNodes = graphData.nodes.filter(n => n.type === 'input')