程序员量化交易实战 44:执行闸门
程序员量化交易实战 44:执行闸门
古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。这个专栏会把一个 A 股量化平台从 0 到 1 拆开写:数据、策略、回测、模拟盘、提醒和生产化,尽量用真实代码和真实运行结果说话。更完整的更新也会同步到微信公众号「字与码」。
第 43 篇把每日运行包装成命令响应,但它还没有解决一个更敏感的问题:什么时候允许真实执行。
在模拟盘系统里,dry_run_ready 很容易被误解成“ready”。如果命令层只看 exit code,dry run 可能被错误地接到真实执行流程。第 44 篇把这个判断集中成执行闸门。

执行决策
第 44 章新增 app/execution_guard.py。
@dataclass(frozen=True)
class ExecutionDecision:
allowed: bool
reason: str
required_actions: tuple[str, ...]
allowed 是机器判断,reason 和 required_actions 是给人和告警系统看的。
| plan 状态 | allowed | reason | required_actions |
|---|---|---|---|
ready | true | ready | 空 |
dry_run_ready | false | dry_run | disable_dry_run |
blocked | false | warning 或 blocker | 失败动作列表 |
判断逻辑
核心函数如下:
def decide_execution(plan: DailyRunPlan) -> ExecutionDecision:
if plan.result.status == "ready":
return ExecutionDecision(allowed=True, reason="ready", required_actions=())
if plan.result.status == "dry_run_ready":
return ExecutionDecision(allowed=False, reason="dry_run", required_actions=("disable_dry_run",))
if plan.failure_actions:
return ExecutionDecision(
allowed=False,
reason=plan.action_summary,
required_actions=tuple(action.action for action in plan.failure_actions),
)
return ExecutionDecision(allowed=False, reason="not_ready", required_actions=("manual_review",))
注意这里没有让 dry_run_ready 通过。它代表检查通过,但仍处于演练模式。
稳定日志格式
执行决策也有一行格式:
def execution_decision_line(decision: ExecutionDecision) -> str:
actions = ",".join(decision.required_actions) if decision.required_actions else "none"
return f"allowed={str(decision.allowed).lower()} reason={decision.reason} actions={actions}"
warning 场景输出:
allowed=false reason=warning actions=inspect_archive
这一行适合放在 artifact、告警或命令响应之后,告诉值班的人为什么没执行。
接到可运行示例
本章继续使用同一个命令查看执行闸门:
uv run python -m scripts.chapter_examples paper-command
本章对应的输出如下:

这次输出里,allowed=false 是最核心的判断。前面第 43 篇的命令已经用 exit_code=1 告诉调度器这次运行失败;执行闸门再明确告诉真实执行层:不要下单,不要更新模拟盘账户,不要触发后续交易动作。
reason=blocker 来自失败动作的严重级别。它和 required_actions=('repair_market_data',) 放在一起,能让人判断这是必须先修复的数据问题,而不是可以忽略的 warning。
执行闸门的价值在边界上。CLI、Web 后台、定时任务以后都可能触发每日运行,但它们不应该各自判断 status 字符串。统一经过 decide_execution(),才能避免 dry run 被误当成 ready,也能避免 blocked 状态下继续产生真实副作用。
测试
本章测试覆盖:
- ready 计划允许执行。
- dry-run-ready 计划不允许执行,并要求
disable_dry_run。 - blocked 计划返回失败动作。
- 日志行格式稳定。
运行命令:
uv run pytest tests/test_execution_guard.py tests/test_daily_run_command.py tests/test_daily_run_plan.py tests/test_failure_policy.py
本批次补充 paper-command 后,全量测试通过:
276 passed, 2 warnings
本章更新与代码仓库
本章更新内容:
- 新增
app/execution_guard.py。 - 新增
ExecutionDecision。 - 实现
decide_execution()。 - 实现
execution_decision_line()。 - 新增
tests/test_execution_guard.py,覆盖 ready、dry-run-ready、blocked 和日志格式。 - 在
scripts/chapter_examples.py中接入执行闸门输出,复现 blocked 时的 required actions。
代码仓库:
https://github.com/ax2/zi-quant-platform
本章代码:
git clone https://github.com/ax2/zi-quant-platform.git
cd zi-quant-platform
git checkout chapter-41-45-paper-command
uv sync --extra dev
uv run python -m scripts.chapter_examples paper-command
uv run pytest tests/test_execution_guard.py tests/test_daily_run_command.py tests/test_daily_run_plan.py tests/test_failure_policy.py tests/test_chapter_examples.py
第 41-45 篇共用 tag chapter-41-45-paper-command。当前全量测试通过:276 passed,只有既有 FastAPI deprecation warning。
本篇小结
第 44 篇把真实执行前的最后一层判断单独抽出来。
这个模块很小,但边界重要。以后无论 CLI、定时任务还是 Web 后台触发每日运行,都应该先拿 ExecutionDecision,而不是各自判断 status 字符串。