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

View File

@@ -0,0 +1,47 @@
from openbb_service import _to_dicts, _first_or_empty
class MockModel:
def __init__(self, data: dict):
self._data = data
def model_dump(self):
return self._data
class MockOBBject:
def __init__(self, results):
self.results = results
class TestToDicts:
def test_none_result(self):
assert _to_dicts(None) == []
def test_none_results(self):
obj = MockOBBject(results=None)
assert _to_dicts(obj) == []
def test_list_results(self):
obj = MockOBBject(results=[
MockModel({"a": 1}),
MockModel({"b": 2}),
])
result = _to_dicts(obj)
assert len(result) == 2
assert result[0] == {"a": 1}
def test_single_result(self):
obj = MockOBBject(results=MockModel({"x": 42}))
result = _to_dicts(obj)
assert result == [{"x": 42}]
class TestFirstOrEmpty:
def test_empty(self):
assert _first_or_empty(None) == {}
def test_with_data(self):
obj = MockOBBject(results=[MockModel({"price": 150.0})])
result = _first_or_empty(obj)
assert result == {"price": 150.0}