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
72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
"""Routes for regulatory data (CFTC, SEC, Congress)."""
|
|
|
|
from fastapi import APIRouter, Path, Query
|
|
|
|
from models import ApiResponse
|
|
from route_utils import safe, validate_symbol
|
|
import regulators_service
|
|
import congress_service
|
|
|
|
router = APIRouter(prefix="/api/v1/regulators")
|
|
|
|
|
|
@router.get("/cot", response_model=ApiResponse)
|
|
@safe
|
|
async def cot_report(symbol: str = Query(..., min_length=1, max_length=20)):
|
|
"""Commitment of Traders: commercial/speculator positions for futures."""
|
|
symbol = validate_symbol(symbol)
|
|
data = await regulators_service.get_cot(symbol)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/cot/search", response_model=ApiResponse)
|
|
@safe
|
|
async def cot_search(query: str = Query(..., min_length=1, max_length=100)):
|
|
"""Search COT report symbols."""
|
|
data = await regulators_service.cot_search(query)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/sec/litigation", response_model=ApiResponse)
|
|
@safe
|
|
async def sec_litigation():
|
|
"""SEC litigation releases RSS feed."""
|
|
data = await regulators_service.get_sec_litigation()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/sec/institutions", response_model=ApiResponse)
|
|
@safe
|
|
async def sec_institutions(query: str = Query(..., min_length=1, max_length=100)):
|
|
"""Search institutional investors filing with SEC."""
|
|
data = await regulators_service.search_institutions(query)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/sec/cik-map/{symbol}", response_model=ApiResponse)
|
|
@safe
|
|
async def sec_cik_map(symbol: str = Path(..., min_length=1, max_length=20)):
|
|
"""Map ticker symbol to SEC CIK number."""
|
|
symbol = validate_symbol(symbol)
|
|
data = await regulators_service.get_cik_map(symbol)
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
# --- Congress Trading ---
|
|
|
|
|
|
@router.get("/congress/trades", response_model=ApiResponse)
|
|
@safe
|
|
async def congress_trades():
|
|
"""Recent US congress member stock trades."""
|
|
data = await congress_service.get_congress_trades()
|
|
return ApiResponse(data=data)
|
|
|
|
|
|
@router.get("/congress/bills", response_model=ApiResponse)
|
|
@safe
|
|
async def congress_bills(query: str = Query(..., min_length=1, max_length=200)):
|
|
"""Search US congress bills by keyword."""
|
|
data = await congress_service.search_congress_bills(query)
|
|
return ApiResponse(data=data)
|