# -*- coding: utf-8 -*- """ =================================== Stock data endpoints =================================== Responsibilities: 1. Provide stock quote endpoint 2. Provide stock history endpoint 3. Provide stock momentum recommendations 4. Provide new high/low stocks 5. Provide stock K-line chart data """ import logging from typing import List from fastapi import APIRouter, HTTPException, Query, Depends from api.v1.schemas.stocks import ( StockQuote, StockHistoryResponse, KLineData, ) from api.v1.schemas.market import ( StockMomentum, StockMomentumResponse, NewHighLowStock, KLineChartResponse, ) from api.v1.schemas.common import ErrorResponse from src.services.stock_service import StockService from src.services.market_service import MarketService logger = logging.getLogger(__name__) router = APIRouter() def get_stock_service() -> StockService: """Dependency injection for StockService""" return StockService() def get_market_service() -> MarketService: """Dependency injection for MarketService""" return MarketService() @router.get( "/momentum-recommendation", response_model=StockMomentumResponse, responses={ 200: {"description": "Stock momentum recommendations"}, 500: {"description": "Server error", "model": ErrorResponse}, }, summary="Get stock momentum recommendations", description="Get stock momentum recommendations based on technical indicators" ) def get_momentum_recommendation( limit: int = Query(15, ge=1, le=50, description="Limit count"), service: MarketService = Depends(get_market_service) ) -> StockMomentumResponse: """ Get stock momentum recommendations Returns stocks with strong momentum signals """ try: stocks = service.get_stock_momentum_recommendation(limit=limit) return StockMomentumResponse( items=stocks, total=len(stocks) ) except Exception as e: logger.error(f"Failed to get momentum recommendation: {e}", exc_info=True) return StockMomentumResponse(items=[], total=0) @router.get( "/new-high", response_model=List[NewHighLowStock], responses={ 200: {"description": "New high stocks"}, 500: {"description": "Server error", "model": ErrorResponse}, }, summary="Get new high stocks", description="Get stocks that reached new high in recent days" ) def get_new_high( days: int = Query(20, ge=1, le=60, description="Days to look back"), limit: int = Query(20, ge=1, le=50, description="Limit count"), service: MarketService = Depends(get_market_service) ) -> List[NewHighLowStock]: """ Get new high stocks Returns stocks that reached new high within specified days """ try: stocks = service.get_new_high_stocks(days=days, limit=limit) return stocks except Exception as e: logger.error(f"Failed to get new high stocks: {e}", exc_info=True) return [] @router.get( "/new-low", response_model=List[NewHighLowStock], responses={ 200: {"description": "New low stocks"}, 500: {"description": "Server error", "model": ErrorResponse}, }, summary="Get new low stocks", description="Get stocks that reached new low in recent days" ) def get_new_low( days: int = Query(20, ge=1, le=60, description="Days to look back"), limit: int = Query(20, ge=1, le=50, description="Limit count"), service: MarketService = Depends(get_market_service) ) -> List[NewHighLowStock]: """ Get new low stocks Returns stocks that reached new low within specified days """ try: stocks = service.get_new_low_stocks(days=days, limit=limit) return stocks except Exception as e: logger.error(f"Failed to get new low stocks: {e}", exc_info=True) return [] @router.get( "/{stock_code}/quote", response_model=StockQuote, responses={ 200: {"description": "Stock quote data"}, 404: {"description": "Stock not found", "model": ErrorResponse}, 500: {"description": "Server error", "model": ErrorResponse}, }, summary="Get stock quote", description="Get real-time stock quote" ) def get_stock_quote( stock_code: str, service: StockService = Depends(get_stock_service) ) -> StockQuote: """ Get stock real-time quote Args: stock_code: Stock code (e.g., 600519, 00700, AAPL) Returns: StockQuote: Real-time quote data """ try: result = service.get_realtime_quote(stock_code) if result is None: raise HTTPException( status_code=404, detail={ "error": "not_found", "message": f"Stock {stock_code} not found" } ) return StockQuote( stock_code=result.get("stock_code", stock_code), stock_name=result.get("stock_name"), current_price=result.get("current_price", 0.0), change=result.get("change"), change_percent=result.get("change_percent"), open=result.get("open"), high=result.get("high"), low=result.get("low"), prev_close=result.get("prev_close"), volume=result.get("volume"), amount=result.get("amount"), update_time=result.get("update_time") ) except HTTPException: raise except Exception as e: logger.error(f"Failed to get stock quote: {e}", exc_info=True) raise HTTPException( status_code=500, detail={ "error": "internal_error", "message": f"Failed to get stock quote: {str(e)}" } ) @router.get( "/{stock_code}/history", response_model=StockHistoryResponse, responses={ 200: {"description": "Stock history data"}, 422: {"description": "Unsupported period", "model": ErrorResponse}, 500: {"description": "Server error", "model": ErrorResponse}, }, summary="Get stock history", description="Get stock historical K-line data" ) def get_stock_history( stock_code: str, period: str = Query("daily", description="K-line period", pattern="^(daily|weekly|monthly)$"), days: int = Query(30, ge=1, le=365, description="Number of days"), service: StockService = Depends(get_stock_service) ) -> StockHistoryResponse: """ Get stock historical data Args: stock_code: Stock code period: K-line period (daily/weekly/monthly) days: Number of days Returns: StockHistoryResponse: Historical data """ try: result = service.get_history_data( stock_code=stock_code, period=period, days=days ) data = [ KLineData( date=item.get("date"), open=item.get("open"), high=item.get("high"), low=item.get("low"), close=item.get("close"), volume=item.get("volume"), amount=item.get("amount"), change_percent=item.get("change_percent") ) for item in result.get("data", []) ] return StockHistoryResponse( stock_code=stock_code, stock_name=result.get("stock_name"), period=period, data=data ) except ValueError as e: raise HTTPException( status_code=422, detail={ "error": "unsupported_period", "message": str(e) } ) except Exception as e: logger.error(f"Failed to get stock history: {e}", exc_info=True) raise HTTPException( status_code=500, detail={ "error": "internal_error", "message": f"Failed to get stock history: {str(e)}" } ) @router.get( "/{stock_code}/kline", response_model=KLineChartResponse, responses={ 200: {"description": "K-line chart data"}, 404: {"description": "Stock not found", "model": ErrorResponse}, 500: {"description": "Server error", "model": ErrorResponse}, }, summary="Get stock K-line chart", description="Get stock K-line chart data formatted for ECharts" ) def get_stock_kline( stock_code: str, days: int = Query(60, ge=1, le=365, description="Number of days"), service: MarketService = Depends(get_market_service) ) -> KLineChartResponse: """ Get stock K-line chart data Returns K-line data formatted for ECharts visualization """ try: kline = service.get_stock_kline_chart(stock_code, days=days) return kline except Exception as e: logger.error(f"Failed to get stock kline: {e}", exc_info=True) return KLineChartResponse( categoryData=[], values=[], volumes=[], stock_name=None )