""" K 线数据 API 路由 """ from datetime import datetime from typing import Annotated, List from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.schemas import KlineRequest, KlineResponse, KlineDataItem, ResponseData from app.services.kline_service import KlineService from app.db.init_db import get_sqlite_db router = APIRouter() @router.get("/data", response_model=KlineResponse) async def get_kline_data( symbol: Annotated[str, Query(description="品种代码,如 IF2406")], period: Annotated[str, Query(description="周期,如 1m, 5m, 1h, 1d")], start: Annotated[datetime, Query(description="开始时间")], end: Annotated[datetime, Query(description="结束时间")], page: Annotated[int, Query(description="页码,默认 1")] = 1, page_size: Annotated[int, Query(description="每页数量,默认 1000,最大 5000")] = 1000, ): """ 获取 K 线数据 - **symbol**: 品种代码 (如 IF2406, SH0001) - **period**: 周期 (1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w) - **start**: 开始时间 (ISO 8601 格式) - **end**: 结束时间 (ISO 8601 格式) - **page**: 页码 (默认 1) - **page_size**: 每页数量 (默认 1000,最大 5000) """ # 验证时间范围 if start >= end: raise HTTPException( status_code=400, detail="开始时间必须早于结束时间" ) # 验证开始时间不能晚于当前时间(允许 1 分钟误差) from datetime import timedelta if start > datetime.utcnow() + timedelta(minutes=1): raise HTTPException( status_code=400, detail="开始时间不能晚于当前时间" ) # 限制 page_size 最大值 if page_size > 5000: page_size = 5000 if page_size < 1: page_size = 1 if page < 1: page = 1 try: data = KlineService.get_kline_data(symbol, period, start, end, page, page_size) return KlineResponse( code=0, message="success", data=[KlineDataItem(**item) for item in data], symbol=symbol, period=period ) except HTTPException: raise except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to fetch kline data: {str(e)}" ) @router.get("/latest", response_model=ResponseData) async def get_latest_kline( symbol: Annotated[str, Query(description="品种代码")], period: Annotated[str, Query(description="周期")], ): """获取最新一条 K 线数据""" data = KlineService.get_latest_kline(symbol, period) if not data: return ResponseData( code=404, message="No data found", data=None ) return ResponseData( code=0, message="success", data=data ) @router.get("/symbols", response_model=ResponseData) async def get_symbols(): """获取所有品种代码列表""" symbols = KlineService.get_symbols() return ResponseData( code=0, message="success", data={"symbols": symbols, "count": len(symbols)} ) @router.get("/periods", response_model=ResponseData) async def get_periods(): """获取所有支持的周期""" periods = KlineService.get_periods() return ResponseData( code=0, message="success", data={"periods": periods, "count": len(periods)} ) @router.post("/data/batch", response_model=ResponseData) async def batch_insert_kline( symbol: Annotated[str, Query(description="品种代码")], period: Annotated[str, Query(description="周期")], kline_data: Annotated[List[KlineDataItem], Query(description="K 线数据列表")], ): """ 批量插入 K 线数据 (管理接口) 注意:此接口需要管理员权限 """ try: data_list = [ { "time": item.time, "open": item.open, "high": item.high, "low": item.low, "close": item.close, "volume": item.volume, "amount": item.amount or 0, "open_interest": item.open_interest or 0 } for item in kline_data ] count = KlineService.insert_kline_data(symbol, period, data_list) return ResponseData( code=0, message="success", data={"inserted": count} ) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to insert kline data: {str(e)}" )