"""Macro economic data via OpenBB FRED provider.""" import asyncio import logging from typing import Any from openbb import obb logger = logging.getLogger(__name__) PROVIDER = "fred" # Key FRED series IDs SERIES = { "fed_funds_rate": "FEDFUNDS", "us_10y_treasury": "DGS10", "us_2y_treasury": "DGS2", "cpi_yoy": "CPIAUCSL", "unemployment_rate": "UNRATE", "gdp_growth": "A191RL1Q225SBEA", "sp500": "SP500", "vix": "VIXCLS", } def _to_dicts(result: Any) -> list[dict[str, Any]]: if result is None or result.results is None: return [] if isinstance(result.results, list): return [ item.model_dump() if hasattr(item, "model_dump") else vars(item) for item in result.results ] if hasattr(result.results, "model_dump"): return [result.results.model_dump()] return [vars(result.results)] async def get_series( series_id: str, limit: int = 10, latest: bool = False, ) -> list[dict[str, Any]]: """Get a FRED time series by ID.""" try: fetch_limit = limit if not latest else None kwargs: dict[str, Any] = { "symbol": series_id, "provider": PROVIDER, } if fetch_limit is not None: kwargs["limit"] = fetch_limit result = await asyncio.to_thread( obb.economy.fred_series, **kwargs, ) items = _to_dicts(result) items = [ {**item, "date": str(item["date"])} if "date" in item and not isinstance(item["date"], str) else item for item in items ] if latest: items = items[-limit:] return items except Exception: logger.warning("Failed to fetch FRED series %s", series_id, exc_info=True) return [] async def get_macro_overview() -> dict[str, Any]: """Get a summary of key macro indicators.""" tasks = { name: get_series(series_id, limit=1, latest=True) for name, series_id in SERIES.items() } results = await asyncio.gather(*tasks.values(), return_exceptions=True) overview: dict[str, Any] = {} for (name, series_id), result in zip(SERIES.items(), results): if isinstance(result, BaseException): logger.warning("Failed to fetch %s: %s", name, result) overview[name] = None elif result and len(result) > 0: entry = result[-1] # FRED returns values keyed by series ID, not "value" value = entry.get(series_id) or entry.get("value") overview[name] = { "value": value, "date": str(entry.get("date", "")), } else: overview[name] = None return overview