85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
"""回测路由。"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
|
|
from src.api.deps import require_admin
|
|
from src.llm.backtest import run_backtest
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/v1", tags=["backtest"])
|
|
|
|
|
|
class BacktestRequest(BaseModel):
|
|
league_id: int | None = Field(None, description="联赛 ID")
|
|
date_from: str | None = Field(None, description="起始日期 YYYY-MM-DD")
|
|
date_to: str | None = Field(None, description="结束日期 YYYY-MM-DD")
|
|
mode: str = Field("single", description="预测模式: single(快) / multi(多 agent)")
|
|
limit: int = Field(20, ge=1, le=200, description="最大回测场数")
|
|
model: str | None = Field(None, description="指定模型 (空=默认)")
|
|
|
|
|
|
@router.post("/backtest", dependencies=[Depends(require_admin)])
|
|
async def backtest(req: BacktestRequest):
|
|
"""对历史比赛运行回测。
|
|
|
|
该接口会对每场已完赛比赛各发起一次 LLM 预测,成本高 —— 因此需要
|
|
管理员鉴权(require_admin)。
|
|
|
|
对每场已完赛比赛:
|
|
1. 用比赛之前的数据构建上下文 (防未来信息泄漏,cutoff=match_date-1天)
|
|
2. 调 LLM 预测(强制 use_cache=False,避免缓存命中导致反复 settle 同一行)
|
|
3. 用实际比分回填(settle)
|
|
4. 统计准确率 / RMSE / 校准度
|
|
|
|
限流:单请求上限 200 场(默认 20),避免一次打爆 LLM 额度。
|
|
回测写入 run_type='backtest',与实盘(live)互不覆盖(唯一键含 run_type)。
|
|
"""
|
|
try:
|
|
summary = await run_backtest(
|
|
league_id=req.league_id,
|
|
date_from=req.date_from,
|
|
date_to=req.date_to,
|
|
mode=req.mode,
|
|
limit=req.limit,
|
|
model=req.model,
|
|
)
|
|
except Exception as e:
|
|
logger.exception("backtest failed")
|
|
raise HTTPException(500, "回测执行失败,请查看服务器日志")
|
|
|
|
return {
|
|
"summary": {
|
|
"total": summary.total,
|
|
"scored": summary.scored,
|
|
"success": summary.success,
|
|
"degraded": summary.degraded,
|
|
"accuracy_1x2": summary.accuracy_1x2,
|
|
"avg_score_rmse": summary.avg_score_rmse,
|
|
"avg_subjective_confidence": summary.avg_subjective_confidence,
|
|
},
|
|
"results": [
|
|
{
|
|
"match_id": r.match_id,
|
|
"league_code": r.league_code,
|
|
"home_team": r.home_team,
|
|
"away_team": r.away_team,
|
|
"home_team_zh": r.home_team_zh,
|
|
"away_team_zh": r.away_team_zh,
|
|
"match_date": r.match_date,
|
|
"actual_score": f"{r.actual_home}-{r.actual_away}",
|
|
"actual_1x2": r.actual_1x2,
|
|
"pred_home": r.pred_home,
|
|
"pred_away": r.pred_away,
|
|
"pred_1x2": r.pred_1x2,
|
|
"subjective_confidence": r.subjective_confidence,
|
|
"correct_1x2": r.correct_1x2,
|
|
}
|
|
for r in summary.results
|
|
],
|
|
}
|