From 8b59eff7448c7495ac49e75b879fbd8b54b2406b Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Wed, 1 Jul 2026 16:16:51 +0200 Subject: [PATCH] feat: macro series --- backend/routers/eco.py | 4 +++ backend/services/database.py | 10 ++++++++ backend/services/fred_bootstrap.py | 39 ++++++++++++++++++++++++------ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/backend/routers/eco.py b/backend/routers/eco.py index 0cf6dd0..dd7c5f7 100644 --- a/backend/routers/eco.py +++ b/backend/routers/eco.py @@ -663,6 +663,10 @@ def series_history( ff_params: list = [series_id] if from_date: ff_where.append("event_date >= ?"); ff_params.append(from_date) + # Filter to the series' native currency to avoid mixing countries + ff_currency = meta.get("ff_currency") + if ff_currency: + ff_where.append("currency = ?"); ff_params.append(ff_currency) ff_rows = conn.execute( f"SELECT event_date, event_time, event_name, actual_value, " diff --git a/backend/services/database.py b/backend/services/database.py index 24006c1..c2aef85 100644 --- a/backend/services/database.py +++ b/backend/services/database.py @@ -1228,6 +1228,16 @@ def init_db(): except Exception: pass + # ── Migrations ──────────────────────────────────────────────────────────── + # Re-assign ADP events that were incorrectly mapped to PAYEMS + try: + c.execute( + "UPDATE ff_calendar SET series_id = 'ADP_NFP' " + "WHERE series_id = 'PAYEMS' AND event_name LIKE 'ADP%'" + ) + except Exception: + pass + conn.commit() conn.close() diff --git a/backend/services/fred_bootstrap.py b/backend/services/fred_bootstrap.py index 5aba140..ff57d72 100644 --- a/backend/services/fred_bootstrap.py +++ b/backend/services/fred_bootstrap.py @@ -29,39 +29,56 @@ FRED_SERIES: Dict[str, Dict[str, Any]] = { "unit": "K", "freq": "monthly", "category": "employment", + "ff_currency": "USD", "higher_is_bullish": True, "assets": ["SPY", "QQQ", "EURUSD=X", "TLT"], "zscore_window": 12, "transform": None, "delta_absolute": False, }, + "ADP_NFP": { + "name": "ADP Non-Farm Employment", + "unit": "K", + "freq": "monthly", + "category": "employment", + "ff_currency": "USD", + "higher_is_bullish": True, + "assets": ["SPY", "QQQ"], + "zscore_window": 12, + "transform": None, + "delta_absolute": False, + "ff_only": True, + }, "UNRATE": { "name": "Unemployment Rate", "unit": "%", "freq": "monthly", "category": "employment", + "ff_currency": "USD", "higher_is_bullish": False, "assets": ["SPY", "QQQ", "EURUSD=X"], "zscore_window": 12, "transform": None, - "delta_absolute": True, # pp change (e.g. 4.1 → 4.0 = -0.1 pp) + "delta_absolute": True, }, "CPIAUCSL": { "name": "CPI All Items (YoY %)", "unit": "%", "freq": "monthly", "category": "inflation", + "ff_currency": "USD", "higher_is_bullish": False, "assets": ["TLT", "GLD", "EURUSD=X", "SPY"], "zscore_window": 12, - "transform": "yoy_pct", # FRED gives index level → compute YoY - "delta_absolute": True, # pp change between consecutive YoY readings + "transform": "yoy_pct", + "delta_absolute": True, }, "CPILFESL": { "name": "Core CPI (YoY %)", "unit": "%", "freq": "monthly", "category": "inflation", + "ff_currency": "USD", "higher_is_bullish": False, "assets": ["TLT", "GLD", "EURUSD=X"], "zscore_window": 12, @@ -73,6 +90,7 @@ FRED_SERIES: Dict[str, Dict[str, Any]] = { "unit": "%", "freq": "monthly", "category": "inflation", + "ff_currency": "USD", "higher_is_bullish": False, "assets": ["TLT", "GLD", "SPY"], "zscore_window": 12, @@ -84,34 +102,36 @@ FRED_SERIES: Dict[str, Dict[str, Any]] = { "unit": "%", "freq": "monthly", "category": "monetary", + "ff_currency": "USD", "higher_is_bullish": False, "assets": ["TLT", "SPY", "EURUSD=X", "GLD"], "zscore_window": 12, "transform": None, - "delta_absolute": True, # bp/pp move + "delta_absolute": True, }, "ICSA": { "name": "Initial Jobless Claims", "unit": "K", "freq": "weekly", "category": "employment", + "ff_currency": "USD", "higher_is_bullish": False, "assets": ["SPY", "QQQ"], "zscore_window": 52, - "transform": "div1000", # FRED gives raw count → display in K + "transform": "div1000", "delta_absolute": False, }, "GDPC1": { - # Real GDP level in billions (chained 2017$) → compute QoQ annualized growth "name": "GDP Growth (QoQ Ann. %)", "unit": "%", "freq": "quarterly", "category": "growth", + "ff_currency": "USD", "higher_is_bullish": True, "assets": ["SPY", "QQQ", "EURUSD=X", "TLT"], "zscore_window": 8, "transform": "qoq_annualized", - "delta_absolute": True, # pp change between quarterly readings + "delta_absolute": True, }, "BAMLH0A0HYM2": { "name": "HY Credit Spread (OAS)", @@ -122,7 +142,7 @@ FRED_SERIES: Dict[str, Dict[str, Any]] = { "assets": ["HYG", "LQD", "SPY"], "zscore_window": 52, "transform": None, - "delta_absolute": True, # pp change in spread + "delta_absolute": True, }, "T10Y2Y": { "name": "Yield Spread 10Y-2Y", @@ -294,6 +314,9 @@ def bootstrap_fred( if not meta: logger.warning(f"[FRED bootstrap] Unknown series: {sid}") continue + if meta.get("ff_only"): + results[sid] = {"skipped": "ff_calendar only — no FRED fetch"} + continue # Warm-up period: extra years before from_date for transform + z-score transform = meta.get("transform")