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