feat: causal lab

This commit is contained in:
OpenSquared
2026-06-28 18:08:09 +02:00
parent c286c7c000
commit 6ebbf4326e
5 changed files with 481 additions and 133 deletions

View File

@@ -24,6 +24,12 @@ interface ChartEvent {
description?: string
}
interface TheoPoint {
date: string
cumulative_pips: number
contributions: { template_name: string; event_name: string; event_date: string; pips: number; decay_factor: number }[]
}
interface Props {
priceData: PriceCandle[]
indicators: Record<string, LinePoint[]>
@@ -31,8 +37,11 @@ interface Props {
height?: number
chartType?: 'candles' | 'line'
onDateHover?: (date: string | null) => void
theoryCurve?: TheoPoint[]
}
export type { TheoPoint }
const MA_COLORS: Record<string, string> = {
ma20: '#f59e0b',
ma50: '#3b82f6',
@@ -58,7 +67,7 @@ const CAT_LABELS: Record<string, string> = {
technical: 'Tech.',
}
export default function InstrumentChart({ priceData, indicators, events = [], height = 420, chartType = 'candles', onDateHover }: Props) {
export default function InstrumentChart({ priceData, indicators, events = [], height = 420, chartType = 'candles', onDateHover, theoryCurve }: Props) {
const containerRef = useRef<HTMLDivElement>(null)
const cleanupRef = useRef<(() => void) | null>(null)
const onHoverRef = useRef(onDateHover)
@@ -173,6 +182,30 @@ export default function InstrumentChart({ priceData, indicators, events = [], he
chart.addLineSeries(bbOpts).setData(indicators.bb_lower)
}
// ── Theoretical curve — left scale (pips) ─────────────────────────────
if (theoryCurve?.length) {
chart.priceScale('left').applyOptions({
visible: true,
borderColor: 'rgba(167,139,250,0.25)',
textColor: '#a78bfa',
scaleMargins: { top: 0.1, bottom: 0.1 },
})
const theorySeries = chart.addLineSeries({
color: 'rgba(167,139,250,0.75)',
lineWidth: 1.5,
priceScaleId: 'left',
title: 'Δ pips théorique',
priceLineVisible: false,
lastValueVisible: true,
crosshairMarkerVisible: true,
crosshairMarkerRadius: 3,
})
const theoryData = theoryCurve
.filter(p => p.contributions.length > 0)
.map(p => ({ time: p.date as any, value: p.cumulative_pips }))
if (theoryData.length) theorySeries.setData(theoryData)
}
chart.timeScale().fitContent()
// ── Star overlay — ★ icons positioned via chart coordinate API ────────
@@ -293,7 +326,7 @@ export default function InstrumentChart({ priceData, indicators, events = [], he
cleanupRef.current?.()
cleanupRef.current = null
}
}, [priceData, indicators, events, height, chartType])
}, [priceData, indicators, events, height, chartType, theoryCurve])
return (
<div className="bg-dark-900/60 rounded-xl border border-slate-700/40 overflow-hidden">
@@ -307,6 +340,12 @@ export default function InstrumentChart({ priceData, indicators, events = [], he
<span className="flex items-center gap-1.5 opacity-40">
<span className="inline-block w-5 border-t border-dashed border-slate-400" />BB(20,2)
</span>
{theoryCurve?.some(p => p.contributions.length > 0) && (
<span className="flex items-center gap-1.5" style={{ color: '#a78bfa' }}>
<span className="inline-block w-5 h-0.5 rounded" style={{ background: '#a78bfa' }} />
Théorie (pips)
</span>
)}
<span className="ml-auto flex items-center gap-2.5">
{Object.entries(CAT_COLORS).map(([cat, c]) => (
<span key={cat} className="flex items-center gap-0.5" style={{ color: c }}>