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:
OpenSquared
2026-06-22 14:00:35 +02:00
parent 3edbd6b0b7
commit d178615c74
6 changed files with 346 additions and 29 deletions

View File

@@ -49,11 +49,27 @@ function EventCard({ ev }: { ev: EconomicEvent }) {
{format(evDate, "EEEE, MMM d yyyy")} · {ev.country}
</div>
</div>
<div className="text-right shrink-0">
<div className="text-right shrink-0 space-y-0.5">
<div className={clsx('text-xs font-semibold', cfg.color.split(' ')[0])}>{cfg.label}</div>
{ev.previous && <div className="text-xs text-slate-600 mt-0.5">Prev: {ev.previous}</div>}
{ev.previous && <div className="text-xs text-slate-600">Prev: {ev.previous}</div>}
{ev.forecast && <div className="text-xs text-slate-500">Fcst: {ev.forecast}</div>}
{ev.actual && <div className="text-xs text-emerald-400 font-bold">Actual: {ev.actual}</div>}
{ev.actual && (
<div className="text-xs text-emerald-400 font-bold">Actual: {ev.actual}</div>
)}
{ev.surprise_zscore !== undefined && Math.abs(ev.surprise_zscore) >= 0.8 && (
<div className={clsx(
'text-xs font-bold px-1.5 py-0.5 rounded border',
ev.surprise_direction === 'bullish'
? 'text-emerald-400 border-emerald-700/40 bg-emerald-900/20'
: ev.surprise_direction === 'bearish'
? 'text-red-400 border-red-700/40 bg-red-900/20'
: 'text-slate-400 border-slate-700/40'
)}>
z={ev.surprise_zscore > 0 ? '+' : ''}{ev.surprise_zscore?.toFixed(1)} {
Math.abs(ev.surprise_zscore) >= 1.5 ? '⚡' : ''
}
</div>
)}
</div>
</div>
{ev.asset_impact && ev.asset_impact.length > 0 && (

View File

@@ -124,6 +124,9 @@ export interface EconomicEvent {
previous?: string
forecast?: string
actual?: string
surprise_zscore?: number
surprise_direction?: 'bullish' | 'bearish' | 'neutral'
source?: string
}
export interface HistoricalCandle {