"""Routes for A-share (China) and Hong Kong stock market data via AKShare.""" from fastapi import APIRouter, HTTPException, Path, Query from models import ApiResponse from route_utils import safe import akshare_service router = APIRouter(prefix="/api/v1/cn", tags=["China & HK Markets"]) # --- Validation helpers --- def _validate_a_share(symbol: str) -> str: """Validate A-share symbol format; raise 400 on failure.""" if not akshare_service.validate_a_share_symbol(symbol): raise HTTPException( status_code=400, detail=( f"Invalid A-share symbol '{symbol}'. " "Must be 6 digits starting with 0, 3, or 6 (e.g. 000001, 300001, 600519)." ), ) return symbol def _validate_hk(symbol: str) -> str: """Validate HK stock symbol format; raise 400 on failure.""" if not akshare_service.validate_hk_symbol(symbol): raise HTTPException( status_code=400, detail=( f"Invalid HK symbol '{symbol}'. " "Must be exactly 5 digits (e.g. 00700, 09988)." ), ) return symbol # --- A-share routes --- # NOTE: /a-share/search MUST be registered before /a-share/{symbol} to avoid shadowing. @router.get("/a-share/search", response_model=ApiResponse) @safe async def a_share_search( query: str = Query(..., description="Stock name to search for (partial match)"), ) -> ApiResponse: """Search A-share stocks by name (partial match).""" data = await akshare_service.search_a_shares(query) return ApiResponse(data=data) @router.get("/a-share/{symbol}/quote", response_model=ApiResponse) @safe async def a_share_quote( symbol: str = Path(..., min_length=6, max_length=6), ) -> ApiResponse: """Get real-time A-share quote (沪深 real-time price).""" symbol = _validate_a_share(symbol) data = await akshare_service.get_a_share_quote(symbol) if data is None: raise HTTPException(status_code=404, detail=f"A-share symbol '{symbol}' not found.") return ApiResponse(data=data) @router.get("/a-share/{symbol}/historical", response_model=ApiResponse) @safe async def a_share_historical( symbol: str = Path(..., min_length=6, max_length=6), days: int = Query(default=365, ge=1, le=3650), ) -> ApiResponse: """Get A-share daily OHLCV history with qfq (前复权) adjustment.""" symbol = _validate_a_share(symbol) data = await akshare_service.get_a_share_historical(symbol, days=days) return ApiResponse(data=data) # --- HK stock routes --- @router.get("/hk/{symbol}/quote", response_model=ApiResponse) @safe async def hk_quote( symbol: str = Path(..., min_length=5, max_length=5), ) -> ApiResponse: """Get real-time HK stock quote (港股 real-time price).""" symbol = _validate_hk(symbol) data = await akshare_service.get_hk_quote(symbol) if data is None: raise HTTPException(status_code=404, detail=f"HK symbol '{symbol}' not found.") return ApiResponse(data=data) @router.get("/hk/{symbol}/historical", response_model=ApiResponse) @safe async def hk_historical( symbol: str = Path(..., min_length=5, max_length=5), days: int = Query(default=365, ge=1, le=3650), ) -> ApiResponse: """Get HK stock daily OHLCV history with qfq adjustment.""" symbol = _validate_hk(symbol) data = await akshare_service.get_hk_historical(symbol, days=days) return ApiResponse(data=data)