Python → Rust Migration: Python Deletion Plan¶
Status: draft — cutover-gated. File removals happen after the matching Rust binary passes its 5-business-day parity gate and the explicit cutover judgment has been recorded in WORK_LOG.
Created: 2026-04-10 (Claude). Source of truth: this file + the per-cutover WORK_LOG entries. Any deviation from this plan must be documented in WORK_LOG before any Python file is deleted.
Rationale¶
The goal of the 2026-04 migration is to delete all Python trading
infrastructure and run the full live LT loop + data archive +
quote collection + token refresh as Rust binaries. Each cutover
(G4 → G1 → G3 → G2) retires a specific chunk of Python. This
document is the explicit inventory: for every Python file in
aegis_v3/aegis/ and aegis_v3/scripts/ that belongs to the
live trading path, we record which cutover deletes it and
what the Rust replacement is.
Categories:
- DELETE @ G4 — Saxo token refresh (post G4 Day 4 cutover)
- DELETE @ G1 — PT loop, multi-scenario, order manager, broker, shadow (post G1 Day 10 cutover). This is the largest chunk.
- DELETE @ G3 — Live quote collector (post G3 Day 13 cutover)
- DELETE @ G2 — LDAS EOD + intraday + writer (post G2 Day 15 cutover)
- KEEP — BT-only code that has no Rust equivalent yet or is shared with the BT lane (Claude's work boundary). These stay until the BT → Rust migration lands in a future phase.
- KEEP (shared) — Files consumed by both the trading loop and the BT; editing boundary applies (Codex LT lane vs Claude BT lane). These stay until the BT stops using them.
G4 DELETE — Saxo token refresh (post Day 4 cutover)¶
| File | Purpose | Rust replacement |
|---|---|---|
aegis_v3/scripts/token_keeper.py |
OAuth2 refresh daemon loop | aegis-bt-rs/src/bin/lt_token_keeper.rs |
The Python keeper writes /volume1/aegis/tokens/saxobank_tokens_live.json
(canonical). The Rust keeper writes
/volume1/aegis/tokens/saxobank_tokens_live_rust.json during parallel
observation, then takes over the canonical filename at cutover.
Gate: 2 business days of saxobank_tokens_live_rust.json mtime
updating every 15 min + 0 401 Unauthorized errors across all
Rust containers.
G1 DELETE — PT loop + broker + shadow (post Day 10 cutover)¶
This is the largest chunk. Python aegis-wft + its supporting
modules + the entire shadow observation architecture come down
together.
Engine (24 files)¶
| File | Purpose | Rust replacement |
|---|---|---|
aegis/engine/paper_trading.py |
PT core scan loop | aegis-bt-rs/src/engine/scan_cycle.rs |
aegis/engine/multi_scenario_pt.py |
MultiScenarioPTEngine (11 scenarios) | aegis-bt-rs/src/engine/multi_scenario.rs |
aegis/engine/order_manager.py |
Pending/Active/Terminal order lifecycle | aegis-bt-rs/src/engine/order_manager.rs |
aegis/engine/paper_trading_state.py |
PtState JSON persistence | aegis-bt-rs/src/engine/pt_state.rs |
aegis/engine/paper_trading_clock_boundary.py |
Market clock helpers | merged into scan_cycle.rs market-hours gate |
aegis/engine/paper_trading_earnings_boundary.py |
Earnings calendar gate | merged into strategy live gates |
aegis/engine/paper_trading_heartbeat_boundary.py |
Heartbeat write helper | aegis-bt-rs/src/bin/lt_scan_cycle.rs heartbeat writer |
aegis/engine/paper_trading_hy_oas_boundary.py |
HY OAS lookup helper | merged into scan cycle bear context |
aegis/engine/paper_trading_no_option_cache_boundary.py |
No-option-cache reader | aegis-bt-rs/src/quote/collector.rs no-option cache |
aegis/engine/paper_trading_artifact_paths.py |
PT artifact path resolver | merged into scan_cycle.rs state path defaults |
aegis/engine/paper_trading_replay.py |
Replay log writer | aegis-bt-rs/src/engine/scan_cycle.rs ParityLogWriter |
aegis/engine/paper_trading_replay_loader.py |
Replay log reader | aegis-bt-rs/src/bin/lt_wft_parity.rs latest-record reader |
aegis/engine/paper_trading_replay_diff.py |
Replay diff helper | aegis-bt-rs/src/bin/lt_decision_diff.rs |
aegis/engine/lt_shadow_loop.py |
Legacy shadow replay loop | RETIRED (Strangler shadow architecture) |
aegis/engine/lt_shadow_observation.py |
Shadow observation rolling state | RETIRED |
aegis/engine/lt_shadow_observation_cycle.py |
Shadow observation cycle writer | RETIRED |
aegis/engine/lt_shadow_observation_daemon.py |
Shadow observation daemon | RETIRED |
aegis/engine/lt_shadow_compare_reports.py |
Shadow compare report generator | RETIRED |
aegis/engine/lt_shadow_live_paths.py |
Shadow live path resolver | RETIRED |
aegis/engine/lt_shadow_multi_scenario_cycle.py |
Shadow multi-scenario cycle | RETIRED (native in multi_scenario.rs) |
aegis/engine/lt_shadow_multi_scenario_observation_summary.py |
Shadow multi-scenario summary | RETIRED |
aegis/engine/lt_shadow_multi_scenario_preflight.py |
Shadow preflight check | RETIRED |
aegis/engine/lt_shadow_multi_scenario_targets.py |
Shadow scenario targets | RETIRED |
aegis/engine/lt_shadow_parity_triage.py |
Shadow parity triage | RETIRED (replaced by lt-wft-parity) |
Broker (8 files; protocol.py is kept until G3)¶
| File | Purpose | Rust replacement |
|---|---|---|
aegis/broker/saxo_adapter.py |
Legacy adapter | aegis-bt-rs/src/broker/saxo/adapter.rs |
aegis/broker/saxo/__init__.py |
Module re-exports | N/A (delete with directory) |
aegis/broker/saxo/adapter.py |
Broker adapter (new) | aegis-bt-rs/src/broker/saxo/adapter.rs |
aegis/broker/saxo/auth.py |
OAuth2 flow | aegis-bt-rs/src/broker/saxo/auth.rs (+ lt-token-keeper binary) |
aegis/broker/saxo/client.py |
REST client | aegis-bt-rs/src/broker/saxo/rest_client.rs |
aegis/broker/saxo/config.py |
Saxo env/config | aegis-bt-rs/src/broker/saxo/config.rs |
aegis/broker/saxo/models.py |
Saxo DTO models | aegis-bt-rs/src/broker/saxo/{orders,position,quote}.rs |
aegis/broker/saxo/streaming.py |
WebSocket subscriptions | aegis-bt-rs/src/broker/saxo/streaming.rs |
Note on aegis/broker/protocol.py: this abstract base class is
still imported by live_quote_collector.py (G3 target), so it
cannot be deleted at G1. It moves to "DELETE @ G3" below. No BT
code uses it (verified 2026-04-10: grep for aegis.broker.protocol
across the repo returns 11 files — all PT runtime, ops scripts, or
unit tests, none in backtest.py or scripts/run_7yr_bt.py).
Scripts (6 files)¶
| File | Purpose | Rust replacement |
|---|---|---|
aegis_v3/scripts/run_paper_trading_v3.py |
Entry-point for legacy PT | N/A (container uses lt-scan-cycle directly) |
aegis_v3/scripts/run_pt.py |
Entry-point for PT | N/A |
aegis_v3/scripts/run_multi_scenario_pt.py |
Entry-point for MultiScenarioPT | N/A (container uses lt-scan-cycle --scenario-multi-yaml) |
aegis_v3/scripts/check_lt_multi_scenario_shadow_preflight.py |
Shadow preflight | RETIRED with shadow architecture |
aegis_v3/scripts/evaluate_lt_shadow_cutover.py |
Shadow cutover evaluator | aegis-bt-rs/src/bin/lt_cutover_gate.rs |
aegis_v3/scripts/notify_lt_shadow_parity.py |
Shadow parity notifier | RETIRED (Rust parity gate is in-process) |
Gate: 5 business days of clean lt_wft_comparison.jsonl
+ Python pt_wft_comparison.jsonl matching per-scenario (equity
±$50, closed_trades ±0), verified daily by lt-wft-parity.
G3 DELETE — Live quote collector (post Day 13 cutover)¶
| File | Purpose | Rust replacement |
|---|---|---|
aegis/engine/live_quote_collector.py |
BT fill_ratio calibration daemon | aegis-bt-rs/src/quote/collector.rs + aegis-bt-rs/src/bin/lt_quote_collector.rs |
aegis_v3/scripts/run_quote_collector.py |
Collector entry-point | N/A (container uses lt-quote-collector directly) |
aegis/broker/protocol.py |
Broker ABC | aegis-bt-rs/src/broker/protocol.rs (deferred from G1 because live_quote_collector.py still imports it between G1 and G3) |
aegis_v3/scripts/live_quote_test.py |
Legacy live quote test script | N/A (replaced by Rust lt-quote-collector heartbeat + lt-wft-parity during observation) |
Gate: 3-5 business days of collector_heartbeat.json (rust)
updating every 30s + api_calls_per_minute within ±5% of Python
baseline + no_option_symbols set overlap ≥95%.
G2 DELETE — LDAS EOD + intraday (post Day 15 cutover)¶
| File | Purpose | Rust replacement |
|---|---|---|
aegis/data/ldas_collector.py |
LDAS EOD + intraday collector | aegis-bt-rs/src/data/ldas_collector.rs |
aegis/data/ldas_writer.py |
LDAS parquet writer | aegis-bt-rs/src/data/ldas_collector.rs (write_*_parquet fns) |
aegis/data/polygon_provider.py |
Polygon REST provider for Python PT + LDAS | aegis-bt-rs/src/data/polygon_live.rs (deferred from G1 because scripts/collect_eod_v3.py + collect_intraday_v3.py still import it between G1 and G2) |
aegis_v3/scripts/collect_eod_v3.py |
EOD cron entry-point | N/A (container uses lt-ldas eod) |
aegis_v3/scripts/collect_intraday_v3.py |
Intraday daemon entry-point | N/A (container uses lt-ldas intraday) |
aegis_v3/scripts/compute_iv_rank.py |
Legacy IV rank compute | aegis-bt-rs/src/ivrank/ + aegis-ivrank binary |
aegis_v3/scripts/compute_iv_rank_parallel.py |
Parallel IV rank | same (the Rust ivrank binary handles parallelism internally) |
Gate: 2 business days of EOD parity + 2 business days of
intraday parity (file count ±1, row count ±5, schema identical,
GEX regime distribution within ±1 symbol per regime), verified
daily by lt-ldas-parity.
KEEP — BT-only (no Rust equivalent yet)¶
These files are used by the BT lane (Claude's boundary) and have no Rust replacement yet. They survive the 2026-04 migration. A future BT → Rust migration phase will retire them.
| File | Why keep |
|---|---|
aegis/engine/backtest.py |
Python BT engine (Rust BT is aegis-bt-rs main binary, but Python BT is still used for edge-case verification) |
aegis/engine/regime.py |
Regime classification — backtest.py imports RegimeAdjuster (verified 2026-04-10). Must stay until BT → Rust phase retires backtest.py. |
aegis/engine/decision_log.py |
Decision log writer (PT + BT shared; simple enough to keep) |
aegis/engine/monthly.py |
Monthly analysis helpers (BT reporting path) |
aegis/data/v2_parquet_provider.py |
v2 parquet reader (BT path) |
aegis/data/pkl_cache_provider.py |
pkl cache for BT warm start |
aegis/data/parquet_provider.py |
v3 parquet reader (BT path) |
aegis_v3/scripts/run_7yr_bt.py |
7-year BT runner |
aegis_v3/scripts/analyze_*.py |
BT result analysis scripts (dozens) |
aegis_v3/scripts/launch_fargate_*.py |
Fargate launchers for BT sweeps |
aegis_v3/scripts/parity_diff.py |
Scan-level parity diff (still useful for shadow era) |
Note: aegis/data/polygon_provider.py and aegis/broker/protocol.py
were originally in this KEEP section but 2026-04-10 verification
confirmed neither is used by BT code. They moved to G2 DELETE and
G3 DELETE respectively (both deferred from G1 because of remaining
G2/G3 callers). The deletion gate for both is "no more Python
callers in the tree" plus the normal cutover gate for their
primary retiring subsystem.
KEEP (shared) — Edit with boundary lock¶
These files are edited by both lanes (LT runtime via Codex, BT
via Claude). Any edit must be synchronized per the boundary rule
in .claude/CLAUDE.md. At G1 cutover, the LT usage drops away and
only BT remains; then they move into the "KEEP — BT-only"
category or get a BT-specific fork.
| File | LT usage | BT usage |
|---|---|---|
aegis_v3/aegis/data/models.py |
PT loop + broker | BT engine |
aegis_v3/aegis/strategy/protocol.py |
PT strategy invocation | BT strategy invocation |
aegis_v3/configs/base.yaml |
PT loop runtime knobs | BT scenario defaults |
aegis_v3/configs/scenarios/lt_rc.yaml |
LT_RC live scenario | BT LT_RC verification |
aegis_v3/configs/scenarios/pt_wft.yaml |
Python MultiScenarioPT | — |
aegis_v3/configs/scenarios/pt_xw.yaml |
PT_XW scenario | BT PT_XW verification |
Removal procedure (when the gate passes)¶
For each cutover (G4 → G1 → G3 → G2), the removal PR follows this sequence:
- Verify the gate — latest
lt-{wft,ldas}-parity --jsonrun pinned to the cutover day showsoverall=PASS, attached to the WORK_LOG entry. - Verify the WORK_LOG approval — a line with explicit Ryo approval ("G1 cutover approved, remove Python PT files").
- Stop the Python container first —
docker stop aegis-wft(or the matching container), wait 30s for in-flight ops to flush. - Run
git rmfor the listed files — one commit per cutover, titledchore(python-migration): remove G{N} Python files post-cutover. - Update the runbook — the matching
lt-{scan-cycle,ldas,quote-collector,token-keeper}-runbook.mdmoves from "parallel observation" section to "cutover complete" section. - Update this file — move the deleted files from the "DELETE" table to a "DELETED @ YYYY-MM-DD" table at the bottom, so the history is visible.
- Re-run the Rust strict clippy CI — confirm nothing breaks from the removals.
- Leave the Python container image in the Synology registry
for 1 week as a rollback safety net. After 1 week with no
regressions, the image can be tagged
-retiredand eventually removed from the registry.
Open questions / follow-ups¶
All three 2026-04-10 open questions have been answered by verification against the live repo state:
- ✅
aegis/engine/regime.pykeep-or-delete: KEEP. Bothpaper_trading.py(G1) andbacktest.py(BT, KEEP) importRegimeAdjusterfrom this file. When paper_trading.py is deleted at G1, regime.py stays because backtest.py still needs it. It moves into KEEP (BT-only) permanently until the BT → Rust phase retires backtest.py. - ✅
aegis/broker/protocol.pykeep-or-delete: DELETE @ G3. No BT code imports it (verified againstaegis/engine/backtest.pyandscripts/run_7yr_bt.py). It stays through G1 becauseaegis/engine/live_quote_collector.py(G3 target) still imports it, and retires at G3 with the rest of the quote collector stack. - ✅
aegis/data/polygon_provider.pykeep-or-delete: DELETE @ G2. All 4 Python callers (scripts/collect_eod_v3.py,scripts/collect_intraday_v3.py,scripts/run_multi_scenario_pt.py,scripts/run_pt.py) are already in G1 or G2 deletion targets. No BT script uses it. It stays through G1 because collect_eod_v3.py and collect_intraday_v3.py still exist between G1 and G2, and retires at G2 together with them.