feat: driver-based timeline strip, regime signal metrics, drivers editor
- instruments.json: add keywords array to every driver across 20 instruments
(Fed, BCE, BOJ, OPEC, CPI, AI, EIA, etc.) for event-to-driver matching
- instrument_service.py: add update_instrument_drivers() persisting changes to JSON
and refreshing in-memory cache
- instruments.py: add PUT /api/instruments/{id}/drivers endpoint (DriverUpdate model)
- InstrumentDashboard:
* RegimeCard: replace regime score bars with 6-metric signal grid
(MA50/MA200 position, MA50 slope, MA200 slope, momentum 20j, dist MA200, ATR vol ratio)
with colour-coded values and contextual sub-labels (Golden cross, Surextension, etc.)
* EventTimelineStrip: rows now keyed by top-4 instrument drivers (by weight)
instead of LT/MT/CT; events matched via case-insensitive keyword scan against
title + description + category; fallback dashed line when no events match
* DriversPanel: inline edit panel (toggle via Drivers button in header);
edit label, weight, keywords (comma-separated) per driver; add/remove drivers;
saves via PUT /api/instruments/{id}/drivers; optimistic local state update
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ Instrument Dashboard Router.
|
||||
Exposes per-instrument snapshot (price, indicators, regime, trend, events) and AI narrative.
|
||||
"""
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from services.instrument_service import (
|
||||
@@ -10,8 +11,12 @@ from services.instrument_service import (
|
||||
get_instrument,
|
||||
get_snapshot,
|
||||
get_narrative,
|
||||
update_instrument_drivers,
|
||||
)
|
||||
|
||||
class DriverUpdate(BaseModel):
|
||||
drivers: List[Dict[str, Any]]
|
||||
|
||||
router = APIRouter(prefix="/api/instruments", tags=["instruments"])
|
||||
|
||||
|
||||
@@ -63,3 +68,20 @@ async def generate_narrative(
|
||||
"instrument_name": config.get("name", instrument_id),
|
||||
"narrative": narrative,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{instrument_id}/drivers")
|
||||
def update_drivers(instrument_id: str, body: DriverUpdate) -> Dict[str, Any]:
|
||||
"""
|
||||
Persist updated drivers (label, weight, keywords) for an instrument to instruments.json.
|
||||
"""
|
||||
config = get_instrument(instrument_id)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail=f"Instrument '{instrument_id}' not found")
|
||||
|
||||
try:
|
||||
update_instrument_drivers(instrument_id, body.drivers)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
return {"ok": True, "instrument_id": instrument_id.upper(), "drivers_count": len(body.drivers)}
|
||||
|
||||
Reference in New Issue
Block a user