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:
Yaojia Wang
2026-03-09 00:20:10 +01:00
commit ad45cb429c
30 changed files with 3107 additions and 0 deletions

68
mappers.py Normal file
View File

@@ -0,0 +1,68 @@
"""Shared mapping functions from raw dicts to response models."""
from typing import Any
from models import (
DiscoverItem,
MetricsResponse,
ProfileResponse,
QuoteResponse,
)
def quote_from_dict(symbol: str, data: dict[str, Any]) -> QuoteResponse:
return QuoteResponse(
symbol=symbol,
name=data.get("name"),
price=data.get("last_price") or data.get("close"),
change=data.get("change"),
change_percent=data.get("change_percent"),
volume=data.get("volume"),
market_cap=data.get("market_cap"),
currency=data.get("currency"),
)
def profile_from_dict(symbol: str, data: dict[str, Any]) -> ProfileResponse:
return ProfileResponse(
symbol=symbol,
name=data.get("name"),
sector=data.get("sector"),
industry=data.get("industry"),
country=data.get("country"),
description=data.get("long_description") or data.get("description"),
website=data.get("website"),
employees=data.get("full_time_employees") or data.get("employees"),
)
def metrics_from_dict(symbol: str, data: dict[str, Any]) -> MetricsResponse:
return MetricsResponse(
symbol=symbol,
pe_ratio=data.get("pe_ratio"),
pb_ratio=data.get("pb_ratio"),
ps_ratio=data.get("ps_ratio"),
peg_ratio=data.get("peg_ratio"),
roe=data.get("roe") or data.get("return_on_equity"),
roa=data.get("roa") or data.get("return_on_assets"),
dividend_yield=data.get("dividend_yield"),
beta=data.get("beta"),
eps=data.get("eps_ttm") or data.get("eps"),
revenue_growth=data.get("revenue_growth"),
earnings_growth=data.get("earnings_growth"),
)
def discover_item_from_dict(data: dict[str, Any]) -> DiscoverItem:
return DiscoverItem(
symbol=data.get("symbol"),
name=data.get("name"),
price=data.get("price") or data.get("last_price"),
change_percent=data.get("change_percent") or data.get("percent_change"),
volume=data.get("volume"),
market_cap=data.get("market_cap"),
)
def discover_items_from_list(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [discover_item_from_dict(item).model_dump() for item in items]