feat: Phase 2 — economic event surprise tracker (FRED actuals + z-score)
- economic_events table in DB (series_id, actual, forecast_baseline, surprise_pct, surprise_zscore, direction) - DB helpers: save_economic_event(), get_recent_economic_surprises(), get_economic_events_for_calendar() - fred_fetcher.py: _compute_zscore_surprise() computes 12-period MA as implied consensus + z-score deviation; save_fred_releases_to_db() persists releases per cycle; build_economic_surprise_block() formats significant surprises for AI prompt - auto_cycle.py: saves FRED releases to economic_events each cycle, appends surprise block to fred_block for injection into both suggestion and scoring prompts - data_fetcher.py: get_economic_calendar() now merges static upcoming events with past FRED actuals from DB (Prev/Fcst/Actual/z-score fields populated) - CalendarPage.tsx: past events show colored z-score badge (⚡ for |z|≥1.5, bullish/bearish colors) - EconomicEvent type: added surprise_zscore, surprise_direction, source fields Activates automatically once fred_api_key is set in Configuration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -431,15 +431,41 @@ def run_cycle_once(trigger: str = "auto") -> Dict[str, Any]:
|
||||
except Exception as _abs_e:
|
||||
logger.warning(f"[Cycle] Price absorption failed (non-blocking): {_abs_e}")
|
||||
|
||||
# ── Phase 2: FRED recent macro releases ──────────────────────────────
|
||||
# ── Phase 2: FRED recent macro releases + economic surprise tracking ────
|
||||
_fred_releases = []
|
||||
_fred_block = ""
|
||||
_surprise_block = ""
|
||||
try:
|
||||
from services.fred_fetcher import get_fred_recent_releases, build_fred_context_block
|
||||
from services.fred_fetcher import (
|
||||
get_fred_recent_releases, build_fred_context_block,
|
||||
save_fred_releases_to_db, build_economic_surprise_block,
|
||||
)
|
||||
_fred_key = get_config("fred_api_key") or ""
|
||||
_fred_releases = get_fred_recent_releases()
|
||||
_fred_block = build_fred_context_block(_fred_releases)
|
||||
if _fred_releases:
|
||||
logger.info(f"[Cycle {run_id[:16]}] FRED: {len(_fred_releases)} releases fetched")
|
||||
# Persist to economic_events with z-score surprise scoring
|
||||
try:
|
||||
_saved = save_fred_releases_to_db(_fred_releases, api_key=_fred_key)
|
||||
if _saved:
|
||||
logger.info(f"[Cycle {run_id[:16]}] FRED: {_saved} releases saved to economic_events")
|
||||
except Exception as _dbe:
|
||||
logger.warning(f"[Cycle] FRED DB save failed (non-blocking): {_dbe}")
|
||||
# Add surprise scores to release dicts for richer prompt block
|
||||
if _fred_key:
|
||||
try:
|
||||
from services.database import get_recent_economic_surprises
|
||||
_db_surprises = {ev["series_id"]: ev for ev in get_recent_economic_surprises(days=60)}
|
||||
for rel in _fred_releases:
|
||||
sid = rel.get("series_id", "")
|
||||
if sid in _db_surprises:
|
||||
rel["surprise_zscore"] = _db_surprises[sid].get("surprise_zscore")
|
||||
except Exception:
|
||||
pass
|
||||
_fred_block = build_fred_context_block(_fred_releases)
|
||||
_surprise_block = build_economic_surprise_block(days=14)
|
||||
if _surprise_block:
|
||||
_fred_block = _fred_block + "\n\n" + _surprise_block if _fred_block else _surprise_block
|
||||
except Exception as _fe:
|
||||
logger.warning(f"[Cycle] FRED fetch failed (non-blocking): {_fe}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user