feat: OpenBB Investment Analysis API
REST API wrapping OpenBB SDK for stock data, sentiment analysis, technical indicators, macro data, and rule-based portfolio analysis. - Stock data via yfinance (quote, profile, metrics, financials, historical, news) - News sentiment via Alpha Vantage (per-article, per-ticker scores) - Analyst data via Finnhub (recommendations, insider trades, upgrades) - Macro data via FRED (Fed rate, CPI, GDP, unemployment, treasury yields) - Technical indicators via openbb-technical (RSI, MACD, SMA, EMA, Bollinger) - Rule-based portfolio analysis engine (BUY_MORE/HOLD/SELL) - Stock discovery (gainers, losers, active, undervalued, growth) - 102 tests, all passing
This commit is contained in:
80
macro_service.py
Normal file
80
macro_service.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""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) -> list[dict[str, Any]]:
|
||||
"""Get a FRED time series by ID."""
|
||||
try:
|
||||
result = await asyncio.to_thread(
|
||||
obb.economy.fred_series,
|
||||
symbol=series_id,
|
||||
limit=limit,
|
||||
provider=PROVIDER,
|
||||
)
|
||||
items = _to_dicts(result)
|
||||
for item in items:
|
||||
if "date" in item and not isinstance(item["date"], str):
|
||||
item = {**item, "date": str(item["date"])}
|
||||
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)
|
||||
for name, series_id in SERIES.items()
|
||||
}
|
||||
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
|
||||
|
||||
overview: dict[str, Any] = {}
|
||||
for name, result in zip(tasks.keys(), 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[0]
|
||||
overview[name] = {
|
||||
"value": entry.get("value"),
|
||||
"date": str(entry.get("date", "")),
|
||||
}
|
||||
else:
|
||||
overview[name] = None
|
||||
|
||||
return overview
|
||||
Reference in New Issue
Block a user