You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
9.0 KiB

# -*- 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
)