feat: InstrumentDashboard — event timeline strip + crosshair date-aware cards

- InstrumentChart: onDateHover callback via subscribeCrosshairMove (useRef pattern)
- EventTimelineStrip: 3 rows LT/MT/CT with CSS-% bars aligned to chart X axis
- Cards date-aware: crosshair drives selectedDate; dateTrend + dateSignals computed
  client-side from lookup maps (priceMap/indMap/sortedDates) without extra API calls
- TrendCard: price, RSI, ATR, slopes, momentum, 52W range all at selected date
- RegimeCard: 5 signals recomputed at selected date; regime label from server
- Date badge above cards; blue tint when browsing history, grey on last date
- instrument_service.py: end_date in events; price_data built before events block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-24 22:21:04 +02:00
parent d47fc8f50d
commit 418d03254d
3 changed files with 447 additions and 231 deletions

View File

@@ -505,11 +505,19 @@ def _get_relevant_events(
asset_hit = any(ra in ev_assets for ra in related)
if keyword_hit or asset_hit:
filtered.append(ev)
filtered.append({
"date": ev_start,
"end_date": ev.get("end_date") or None,
"title": ev.get("name") or ev.get("event_name") or "",
"level": ev.get("level", "medium"),
"category": ev.get("category", ""),
"description": (ev.get("description") or "")[:120],
"impact_score": float(ev.get("impact_score") or 0.5),
})
# Sort by start_date desc, cap at 15
filtered.sort(key=lambda e: str(e.get("start_date", "") or ""), reverse=True)
return filtered[:15]
# Sort by start_date asc for timeline display, cap at 30
filtered.sort(key=lambda e: str(e.get("date", "") or ""))
return filtered[:30]
# ── Main snapshot ──────────────────────────────────────────────────────────────
@@ -557,15 +565,6 @@ async def get_snapshot(
change_abs = _round(last - prev)
change_pct = _round((last - prev) / abs(prev) * 100, 3)
# Events (last 1 year)
try:
from_date = (pd.Timestamp.now() - pd.Timedelta(days=365)).strftime("%Y-%m-%d")
to_date = pd.Timestamp.now().strftime("%Y-%m-%d")
events = _get_relevant_events(config, from_date=from_date, to_date=to_date)
except Exception as e:
logger.warning(f"[instrument_service] Event filtering error: {e}")
events = []
# Price data for chart (time + ohlcv)
price_data = []
if records:
@@ -579,6 +578,15 @@ async def get_snapshot(
"volume": r.get("volume", 0),
})
# Events spanning the chart period (built after price_data so dates are known)
try:
chart_start = price_data[0]["time"] if price_data else None
chart_end = price_data[-1]["time"] if price_data else None
events = _get_relevant_events(config, from_date=chart_start, to_date=chart_end)
except Exception as e:
logger.warning(f"[instrument_service] Event filtering error: {e}")
events = []
return {
"instrument": config,
"price_data": price_data,