|
|
|
|
|
"""
|
|
|
|
|
|
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)}"
|
|
|
|
|
|
)
|