feat: market event

This commit is contained in:
OpenSquared
2026-06-30 13:55:03 +02:00
parent 2b1a43a093
commit 69ca36bd7e

View File

@@ -51,6 +51,13 @@ interface MarketEvent {
evaluated?: boolean
}
function resolveFormula(formula: string, coefs: Record<string, GraphCoef>): string {
return formula.replace(/\{\{(\w+)\}\}/g, (_, k) => {
const v = coefs[k]?.value
return v !== undefined ? String(v) : `[${k}]`
})
}
interface CausalAnalysis {
id: number
market_event_id: number
@@ -63,7 +70,7 @@ interface CausalAnalysis {
prediction_json: Record<string, number>
actual_json: Record<string, number>
drift_json?: Record<string, any>
graph_json?: { nodes: GraphNode[]; edges: GraphEdge[] }
graph_json?: GraphData
}
interface TemplateRef {
@@ -73,9 +80,10 @@ interface TemplateRef {
instruments: string[]
}
interface GraphNode { id: string; label: string; type: string; x: number; y: number; unit?: string; instrument?: string }
interface GraphEdge { from: string; to: string; style?: string; sign?: string }
interface GraphData { nodes: GraphNode[]; edges: GraphEdge[] }
interface GraphNode { id: string; label: string; type: string; x: number; y: number; unit?: string; instrument?: string; formula?: string }
interface GraphEdge { from: string; to: string; style?: string; sign?: string; label?: string }
interface GraphCoef { value: number; description?: string }
interface GraphData { nodes: GraphNode[]; edges: GraphEdge[]; coefficients?: Record<string, GraphCoef> }
// ── Constants ─────────────────────────────────────────────────────────────────
@@ -764,6 +772,7 @@ function EventDetail({
const isExpanded = expandedAnalysis === a.id
const gNodes = a.graph_json?.nodes || []
const gEdges = a.graph_json?.edges || []
const gCoefs = a.graph_json?.coefficients || {}
const allVals: Record<string, number> = { ...(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')
@@ -842,17 +851,24 @@ function EventDetail({
{midNodes.length > 0 && (
<div>
<div className="text-[10px] text-slate-500 uppercase tracking-wider mb-1">Propagation</div>
<div className="flex flex-wrap gap-1">
<div className="space-y-0.5">
{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)
: n.unit === '%' ? v.toFixed(3) + '%'
: v.toFixed(4)
const formula = n.formula ? resolveFormula(n.formula, gCoefs) : null
return (
<span key={n.id} className="bg-slate-800/60 border border-slate-700/40 text-slate-300 px-1.5 py-0.5 rounded font-mono text-[10px]">
{n.label}: <span className={v > 0 ? 'text-teal-400' : v < 0 ? 'text-orange-400' : 'text-slate-500'}>{v > 0 ? '+' : ''}{fmt}</span>
</span>
<div key={n.id} className="flex items-baseline gap-2 flex-wrap">
<span className="text-slate-400 text-[10px] font-medium shrink-0">{n.label}:</span>
<span className={clsx('font-mono text-[10px] font-bold shrink-0', v > 0 ? 'text-teal-400' : v < 0 ? 'text-orange-400' : 'text-slate-500')}>
{v > 0 ? '+' : ''}{fmt}
</span>
{formula && (
<span className="text-[9px] text-slate-600 font-mono truncate">= {formula}</span>
)}
</div>
)
})}
</div>
@@ -862,21 +878,29 @@ function EventDetail({
{outputNodes.length > 0 && (
<div>
<div className="text-[10px] text-slate-500 uppercase tracking-wider mb-1">Prédictions vs Réel</div>
<div className="flex flex-wrap gap-1">
<div className="space-y-0.5">
{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' : ''}`
const actual = (a.actual_json || {})[n.id]
?? (a.actual_json || {})[n.instrument || '']
?? (a.actual_json || {})[(n.instrument || '').toUpperCase()]
const fmt = (x: number) => `${x > 0 ? '+' : ''}${Math.round(x)}${n.unit === 'pips' ? 'p' : n.unit === '$/oz' ? '$' : ''}`
const formula = n.formula ? resolveFormula(n.formula, gCoefs) : null
return (
<span key={n.id} className={clsx('border px-2 py-0.5 rounded font-mono text-[10px]',
pred > 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 && (
<span className="text-slate-500 ml-1">/ réel {fmt(actual)}</span>
<div key={n.id} className="flex items-baseline gap-2 flex-wrap">
<span className={clsx('border px-1.5 py-0.5 rounded font-mono text-[10px] shrink-0',
pred > 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 && (
<span className="text-slate-500 ml-1.5">/ réel {fmt(actual)}</span>
)}
</span>
{formula && (
<span className="text-[9px] text-slate-600 font-mono truncate">= {formula}</span>
)}
</span>
</div>
)
})}
</div>