feat: market event
This commit is contained in:
@@ -1014,11 +1014,12 @@ function CausalFrise({
|
|||||||
// ── ExplanationScore ──────────────────────────────────────────────────────────
|
// ── ExplanationScore ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function ExplanationScore({
|
function ExplanationScore({
|
||||||
events, templates, causalInsts,
|
events, templates, causalInsts, onRefreshDone,
|
||||||
}: {
|
}: {
|
||||||
events: SnapshotEvent[]
|
events: SnapshotEvent[]
|
||||||
templates: CausalTemplate[]
|
templates: CausalTemplate[]
|
||||||
causalInsts: string[]
|
causalInsts: string[]
|
||||||
|
onRefreshDone?: () => void
|
||||||
}) {
|
}) {
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
const [refreshDone, setRefreshDone] = useState(false)
|
const [refreshDone, setRefreshDone] = useState(false)
|
||||||
@@ -1059,6 +1060,7 @@ function ExplanationScore({
|
|||||||
try {
|
try {
|
||||||
await api.post('/causal-lab/auto-analyze/refresh')
|
await api.post('/causal-lab/auto-analyze/refresh')
|
||||||
setRefreshDone(true)
|
setRefreshDone(true)
|
||||||
|
onRefreshDone?.()
|
||||||
setTimeout(() => setRefreshDone(false), 3000)
|
setTimeout(() => setRefreshDone(false), 3000)
|
||||||
} finally {
|
} finally {
|
||||||
setRefreshing(false)
|
setRefreshing(false)
|
||||||
@@ -1137,16 +1139,8 @@ export default function InstrumentDashboard() {
|
|||||||
api.get('/causal-lab/templates').then(r => setTemplates(r.data)).catch(() => {})
|
api.get('/causal-lab/templates').then(r => setTemplates(r.data)).catch(() => {})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchSnapshot = useCallback(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setSnapshot(null)
|
|
||||||
setNarrative('')
|
|
||||||
setSelectedDate(null)
|
|
||||||
setMacroAtDate(null)
|
|
||||||
setTheoryCurve(null)
|
|
||||||
setShowTheory(false)
|
|
||||||
chartDateToXRef.current = null
|
|
||||||
setChartReady(0)
|
|
||||||
api.get(`/instruments/${instrumentId}/snapshot?period=${period}`)
|
api.get(`/instruments/${instrumentId}/snapshot?period=${period}`)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
setSnapshot(r.data)
|
setSnapshot(r.data)
|
||||||
@@ -1157,6 +1151,18 @@ export default function InstrumentDashboard() {
|
|||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}, [instrumentId, period])
|
}, [instrumentId, period])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSnapshot(null)
|
||||||
|
setNarrative('')
|
||||||
|
setSelectedDate(null)
|
||||||
|
setMacroAtDate(null)
|
||||||
|
setTheoryCurve(null)
|
||||||
|
setShowTheory(false)
|
||||||
|
chartDateToXRef.current = null
|
||||||
|
setChartReady(0)
|
||||||
|
fetchSnapshot()
|
||||||
|
}, [instrumentId, period])
|
||||||
|
|
||||||
const loadNarrative = useCallback(() => {
|
const loadNarrative = useCallback(() => {
|
||||||
setLoadingNarr(true)
|
setLoadingNarr(true)
|
||||||
api.post(`/instruments/${instrumentId}/narrative`)
|
api.post(`/instruments/${instrumentId}/narrative`)
|
||||||
@@ -1525,6 +1531,7 @@ export default function InstrumentDashboard() {
|
|||||||
events={snapshot.events}
|
events={snapshot.events}
|
||||||
templates={templates}
|
templates={templates}
|
||||||
causalInsts={causalInsts}
|
causalInsts={causalInsts}
|
||||||
|
onRefreshDone={fetchSnapshot}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1000,6 +1000,38 @@ function EventDetail({
|
|||||||
{outputNodes.length > 0 && (
|
{outputNodes.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[10px] text-slate-600 uppercase tracking-wide mb-1">Prédictions</div>
|
<div className="text-[10px] text-slate-600 uppercase tracking-wide mb-1">Prédictions</div>
|
||||||
|
{(() => {
|
||||||
|
const nodeScores = outputNodes
|
||||||
|
.map(n => {
|
||||||
|
const pred = anResult.preds[n.id]
|
||||||
|
const act = anResult.actuals[n.instrument || n.id] ?? anResult.actuals[n.id]
|
||||||
|
if (pred == null || act == null) return null
|
||||||
|
if (pred === 0 && act === 0) return 100
|
||||||
|
if (pred === 0) return 40
|
||||||
|
const sameDir = pred * act > 0
|
||||||
|
if (!sameDir) return 0
|
||||||
|
const ratio = Math.min(Math.abs(act), Math.abs(pred)) / Math.max(Math.abs(act), Math.abs(pred))
|
||||||
|
return Math.round(50 + ratio * 50)
|
||||||
|
})
|
||||||
|
.filter((s): s is number => s != null)
|
||||||
|
const graphScore = nodeScores.length > 0
|
||||||
|
? Math.round(nodeScores.reduce((a, b) => a + b, 0) / nodeScores.length)
|
||||||
|
: null
|
||||||
|
if (graphScore == null) return null
|
||||||
|
const barCls = graphScore >= 70 ? 'bg-emerald-500' : graphScore >= 40 ? 'bg-amber-500' : 'bg-red-500'
|
||||||
|
const txtCls = graphScore >= 70 ? 'text-emerald-400' : graphScore >= 40 ? 'text-amber-400' : 'text-red-400'
|
||||||
|
return (
|
||||||
|
<div className="mb-2 pb-2 border-b border-slate-800/60">
|
||||||
|
<div className="flex items-center justify-between text-[9px] text-slate-600 mb-0.5">
|
||||||
|
<span>Précision</span>
|
||||||
|
<span className={txtCls}>{graphScore}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-1 bg-slate-800 rounded-full overflow-hidden">
|
||||||
|
<div className={clsx('h-full rounded-full', barCls)} style={{ width: `${graphScore}%` }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
{outputNodes.map(n => {
|
{outputNodes.map(n => {
|
||||||
const pred = anResult.preds[n.id]
|
const pred = anResult.preds[n.id]
|
||||||
const actual = anResult.actuals[n.instrument || n.id] ?? anResult.actuals[n.id]
|
const actual = anResult.actuals[n.instrument || n.id] ?? anResult.actuals[n.id]
|
||||||
|
|||||||
Reference in New Issue
Block a user