Portfolio optimization (3 endpoints): - POST /portfolio/optimize - HRP optimal weights via scipy clustering - POST /portfolio/correlation - pairwise correlation matrix - POST /portfolio/risk-parity - inverse-volatility risk parity weights Congress tracking (2 endpoints): - GET /regulators/congress/trades - congress member stock trades - GET /regulators/congress/bills?query= - search congress bills Implementation: - portfolio_service.py: HRP with scipy fallback to inverse-vol - congress_service.py: multi-provider fallback pattern - 51 new tests (14 portfolio unit, 20 portfolio route, 12 congress unit, 7 congress route) - All 312 tests passing
69 lines
1.8 KiB
Python
69 lines
1.8 KiB
Python
"""Congress trading data: member trades and bill search."""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Any
|
|
|
|
from openbb import obb
|
|
|
|
from obb_utils import to_list
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def _try_obb_call(fn, *args, **kwargs) -> list[dict[str, Any]] | None:
|
|
"""Attempt a single OBB call and return to_list result, or None on failure."""
|
|
try:
|
|
result = await asyncio.to_thread(fn, *args, **kwargs)
|
|
return to_list(result)
|
|
except Exception as exc:
|
|
logger.debug("OBB call failed: %s", exc)
|
|
return None
|
|
|
|
|
|
def _get_congress_fn():
|
|
"""Resolve the congress trading OBB function safely."""
|
|
try:
|
|
return obb.regulators.government_us.congress_trading
|
|
except AttributeError:
|
|
logger.debug("obb.regulators.government_us.congress_trading not available")
|
|
return None
|
|
|
|
|
|
async def get_congress_trades() -> list[dict[str, Any]]:
|
|
"""Get recent US congress member stock trades.
|
|
|
|
Returns an empty list if the data provider is unavailable.
|
|
"""
|
|
fn = _get_congress_fn()
|
|
if fn is None:
|
|
return []
|
|
|
|
providers = ["quiverquant", "fmp"]
|
|
for provider in providers:
|
|
data = await _try_obb_call(fn, provider=provider)
|
|
if data is not None:
|
|
return data
|
|
|
|
logger.warning("All congress trades providers failed")
|
|
return []
|
|
|
|
|
|
async def search_congress_bills(query: str) -> list[dict[str, Any]]:
|
|
"""Search US congress bills by keyword.
|
|
|
|
Returns an empty list if the data provider is unavailable.
|
|
"""
|
|
fn = _get_congress_fn()
|
|
if fn is None:
|
|
return []
|
|
|
|
providers = ["quiverquant", "fmp"]
|
|
for provider in providers:
|
|
data = await _try_obb_call(fn, query, provider=provider)
|
|
if data is not None:
|
|
return data
|
|
|
|
logger.warning("All congress bills providers failed for query: %s", query)
|
|
return []
|