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.

177 lines
5.1 KiB

# -*- coding: utf-8 -*-
"""
===================================
股票数据接口
===================================
职责
1. 提供 GET /api/v1/stocks/{code}/quote 实时行情接口
2. 提供 GET /api/v1/stocks/{code}/history 历史行情接口
"""
import logging
from fastapi import APIRouter, HTTPException, Query
from api.v1.schemas.stocks import (
StockQuote,
StockHistoryResponse,
KLineData,
)
from api.v1.schemas.common import ErrorResponse
from src.services.stock_service import StockService
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get(
"/{stock_code}/quote",
response_model=StockQuote,
responses={
200: {"description": "行情数据"},
404: {"description": "股票不存在", "model": ErrorResponse},
500: {"description": "服务器错误", "model": ErrorResponse},
},
summary="获取股票实时行情",
description="获取指定股票的最新行情数据"
)
def get_stock_quote(stock_code: str) -> StockQuote:
"""
获取股票实时行情
获取指定股票的最新行情数据
Args:
stock_code: 股票代码 60051900700AAPL
Returns:
StockQuote: 实时行情数据
Raises:
HTTPException: 404 - 股票不存在
"""
try:
service = StockService()
# 使用 def 而非 async defFastAPI 自动在线程池中执行
result = service.get_realtime_quote(stock_code)
if result is None:
raise HTTPException(
status_code=404,
detail={
"error": "not_found",
"message": f"未找到股票 {stock_code} 的行情数据"
}
)
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"获取实时行情失败: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail={
"error": "internal_error",
"message": f"获取实时行情失败: {str(e)}"
}
)
@router.get(
"/{stock_code}/history",
response_model=StockHistoryResponse,
responses={
200: {"description": "历史行情数据"},
422: {"description": "不支持的周期参数", "model": ErrorResponse},
500: {"description": "服务器错误", "model": ErrorResponse},
},
summary="获取股票历史行情",
description="获取指定股票的历史 K 线数据"
)
def get_stock_history(
stock_code: str,
period: str = Query("daily", description="K 线周期", pattern="^(daily|weekly|monthly)$"),
days: int = Query(30, ge=1, le=365, description="获取天数")
) -> StockHistoryResponse:
"""
获取股票历史行情
获取指定股票的历史 K 线数据
Args:
stock_code: 股票代码
period: K 线周期 (daily/weekly/monthly)
days: 获取天数
Returns:
StockHistoryResponse: 历史行情数据
"""
try:
service = StockService()
# 使用 def 而非 async defFastAPI 自动在线程池中执行
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:
# period 参数不支持的错误(如 weekly/monthly
raise HTTPException(
status_code=422,
detail={
"error": "unsupported_period",
"message": str(e)
}
)
except Exception as e:
logger.error(f"获取历史行情失败: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail={
"error": "internal_error",
"message": f"获取历史行情失败: {str(e)}"
}
)