feat: 添加回测框架

- 新增 src/llm/backtest.py: 回测核心逻辑
  - 查询历史已完赛比赛
  - 逐场预测(自动防未来信息泄漏)
  - 实际比分回填 + 统计
  - 1X2 准确率 / 比分 RMSE / 置信度校准
- 新增 POST /api/v1/backtest 路由
- 支持按联赛/日期范围/模式/模型筛选
This commit is contained in:
shangfangjian
2026-09-09 21:39:56 +08:00
parent 9c24b5f744
commit 1166a157ec
3 changed files with 275 additions and 0 deletions
+2
View File
@@ -41,11 +41,13 @@ def create_app() -> FastAPI:
from src.api.routes.predict import router as predict_router
from src.api.routes.ingest import router as ingest_router
from src.api.routes.eval import router as eval_router
from src.api.routes.backtest import router as backtest_router
app.include_router(matches_router)
app.include_router(predict_router)
app.include_router(ingest_router)
app.include_router(eval_router)
app.include_router(backtest_router)
@app.get("/health")
async def health():
+69
View File
@@ -0,0 +1,69 @@
"""回测路由。"""
from __future__ import annotations
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from src.llm.backtest import run_backtest
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")
async def backtest(req: BacktestRequest):
"""对历史比赛运行回测。
对每场已完赛比赛:
1. 用比赛之前的数据构建上下文 (防未来信息泄漏)
2. 调 LLM 预测
3. 用实际比分回填
4. 统计准确率 / RMSE / 校准度
"""
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:
raise HTTPException(500, f"backtest failed: {e}")
return {
"summary": {
"total": summary.total,
"scored": summary.scored,
"accuracy_1x2": summary.accuracy_1x2,
"avg_score_rmse": summary.avg_score_rmse,
"avg_confidence": summary.avg_confidence,
"calibration": summary.calibration,
},
"results": [
{
"match_id": r.match_id,
"league_code": r.league_code,
"home_team": r.home_team,
"away_team": r.away_team,
"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,
"confidence": r.confidence,
"correct_1x2": r.correct_1x2,
}
for r in summary.results
],
}