"""Routes for technical analysis indicators.""" import functools import logging from collections.abc import Awaitable, Callable from typing import ParamSpec, TypeVar from fastapi import APIRouter, HTTPException, Path from models import SYMBOL_PATTERN, ApiResponse import technical_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1") P = ParamSpec("P") R = TypeVar("R") def _validate_symbol(symbol: str) -> str: if not SYMBOL_PATTERN.match(symbol): raise HTTPException(status_code=400, detail="Invalid symbol format") return symbol.upper() def _safe(fn: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[R]]: @functools.wraps(fn) async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: try: return await fn(*args, **kwargs) except HTTPException: raise except Exception: logger.exception("Upstream data error") raise HTTPException( status_code=502, detail="Data provider error. Check server logs.", ) return wrapper # type: ignore[return-value] @router.get("/stock/{symbol}/technical", response_model=ApiResponse) @_safe async def stock_technical(symbol: str = Path(..., min_length=1, max_length=20)): """Get technical indicators: RSI, MACD, SMA, EMA, Bollinger Bands + signal interpretation.""" symbol = _validate_symbol(symbol) data = await technical_service.get_technical_indicators(symbol) return ApiResponse(data=data)