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
tests/test_mappers.py Normal file
View File

@@ -0,0 +1,68 @@
from mappers import (
discover_items_from_list,
metrics_from_dict,
profile_from_dict,
quote_from_dict,
)
class TestQuoteFromDict:
def test_basic(self):
q = quote_from_dict("AAPL", {"name": "Apple", "last_price": 180.0})
assert q.symbol == "AAPL"
assert q.price == 180.0
def test_fallback_to_close(self):
q = quote_from_dict("AAPL", {"close": 175.0})
assert q.price == 175.0
def test_empty_dict(self):
q = quote_from_dict("AAPL", {})
assert q.price is None
class TestProfileFromDict:
def test_basic(self):
p = profile_from_dict("AAPL", {"name": "Apple", "sector": "Tech"})
assert p.sector == "Tech"
def test_description_fallback(self):
p = profile_from_dict("AAPL", {"long_description": "A company"})
assert p.description == "A company"
def test_employees_fallback(self):
p = profile_from_dict("AAPL", {"full_time_employees": 150000})
assert p.employees == 150000
class TestMetricsFromDict:
def test_basic(self):
m = metrics_from_dict("AAPL", {"pe_ratio": 28.0, "roe": 0.15})
assert m.pe_ratio == 28.0
assert m.roe == 0.15
def test_roe_fallback(self):
m = metrics_from_dict("AAPL", {"return_on_equity": 0.20})
assert m.roe == 0.20
def test_eps_fallback(self):
m = metrics_from_dict("AAPL", {"eps_ttm": 6.5})
assert m.eps == 6.5
def test_empty_dict(self):
m = metrics_from_dict("AAPL", {})
assert m.pe_ratio is None
class TestDiscoverItemsFromList:
def test_basic(self):
items = discover_items_from_list([
{"symbol": "TSLA", "price": 250.0},
{"symbol": "AAPL", "last_price": 180.0},
])
assert len(items) == 2
assert items[0]["symbol"] == "TSLA"
assert items[1]["price"] == 180.0
def test_empty_list(self):
assert discover_items_from_list([]) == []