Quant Trading for Programmers 42: Persist Daily Run Artifacts
Quant Trading for Programmers 42: Persist Daily Run Artifacts
Article 41 compressed the daily-run plan into a one-line summary suitable for logs and CLI output.
But logs only answer “what did it look like at that moment?” To debug why a run became blocked, we need more complete evidence: input symbols, failed checks, failure actions, and generation time. That is the role of an artifact.

What The Artifact Stores
Article 42 adds app/daily_run_artifacts.py.
@dataclass(frozen=True)
class DailyRunArtifact:
path: Path
payload: dict[str, Any]
payload is not a rough serialization of the dataclass. It is written as a stable debugging format.
| Field | Description |
|---|---|
trade_date | The trading day for the run |
status | ready, dry_run_ready, or blocked |
required_symbols | Symbol list requested by this run |
failed_checks | Checks that did not pass |
actions | Remediation action for each failed check |
generated_at | Request generation time |
executable | Whether real execution is allowed |
Build JSON Payload
The core function is daily_run_artifact_payload().
def daily_run_artifact_payload(plan: DailyRunPlan) -> dict[str, Any]:
summary = build_daily_run_summary(plan)
return {
"trade_date": summary.trade_date,
"status": summary.status,
"dry_run": summary.dry_run,
"symbol_count": summary.symbol_count,
"required_symbols": list(plan.request.required_symbols),
"failed_checks": list(plan.result.failed_checks),
"actions": [
{
"check_name": action.check_name,
"action": action.action,
"severity": action.severity,
}
for action in plan.failure_actions
],
"action_summary": summary.action_summary,
"executable": summary.executable,
"generated_at": plan.request.generated_at.isoformat(),
}
The tuple fields are deliberately converted into lists. Artifacts are for external readers, so they should not expose Python object habits.
Write The File
The file name is generated by trading day:
def write_daily_run_artifact(plan: DailyRunPlan, *, directory: Path) -> DailyRunArtifact:
payload = daily_run_artifact_payload(plan)
directory.mkdir(parents=True, exist_ok=True)
path = directory / f"daily-run-{payload['trade_date']}.json"
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return DailyRunArtifact(path=path, payload=payload)
In a blocked scenario, the artifact keeps action information like this:
{
"actions": [
{
"check_name": "run_health",
"action": "inspect_run_health",
"severity": "blocker"
}
]
}
That is much more useful than seeing only blocked in logs.
Runnable Example
Article 42 continues to use the same command to reproduce artifact persistence:
uv run python -m scripts.chapter_examples paper-command
The output for this article is:

Three details in the screenshot are genuinely useful for debugging.
artifact=daily-run-2026-03-07.json shows that the file name is stable and keyed by trading day. On call, nobody needs to guess which file in the log directory belongs to today’s result.
payload_keys shows that the artifact keeps request, status, failed checks, actions, and generation time. It is not a backup of the one-line summary; it is richer runtime evidence.
failed_checks=['data_gaps'] actions=['repair_market_data'] executable=False puts failure cause and remediation direction together. From this line alone, we know the blocker is not a strategy signal issue and not a run-window issue. Market-data gaps blocked execution.
In a production system, I would keep logs, artifacts, and the later runbook separate: logs for first glance, artifact for review evidence, and runbook for next actions. They should reference each other, but not replace each other.
Tests
The tests cover:
- payload contains the context needed for debugging;
- written JSON can be read back completely;
- blocked scenario keeps
check_name,action, andseverity.
Run:
uv run pytest tests/test_daily_run_artifacts.py tests/test_daily_run_summary.py tests/test_daily_run_plan.py
After adding paper-command in this batch, the full suite passed:
276 passed, 2 warnings
Repository
This article adds:
app/daily_run_artifacts.py;DailyRunArtifact;daily_run_artifact_payload();write_daily_run_artifact()andread_daily_run_artifact();tests/test_daily_run_artifacts.py, covering payload and JSON round trip;- artifact write/read in
scripts/chapter_examples.py; the article screenshot comes from that command output.
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_daily_run_artifacts.py tests/test_daily_run_summary.py tests/test_daily_run_plan.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
An artifact is an evidence file in a production system.
Article 42 writes the daily-run plan into JSON so later commands, alerts, and debugging can all point to the same file instead of each rebuilding their own context.
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
- Quant Trading for Programmers: Column Roadmap
- Quant Trading for Programmers 45: Daily Run Runbook
- Quant Trading for Programmers 44: Execution Guard
- Quant Trading for Programmers 43: Command Response Object
- Quant Trading for Programmers 41: Summarize The Daily Run Plan
- Quant Trading for Programmers 40: Compose The Daily Run Plan
- Quant Trading for Programmers 39: Turn Failed Checks Into Actions
- Quant Trading for Programmers 38: Index Daily Report Archives