feat(job): D2 全市场选股 Job 化(kind=selection 异步)
- Job kind=selection:executor 分支用 SelectionService(spec=SelectionQuery),结果 落 result_json、自动归档 Experiment(summary:as_of + 选出 N);/api/jobs 与 /api/experiments 的 result 解码支持 SelectionResult - POST /api/selections/jobs:提交 SelectionQuery 为异步 Job(BackgroundTasks/subprocess) - Web 选股页新增「异步(全市场)」按钮:提交 Job → waitJob 轮询结果(解决同步 60s+) - tests/test_selection_job.py:executor 执行归档(SelectionResult/Experiment kind)、 API 提交→轮询→结果与实验列表;全量 pytest + tsc 通过
This commit is contained in:
@@ -61,12 +61,16 @@ def _git_short_rev() -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _summary_text(kind: str, result: BacktestResult | FactorTestReport) -> str | None:
|
||||
def _summary_text(kind: str, result) -> str | None:
|
||||
from app.domain.entities.selection import SelectionResult
|
||||
|
||||
if kind == "backtest" and isinstance(result, BacktestResult):
|
||||
s = result.summary
|
||||
return f"总收益 {s.total_return_pct:.2f}% · 年化 {s.annual_return_pct:.2f}% · 回撤 {s.max_drawdown_pct:.2f}%"
|
||||
if isinstance(result, FactorTestReport):
|
||||
return f"IC {result.ic_mean:.4f} · RankIC {result.rank_ic_mean:.4f} · 样本 {result.sample_days} 日"
|
||||
if kind == "selection" and isinstance(result, SelectionResult):
|
||||
return f"as_of {result.as_of_date} · 选出 {result.statistics.selected} / 评估 {result.statistics.evaluated}"
|
||||
return None
|
||||
|
||||
|
||||
@@ -91,10 +95,20 @@ def _execute_inner(
|
||||
job_repo.update(job)
|
||||
session.commit()
|
||||
try:
|
||||
spec = ResearchSpec.model_validate_json(job.spec_json)
|
||||
service = ResearchService(
|
||||
stock_repo_factory(session), daily_repo_factory(session), engine
|
||||
)
|
||||
is_selection = job.kind == "selection"
|
||||
if is_selection:
|
||||
from app.application.services.selection_service import SelectionService
|
||||
from app.domain.entities.selection import SelectionQuery
|
||||
|
||||
spec = SelectionQuery.model_validate_json(job.spec_json)
|
||||
service = SelectionService(
|
||||
stock_repo_factory(session), daily_repo_factory(session)
|
||||
)
|
||||
else:
|
||||
spec = ResearchSpec.model_validate_json(job.spec_json)
|
||||
service = ResearchService(
|
||||
stock_repo_factory(session), daily_repo_factory(session), engine
|
||||
)
|
||||
|
||||
def _set_stage(name: str) -> None:
|
||||
"""阶段上报(v3 §23):独立短会话写 job.stage 并 commit(子进程同样走 DB)。"""
|
||||
@@ -108,7 +122,10 @@ def _execute_inner(
|
||||
except Exception: # noqa: BLE001 —— 阶段上报失败不阻断执行
|
||||
pass
|
||||
|
||||
if spec.type == "backtest":
|
||||
if is_selection:
|
||||
_set_stage("selection")
|
||||
result = service.select(spec)
|
||||
elif spec.type == "backtest":
|
||||
result = service.run_backtest(spec, on_stage=_set_stage)
|
||||
else:
|
||||
result = service.run_factor_test(spec, on_stage=_set_stage)
|
||||
@@ -116,10 +133,10 @@ def _execute_inner(
|
||||
result_json = json.dumps(result.model_dump(mode="json"), ensure_ascii=False)
|
||||
experiment = ExperimentRecord(
|
||||
id=new_id("EXP"),
|
||||
kind=spec.type,
|
||||
kind=job.kind,
|
||||
spec_json=job.spec_json,
|
||||
result_json=result_json,
|
||||
summary_text=_summary_text(spec.type, result),
|
||||
summary_text=_summary_text(job.kind, result),
|
||||
code_version=_git_short_rev(),
|
||||
job_id=job.id,
|
||||
created_at=datetime.now(),
|
||||
|
||||
Reference in New Issue
Block a user