From 5e654245004e1b039f48b307cc123948e90fdbcb Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Mon, 29 Jun 2026 00:06:14 +0200 Subject: [PATCH] feat: instrument analysis --- backend/routers/causal_lab.py | 6 +++++- backend/services/instrument_service.py | 10 +++++++++- frontend/src/pages/MarketEvents.tsx | 17 ++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/backend/routers/causal_lab.py b/backend/routers/causal_lab.py index 64992e1..1e3030f 100644 --- a/backend/routers/causal_lab.py +++ b/backend/routers/causal_lab.py @@ -1176,7 +1176,7 @@ def list_analyses( rows = conn.execute(f""" SELECT a.*, e.name as event_name, e.category, e.start_date, - t.name as template_name + t.name as template_name, t.graph_json as template_graph_json FROM causal_event_analyses a LEFT JOIN market_events e ON e.id = a.market_event_id LEFT JOIN causal_graph_templates t ON t.id = a.template_id @@ -1194,6 +1194,10 @@ def list_analyses( d[f] = json.loads(d[f] or "{}") except Exception: d[f] = {} + try: + d["graph_json"] = json.loads(d.get("template_graph_json") or "{}") + except Exception: + d["graph_json"] = {} result.append(d) return result diff --git a/backend/services/instrument_service.py b/backend/services/instrument_service.py index 9505a73..e45c334 100644 --- a/backend/services/instrument_service.py +++ b/backend/services/instrument_service.py @@ -5,6 +5,7 @@ regime detection, trend summary, event filtering, and AI narrative. """ import json import os +import re import logging import numpy as np import pandas as pd @@ -12,6 +13,11 @@ from pathlib import Path from typing import Dict, Any, List, Optional, Tuple from datetime import datetime, date + +def _base_ticker(t: str) -> str: + """Normalize Yahoo Finance tickers for comparison: EURUSD=X → EURUSD, BZ=F → BZ, ^GSPC → GSPC.""" + return re.sub(r'(=X|=F|=RR|-USD|\^)$', '', t.strip().upper()) + logger = logging.getLogger(__name__) # ── Config loading ───────────────────────────────────────────────────────────── @@ -586,8 +592,10 @@ def _get_relevant_events( asset_hit = any(ra in ev_assets for ra in related) # Always include events that have a causal analysis for this instrument + # Normalize tickers: strip Yahoo Finance suffixes (=X, =F, ^, -USD, =RR) for comparison analyzed = ev.get("analyzed_instruments") or "" - analysis_hit = inst_upper and inst_upper in [i.strip().upper() for i in analyzed.split(",") if i.strip()] + analyzed_bases = [_base_ticker(s) for s in analyzed.split(",") if s.strip()] + analysis_hit = bool(inst_upper and _base_ticker(inst_upper) in analyzed_bases) if keyword_hit or asset_hit or analysis_hit: filtered.append({ diff --git a/frontend/src/pages/MarketEvents.tsx b/frontend/src/pages/MarketEvents.tsx index 4fada7a..ed38556 100644 --- a/frontend/src/pages/MarketEvents.tsx +++ b/frontend/src/pages/MarketEvents.tsx @@ -60,6 +60,8 @@ interface CausalAnalysis { activation_score: number | null analyzed_at: string prediction_json: Record + actual_json: Record + graph_json?: { nodes: GraphNode[]; edges: GraphEdge[] } } interface TemplateRef { @@ -410,9 +412,18 @@ function EventDetail({ if (r.ok) { const data: CausalAnalysis[] = await r.json() setAnalyses(data) - // Pre-select the most recent analysis's template so the form isn't blank on re-open - if (data.length > 0 && data[0].template_id) { - setSelTmpl(data[0].template_id) + if (data.length > 0) { + const latest = data[0] + if (latest.template_id) setSelTmpl(latest.template_id) + // Reconstruct graph state from stored analysis so it's always visible on reopen + setAnResult({ + score: latest.activation_score, + preds: latest.prediction_json || {}, + actuals: latest.actual_json || {}, + }) + if (latest.graph_json?.nodes?.length) { + setGraphData(latest.graph_json as GraphData) + } } } }, [event.id])