- domain/entities/replay.py:ReplayDay{top/events/counts}/ReplayResult 时间线
- ReplayService:universe.symbols 白名单必填(≤40)且重放交易日 ≤90(防全市场长任务);
每个交易日以 <=as_of 数据经同一 signal/score 引擎生成帧
- POST /api/replays(边界校验)→ 时间线;供前端 Bar Replay 控件(v3 §20.6 阶段二)
- tests/test_replays.py:重放帧 == 回测 selection_history 逐调仓日一致;范围约束;
后段暴涨股不泄漏进早段帧(未来函数);API 400/200;全量 pytest 通过
34 lines
1.1 KiB
Python
34 lines
1.1 KiB
Python
"""Bar Replay API(M9-6):POST /api/replays —— 线性重放选股/信号时间线。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import date
|
||
|
||
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel, Field
|
||
|
||
from app.api.deps import ReplayServiceDep
|
||
from app.domain.entities.replay import ReplayResult
|
||
from app.domain.entities.selection import SelectionQuery
|
||
from app.domain.entities.signal import SignalRules
|
||
|
||
router = APIRouter(prefix="/replays", tags=["replays"])
|
||
|
||
|
||
class ReplayRequest(BaseModel):
|
||
query: SelectionQuery
|
||
rules: SignalRules = SignalRules()
|
||
start: date
|
||
end: date
|
||
top_n: int = Field(default=5, ge=1, le=20)
|
||
|
||
|
||
@router.post("", response_model=ReplayResult, summary="线性重放(as_of 逐日,仅用当时数据)")
|
||
def run_replay(req: ReplayRequest, service: ReplayServiceDep) -> ReplayResult:
|
||
if req.start >= req.end:
|
||
raise HTTPException(status_code=400, detail="start 必须早于 end")
|
||
try:
|
||
return service.replay(req.query, req.rules, req.start, req.end, req.top_n)
|
||
except ValueError as exc:
|
||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|