feat(job): D1 Job 阶段上报(v3 §23)+ 列表/取消端点

- ResearchService.run_backtest/run_factor_test 支持 on_stage 回调(data_loading →
  factor_calculation/backtesting → analysis);job_executor 经独立短会话把 stage 写库
  (子进程模式同样走 DB),收尾回读最后阶段避免覆盖
- /api/jobs:GET 列表(kind/limit)、POST /{id}/cancel(queued/running → CANCELLED +
  终止子进程 terminate_active;父进程兜底已跳过 CANCELLED)
- SSE /jobs/{id}/events 现会携带 stage
- tests/test_job_stages.py:成功 job 终态 stage=analysis;executor 取消 queued/幂等/
  已完成不可取消;API 列表+终态不可取消+404;全量 pytest 通过
This commit is contained in:
Simon
2026-09-09 07:37:25 +08:00
parent 67d3aa1349
commit 7c268e43df
4 changed files with 271 additions and 8 deletions
+21 -4
View File
@@ -120,17 +120,29 @@ class ResearchService:
self._engine = engine
self._index_repo = index_repo
def run_factor_test(self, spec: ResearchSpec, horizon_days: int = 21) -> FactorTestReport:
def run_factor_test(
self, spec: ResearchSpec, horizon_days: int = 21, on_stage=None
) -> FactorTestReport:
"""on_stage(str):执行阶段回调(data_loading / factor_calculation / analysis),
供 Job 状态机上报 stage(v3 §23)。"""
if spec.type != "factor_test":
raise ValueError("factor_test 用例需要 spec.type=factor_test")
_stage(on_stage, "data_loading")
daily = self._load_daily(spec)
return self._engine.run_factor_test(daily, spec, horizon_days=horizon_days)
_stage(on_stage, "factor_calculation")
report = self._engine.run_factor_test(daily, spec, horizon_days=horizon_days)
_stage(on_stage, "analysis")
return report
def run_backtest(self, spec: ResearchSpec) -> BacktestResult:
def run_backtest(self, spec: ResearchSpec, on_stage=None) -> BacktestResult:
if spec.type != "backtest":
raise ValueError("backtest 用例需要 spec.type=backtest")
_stage(on_stage, "data_loading")
daily = self._load_daily(spec)
return self._engine.run_backtest(daily, spec)
_stage(on_stage, "backtesting")
result = self._engine.run_backtest(daily, spec)
_stage(on_stage, "analysis")
return result
def run_factor_correlation(self, spec: ResearchSpec) -> FactorCorrelationReport:
"""多因子两两相关(v3 §12):同 universe/period 装配 → 横截面相关矩阵。"""
@@ -158,3 +170,8 @@ class ResearchService:
sorted(required),
adjust=spec.price_adjustment,
)
def _stage(cb, name: str) -> None:
if cb is not None:
cb(name)