From 2b1a43a093258a79253498783ae749eac5fd683b Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Tue, 30 Jun 2026 12:22:14 +0200 Subject: [PATCH] feat: market event --- frontend/src/pages/MarketEvents.tsx | 159 ++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 30 deletions(-) diff --git a/frontend/src/pages/MarketEvents.tsx b/frontend/src/pages/MarketEvents.tsx index 4ae12c6..c3569f6 100644 --- a/frontend/src/pages/MarketEvents.tsx +++ b/frontend/src/pages/MarketEvents.tsx @@ -383,6 +383,7 @@ function EventDetail({ const [editData, setEditData] = useState>({}) const [saving, setSaving] = useState(false) const [analyses, setAnalyses] = useState([]) + const [expandedAnalysis, setExpandedAnalysis] = useState(null) // Inline analysis const [templates, setTemplates] = useState([]) const [selTmpl, setSelTmpl] = useState(0) @@ -760,39 +761,137 @@ function EventDetail({ .filter(([k]) => !k.includes('error')) .sort(([, v1], [, v2]) => Math.abs(v2) - Math.abs(v1)) .slice(0, 3) + const isExpanded = expandedAnalysis === a.id + const gNodes = a.graph_json?.nodes || [] + const gEdges = a.graph_json?.edges || [] + const allVals: Record = { ...(a.inputs_json || {}), ...(a.prediction_json || {}) } + const inputNodes = gNodes.filter(n => n.type === 'input') + const outputNodes = gNodes.filter(n => n.type === 'market_asset' || n.type === 'output') + const midNodes = gNodes.filter(n => n.type !== 'input' && n.type !== 'market_asset' && n.type !== 'output') return ( -
- + +
+ + {/* Détail du calcul */} + {isExpanded && ( +
+ {/* Mini graphe */} + {gNodes.length > 0 && ( + + )} + + {/* Breakdown par couche */} +
+ {inputNodes.length > 0 && ( +
+
Entrées
+
+ {inputNodes.map(n => { + const v = allVals[n.id] + if (v === undefined) return null + return ( + + {n.label}: 0 ? 'text-blue-200' : 'text-red-300'}>{v > 0 ? '+' : ''}{v.toFixed(2)} + + ) + })} +
+
+ )} + + {midNodes.length > 0 && ( +
+
Propagation
+
+ {midNodes.map(n => { + const v = allVals[n.id] + if (v === undefined) return null + const fmt = n.unit === 'pips' ? Math.round(v).toString() + : n.unit === '%' ? v.toFixed(2) + '%' + : v.toFixed(3) + return ( + + {n.label}: 0 ? 'text-teal-400' : v < 0 ? 'text-orange-400' : 'text-slate-500'}>{v > 0 ? '+' : ''}{fmt} + + ) + })} +
+
+ )} + + {outputNodes.length > 0 && ( +
+
Prédictions vs Réel
+
+ {outputNodes.map(n => { + const pred = allVals[n.id] + const actual = (a.actual_json || {})[n.id] ?? (a.actual_json || {})[n.instrument || ''] + const fmt = (x: number) => `${x > 0 ? '+' : ''}${Math.round(x)}${n.unit === 'pips' ? 'p' : ''}` + return ( + 0 ? 'bg-emerald-900/20 border-emerald-700/30 text-emerald-400' : + pred < 0 ? 'bg-red-900/20 border-red-700/30 text-red-400' : + 'bg-slate-800/60 border-slate-700/40 text-slate-400')}> + {n.label}: {pred !== undefined ? fmt(pred) : '—'} + {actual !== undefined && ( + / réel {fmt(actual)} + )} + + ) + })} +
+
+ )} +
+ +
-
- {a.instrument} - {topPreds.map(([k, v]) => ( - 0 ? 'text-emerald-500' : 'text-red-500')}> - {k}: {v > 0 ? '+' : ''}{Math.round(v)} - - ))} -
- - + )} ) })}