feat: instrument analysis

This commit is contained in:
OpenSquared
2026-06-28 23:07:20 +02:00
parent aba599cec7
commit 5a0aa30d60
2 changed files with 59 additions and 11 deletions

View File

@@ -697,27 +697,37 @@ const FRISE_POPUP_W = 220 // largeur du popup
function CausalFrise({
events, templates, priceData, selectedDate, causalInsts,
chartDateToX, chartCanvasLeft, chartReady,
}: {
events: SnapshotEvent[]
templates: CausalTemplate[]
priceData: PriceCandle[]
selectedDate: string | null
causalInsts: string[]
events: SnapshotEvent[]
templates: CausalTemplate[]
priceData: PriceCandle[]
selectedDate: string | null
causalInsts: string[]
chartDateToX?: (d: string) => number | null
chartCanvasLeft?: number
chartReady?: number
}) {
const navigate = useNavigate()
const containerRef = useRef<HTMLDivElement>(null)
const [contWidth, setContWidth] = useState(400)
const [contLeft, setContLeft] = useState(0)
const [activeChip, setActiveChip] = useState<{
tmpl: CausalTemplate; ev: SnapshotEvent; chipX: number; chipY: number
} | null>(null)
useEffect(() => {
const el = containerRef.current; if (!el) return
setContWidth(el.getBoundingClientRect().width)
const obs = new ResizeObserver(e => setContWidth(e[0].contentRect.width))
const update = () => {
const rect = el.getBoundingClientRect()
setContWidth(rect.width)
setContLeft(rect.left)
}
update()
const obs = new ResizeObserver(update)
obs.observe(el)
return () => obs.disconnect()
}, [])
}, [chartReady]) // re-measure when chart is ready (layout may have changed)
// Close popup on outside click (tiny delay avoids self-close)
useEffect(() => {
@@ -745,7 +755,20 @@ function CausalFrise({
const minDate = priceData[0].time
const maxDate = priceData[priceData.length - 1].time
const usable = Math.max(contWidth, 1)
const tdToX = makeTdToX(priceData, usable)
// If chart coordinate bridge is available, use it for pixel-perfect alignment.
// chart.timeToCoordinate() gives x relative to chart canvas left edge.
// We subtract (chartCanvasLeft - contLeft) to convert to frise container coords.
// canvasOffset > 0 means chart canvas starts to the right of the frise container.
const canvasOffset = (chartCanvasLeft ?? 0) - contLeft
const tdToX: (d: string) => number = chartDateToX
? (d: string) => {
const x = chartDateToX(d)
if (x !== null) return x - canvasOffset
// Fallback for out-of-range dates: clamp to edges
return d <= minDate ? -canvasOffset : usable - canvasOffset
}
: makeTdToX(priceData, usable)
// Build chips (one per event × template)
type Chip = {
@@ -1004,6 +1027,10 @@ export default function InstrumentDashboard() {
const [theoryCurve, setTheoryCurve] = useState<TheoPoint[] | null>(null)
const [loadingTheory, setLoadingTheory] = useState(false)
const [showTheory, setShowTheory] = useState(false)
// Chart coordinate bridge — lets CausalFrise align with the chart's x-axis
const chartDateToXRef = useRef<((d: string) => number | null) | null>(null)
const chartCanvasLeftRef = useRef<number>(0)
const [chartReady, setChartReady] = useState(0) // bump to trigger frise re-render
const instrumentId = id.toUpperCase()
@@ -1020,6 +1047,8 @@ export default function InstrumentDashboard() {
setMacroAtDate(null)
setTheoryCurve(null)
setShowTheory(false)
chartDateToXRef.current = null
setChartReady(0)
api.get(`/instruments/${instrumentId}/snapshot?period=${period}`)
.then(r => {
setSnapshot(r.data)
@@ -1042,6 +1071,12 @@ export default function InstrumentDashboard() {
if (date) setSelectedDate(date)
}, [])
const handleChartReady = useCallback((fn: ((d: string) => number | null) | null, left: number) => {
chartDateToXRef.current = fn
chartCanvasLeftRef.current = left
setChartReady(k => k + 1)
}, [])
const toggleTheory = useCallback(() => {
if (showTheory) {
setShowTheory(false)
@@ -1253,6 +1288,7 @@ export default function InstrumentDashboard() {
chartType={chartStyle}
onDateHover={handleDateHover}
theoryCurve={showTheory && theoryCurve ? theoryCurve : undefined}
onChartReady={handleChartReady}
/>
{/* Date badge */}
@@ -1342,6 +1378,9 @@ export default function InstrumentDashboard() {
priceData={snapshot.price_data}
selectedDate={effectiveDate}
causalInsts={causalInsts}
chartDateToX={chartDateToXRef.current ?? undefined}
chartCanvasLeft={chartCanvasLeftRef.current}
chartReady={chartReady}
/>
</div>