Quant Trading for Programmers 44: Execution Guard
Quant Trading for Programmers 44: Execution Guard
Article 43 wrapped the daily run as a command response, but it did not solve a more sensitive question: when is real execution allowed?
In a paper-trading system, dry_run_ready is easy to misread as “ready.” If the command layer only looks at exit code, a dry run may accidentally be connected to the real execution flow. Article 44 centralizes that decision into an execution guard.

Execution Decision
Article 44 adds app/execution_guard.py.
@dataclass(frozen=True)
class ExecutionDecision:
allowed: bool
reason: str
required_actions: tuple[str, ...]
allowed is the machine decision. reason and required_actions are for humans and alerting systems.
| Plan status | allowed | reason | required_actions |
|---|---|---|---|
ready | true | ready | empty |
dry_run_ready | false | dry_run | disable_dry_run |
blocked | false | warning or blocker | failure-action list |
Decision Logic
The core function is:
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",))
The important part is that dry_run_ready does not pass. It means checks passed, but the system is still in rehearsal mode.
Stable Log Line
The execution decision also has a one-line format:
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}"
A warning case prints:
allowed=false reason=warning actions=inspect_archive
This line is suitable for artifacts, alerts, or command responses. It tells the on-call engineer why execution did not happen.
Runnable Example
This article continues to use the same command:
uv run python -m scripts.chapter_examples paper-command
The output for this article is:

Here, allowed=false is the core decision. Article 43 already used exit_code=1 to tell the scheduler that the run failed. The execution guard now tells the real execution layer: do not place orders, do not update the paper-trading account, and do not trigger downstream trading actions.
reason=blocker comes from the severity of the failure action. Together with required_actions=('repair_market_data',), it tells humans that a data problem must be fixed first, not ignored as a warning.
The value of the execution guard lies at the boundary. CLI, web admin, and scheduled jobs may all trigger daily runs later, but they should not each parse the status string independently. Everything should pass through decide_execution() so dry runs are not mistaken for ready states, and blocked states do not produce real side effects.
Tests
The tests cover:
readyplan allows execution;dry_run_readydoes not allow execution and requiresdisable_dry_run;blockedplan returns failure actions;- log-line format remains stable.
Run:
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
After adding paper-command in this batch, the full test suite passed:
276 passed, 2 warnings
Repository
This article adds:
app/execution_guard.py;ExecutionDecision;decide_execution();execution_decision_line();tests/test_execution_guard.py, covering ready, dry-run-ready, blocked, and log format;- execution-guard output in
scripts/chapter_examples.py, reproducing required actions for a blocked run.
Repository:
https://github.com/ax2/zi-quant-platform
Code for this chapter:
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
Articles 41-45 share the tag chapter-41-45-paper-command. The current full suite passes with 276 passed, with only existing FastAPI deprecation warnings.
Summary
Article 44 extracts the final judgment before real execution into its own module.
The module is small, but the boundary matters. Whether a daily run is triggered by CLI, scheduled job, or web admin later, it should first obtain an ExecutionDecision instead of checking the status string itself.
Follow ZiCode on WeChat
If this post was useful, you can follow later updates on WeChat as well.
X / Twitter
Follow @ax2_zicode
Faster technical notes, short thoughts, and new-post alerts are posted on X.
More in this column
- What Programmers Need to Learn Before Building a Quant Platform
- Quant Trading for Programmers: Column Roadmap
- Quant Trading for Programmers 45: Daily Run Runbook
- Quant Trading for Programmers 43: Command Response Object
- Quant Trading for Programmers 42: Persist Daily Run Artifacts
- Quant Trading for Programmers 41: Summarize The Daily Run Plan
- Quant Trading for Programmers 40: Compose The Daily Run Plan