"""Routes for ETF, index, crypto, currency, and derivatives data.""" from fastapi import APIRouter, Path, Query from models import ApiResponse from route_utils import safe, validate_symbol import market_service router = APIRouter(prefix="/api/v1") # --- ETF --- # NOTE: /etf/search MUST be registered before /etf/{symbol} to avoid shadowing. @router.get("/etf/search", response_model=ApiResponse) @safe async def etf_search(query: str = Query(..., min_length=1, max_length=100)): """Search for ETFs by name or keyword.""" data = await market_service.search_etf(query) return ApiResponse(data=data) @router.get("/etf/{symbol}/info", response_model=ApiResponse) @safe async def etf_info(symbol: str = Path(..., min_length=1, max_length=20)): """Get ETF profile and info.""" symbol = validate_symbol(symbol) data = await market_service.get_etf_info(symbol) return ApiResponse(data=data) @router.get("/etf/{symbol}/historical", response_model=ApiResponse) @safe async def etf_historical( symbol: str = Path(..., min_length=1, max_length=20), days: int = Query(default=365, ge=1, le=3650), ): """Get ETF price history.""" symbol = validate_symbol(symbol) data = await market_service.get_etf_historical(symbol, days=days) return ApiResponse(data=data) # --- Index --- @router.get("/index/available", response_model=ApiResponse) @safe async def index_available(): """List available market indices.""" data = await market_service.get_available_indices() return ApiResponse(data=data) @router.get("/index/{symbol}/historical", response_model=ApiResponse) @safe async def index_historical( symbol: str = Path(..., min_length=1, max_length=20), days: int = Query(default=365, ge=1, le=3650), ): """Get index price history (e.g., ^GSPC, ^DJI, ^IXIC).""" symbol = validate_symbol(symbol) data = await market_service.get_index_historical(symbol, days=days) return ApiResponse(data=data) # --- Crypto --- # NOTE: /crypto/search MUST be registered before /crypto/{symbol} to avoid shadowing. @router.get("/crypto/search", response_model=ApiResponse) @safe async def crypto_search(query: str = Query(..., min_length=1, max_length=100)): """Search for cryptocurrencies.""" data = await market_service.search_crypto(query) return ApiResponse(data=data) @router.get("/crypto/{symbol}/historical", response_model=ApiResponse) @safe async def crypto_historical( symbol: str = Path(..., min_length=1, max_length=20), days: int = Query(default=365, ge=1, le=3650), ): """Get cryptocurrency price history (e.g., BTC-USD).""" symbol = validate_symbol(symbol) data = await market_service.get_crypto_historical(symbol, days=days) return ApiResponse(data=data) # --- Currency --- @router.get("/currency/{symbol}/historical", response_model=ApiResponse) @safe async def currency_historical( symbol: str = Path(..., min_length=1, max_length=20), days: int = Query(default=365, ge=1, le=3650), ): """Get forex price history (e.g., EURUSD, USDSEK).""" symbol = validate_symbol(symbol) data = await market_service.get_currency_historical(symbol, days=days) return ApiResponse(data=data) # --- Derivatives --- @router.get("/options/{symbol}/chains", response_model=ApiResponse) @safe async def options_chains(symbol: str = Path(..., min_length=1, max_length=20)): """Get options chain data.""" symbol = validate_symbol(symbol) data = await market_service.get_options_chains(symbol) return ApiResponse(data=data) @router.get("/futures/{symbol}/historical", response_model=ApiResponse) @safe async def futures_historical( symbol: str = Path(..., min_length=1, max_length=20), days: int = Query(default=365, ge=1, le=3650), ): """Get futures price history.""" symbol = validate_symbol(symbol) data = await market_service.get_futures_historical(symbol, days=days) return ApiResponse(data=data) @router.get("/futures/{symbol}/curve", response_model=ApiResponse) @safe async def futures_curve(symbol: str = Path(..., min_length=1, max_length=20)): """Get futures term structure/curve.""" symbol = validate_symbol(symbol) data = await market_service.get_futures_curve(symbol) return ApiResponse(data=data) # --- Currency Reference Rates (Group H) --- @router.get("/currency/reference-rates", response_model=ApiResponse) @safe async def currency_reference_rates(): """Get ECB reference exchange rates for 28 major currencies.""" data = await market_service.get_currency_reference_rates() return ApiResponse(data=data) # --- Index Enhanced (Group F) --- @router.get("/index/sp500-multiples", response_model=ApiResponse) @safe async def sp500_multiples( series: str = Query(default="pe_ratio", pattern="^[a-z_]+$"), ): """Historical S&P 500 valuation: pe_ratio, shiller_pe_ratio, dividend_yield, etc.""" data = await market_service.get_sp500_multiples(series) return ApiResponse(data=data) @router.get("/index/{symbol}/constituents", response_model=ApiResponse) @safe async def index_constituents(symbol: str = Path(..., min_length=1, max_length=20)): """Get index member stocks with sector and price data.""" symbol = validate_symbol(symbol) data = await market_service.get_index_constituents(symbol) return ApiResponse(data=data) @router.get("/etf/{symbol}/nport", response_model=ApiResponse) @safe async def etf_nport(symbol: str = Path(..., min_length=1, max_length=20)): """Detailed ETF holdings from SEC N-PORT filings.""" symbol = validate_symbol(symbol) data = await market_service.get_etf_nport(symbol) return ApiResponse(data=data)