Repository navigation
Mover trial mode for the adaptive NautilusTrader paper engine (SYN-verified; paper needs an isolated account) - #172
Merged
Conversation
Protocol mover-early-entry-v1-20260924, paper_e2e section. A bounded trial enters up to five scanner-ranked movers at trial start and exits them by the configured rule (X1 close, X2 +60 min, X3 0.85 trail, X4 0.85/1.50 bracket), with a hard flatten at the configured trial end. - mover.py: pure logic. Config and scan validation (sha256 recorded, older than 5 minutes refused), notional_i = equity x L_i / 5 with the dollar-volume, entry-bar, per-order and gross caps, the drawdown ladder and rung schedule, tick-rounded marketable limits, and the per-symbol order state machine (one buy per symbol, never chased, chunked exits, STOP/hard-flatten/guard). - mover_strategy.py: NautilusTrader shell; orders go through the unchanged adapter, controller, ledger caps and reconciliation. - mover_runner.py: check/paper/recover/synthetic commands and the native trial loop modeled on run_native; reuses preflight, validate_preflight, Ledger, reconcile and recovery.recover. Suspends quote-driven orders while a reconciliation snapshot is in flight (a measured race otherwise), ignores only foreign terminal orders, and refuses a state root shared with the adaptive lane unless --allow-shared-account. - mover_simulation.py: synthetic port with resting limits and partial fills. - config-mover.json: pre-market only, ends 09:25 ET, exit X2, rung 1, 200 USD per order, 2000 USD gross cap with a 2x appreciation allowance. - 44 pure tests (both runtimes) and 11 native end-to-end tests (pinned runtime), including the paper command wiring against the synthetic port. Evidence class SYN only: no broker order was sent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…y, bounded exits Review findings on 0480c8d (mover trial mode). Each fix has SYN tests, and each test was checked to fail when its fix is reverted in a scratch copy: - Recovery adopted every ledger intent, but a transport can only adopt intents for symbols it subscribes. From the second session on (other scan symbols) the forced recovery and `recover` raised TransportError before any cancel or sell, and the lane stuck. mover_runner.recover_mover now adopts every unresolved intent and only the terminal intents of subscribed symbols; skipped ones stay covered by the history pages and submitted_intent_absent. The synthetic port now applies normalize_intent on submit and adopt and needs an owned intent to cancel, as the transport does, and the native harness recovers on a fresh port. - recovery.recover sized exits as min(position, max_order_qty, notional capacity). In "notional" mode that exceeds the ledger's floor(notional cap / bid) share cap for any appreciated position (order_size_cap_exceeded). It now uses RiskLimits.effective_max_order_qty, which is unchanged in "fixed" mode. The mover requires "notional" mode so recovery chunks are whole shares. - Exits: no sell on a halted quote; a pre-wire refusal is retried after 1 s without being charged (own bound); a latched force grants one fresh budget; exhaustion before a force latches a book-wide flatten. After a force, the native loop hands the residual to recovery when every held leg is blocked or no exit fills for three exit timeouts, instead of idling until the sell window ends. - sell_limit_price never prices above the bid rounded down to the tick. The new native test then showed a 2-decimal instrument cannot carry the sub-penny fill below 1 USD (cumulative_fill_precision_requires_reconciliation), so every mover instrument is registered at 4 decimals; limits keep the 0.01 tick at or above 1 USD. Kept: a trial that needed a forced recovery stays needs_attention (the engine's sticky rule); `recover` clears it and now passes on a multi-session ledger. Not taken: an immediate hand-off for an unsellable leg before any force, because recovery stops at its first failed symbol and could leave sellable legs unsold. Evidence class SYN only: pure tests, native LiveNode tests against the synthetic port and mutation checks. No broker order was sent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s mid-pass Recheck findings on 5438e5b (mover trial mode). Each fix has SYN tests that fail on the original code and pass now; each piece was also reverted alone in a scratch copy and caught by its tests. - MAJOR: the ledger's max_order_notional_usd (200 in config-mover.json) bounded exit sells as well as entries, so once one share of a leg was worth more than 200 USD no engine path could sell it (book blocked, forced recovery and `recover` refused it) and the trial ended needs_attention holding the position. The mover now has its own required entry cap, mover.max_entry_notional_usd (200), used for sizing, the entry headroom skip and buy quantities, and MoverController refuses a larger buy before the ledger reserves it (mover_entry_notional_cap_exceeded), so entries are unchanged. The ledger cap is 2000 (the gross cap, the most RiskLimits allows) and config load requires it to be at least EXIT_HEADROOM_FACTOR (10) x the entry cap (mover_ledger_order_cap_below_exit_headroom). A leg sells whole through a tenfold rise and in whole-share chunks until one share exceeds the ledger cap (about 20x from an entered share, past the ledger's own gross-cap halt). Past that the book blocks it as exit_share_exceeds_ledger_order_cap and recovery_mover lists it under unsellable_positions (share_exceeds_ledger_order_cap), surfaced in the trial receipt. The factor is a code constant, keep-but-compare against the historical distribution of movers' largest rise within the hold. - MINOR: MoverBook.evaluate read the force once before its per-symbol loop, so a book-wide force latched by _exit mid-loop (exit_orders_exhausted or exit_refusals_exhausted) missed later legs; a waiting entry after it could still buy. Cancels and exits now run first and read the force per leg; a latch during that pass sends every leg (also outside the evaluated symbols) through again; entries run last under the current force. The latching evaluation cancels open buys, flattens every leg and sends no buy (so no buy is sent and canceled in one batch). Shared engine modules are unchanged (safety.py, recovery.py, runner.py). The mover ledger pins its limits (persisted_risk_limits_differ); no mover ledger existed on this host. README-mover.md documents the two caps, the exit headroom and the pin. Evidence class SYN only: pure tests, native LiveNode tests against the synthetic port (including the paper and recover commands with a fake preflight) and mutation checks. No broker order was sent and no credential was read. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the pre-market mechanics rule The pre-pass force is read before the hard-flatten latch, so a one-symbol evaluation at the flatten time also flattens every other leg (regression test fails without it). config-mover.json uses 08:00|G20|V250000|any with X2, the pre-market mechanics rule of the mover study's paper plan. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mover_scan.engine_scan formats every number with at most 9 decimal places, the limit of the engine's scan parser (mover._scan_decimal); a 12-place volatility input was refused as scan_regime_invalid. Checked end to end: the scanner's engine file passes mover_runner check and a synthetic trial through the real LiveNode (5 entries, 5 X2 exits, flat, cash delta = realized P&L). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28c2e6950f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Keep the order journal when the native loop raises: run_mover records any exception once its node task exists and still returns the book's legs, orders, events and native counts (status needs_attention, bounded error_type/error_reason, a mover_loop_error event). A periodic reconciliation that finds an external order after fills no longer yields a receipt with zero symbols and entries. - Refuse scan rows below the protocol's 1.00 USD minimum price (scan_symbol_below_min_price), and apply the scanner's own gain tolerance (G - 1e-9 as a ratio, 1e-7 percentage points at nine decimals) so a boundary candidate the scanner fired is accepted. - Require a positive max_leverage at or above the highest scheduled rung (mover_rung_exceeds_max_leverage): entries are sized at the full rung. - Refuse an X1 config that could never reach 15:58: a session-close latch (controller close less cleanup_seconds) or a trial end at or before it (mover_x1_preempted_by_session_close, mover_x1_preempted_by_trial_end). - check --assume-fresh and synthetic --allow-stale-scan take the scan time from load_scan's guarded parser (now=None), so a malformed scan is refused with its reason instead of raised; an out-of-range scan_time is scan_time_invalid. - Keep config-mover.json at the 08:00|G20|V1000000|any rule the tests and README pin; regenerate source-hashes.json and manifests/evidence.json. Each behaviour change has a test that fails before and passes after (SYN fixtures only; no broker, network or credentials). The adaptive-paper suite passes on the pinned native runtime (732 tests, 1 skipped) and on system python3 (732, 198 native skips); evidence_manifest --check and validate.py pass. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
X1 fires two minutes before the session's regular close from the engine calendar (15:58, or 12:58 on a scheduled early close; study clarification C26). plan_timing refuses a plan whose hard flatten comes at or before X1 (mover_x1_unreachable): the independent recheck showed a regular-session X1 config with cleanup_seconds 119 and the default 120 s flatten reserve could never reach X1 at the latest start the preflight allows. Tests cover the early close and the refusal. 732 adaptive-paper tests OK on both runtimes; synthetic trial through the LiveNode passed (5 entries, 5 X2 exits, flat, reconciled). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 24, 2026
5 tasks done
seathatflowsinourveins
added a commit
that referenced
this pull request
Oct 12, 2026
Fix the decision record of the compounding rule text, docs/decisions/2026-10-11-compounding-rule-text.md. The six rule surfaces do not change. - Approval: the owner approved section 1's proposal, the first sentence, at 22:01Z. The second sentence was presented as an optional clause and the owner's answer did not single it out; the command center (CC) included it under the owner's delegation, because it restates the owner's own no-dates direction. The owner was told at 23:44Z, with the option to drop it. The record cites the ruling's addendum-01 and the SHA256 of each sentence. - #172: restated with its recorded supersession at 22:18:17Z, under which guard bypasses are follow-ups (research-queue row 23). #1014 landed as ccff876 with only the one-letter POINTER_VARIABLE suffix. - PR triage: 3 fold receivers (#878, #927, #958) merged without their 4 donors (#804, #915, #870, #829), citing the triage's addendum-01. - What the second sentence leaves in force (F1 of the trading lane's read of us-equities-trading #87): it forbids a date as the trigger that starts work, and every time bound the contract states as a fact or protocol window stays. - Checks: the budget test's sentence now says what the test does (the first GPT read of this PR): test_growth_in_any_loaded_file_crosses_the_fixed_budget fills the available headroom in each loaded file in turn, verifies that equality with the ceiling passes, then appends a two-byte UTF-8 character and verifies rejection. - manifests/evidence.json: the record re-registered with register_file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A bounded paper trial mode for the mover studies (#162): up to five scanner-selected symbols entered at trial start with marketable limits (extended hours outside RTH), sized by the preregistered leverage schedule (rung x regime x drawdown, sub-$5 at 1x, capacity caps), exited by one study rule (X1-X4), hard-flattened at the configured trial end, reconciled.
mover.py(pure logic,MoverBookstate machine),mover_strategy.py(Nautilus shell),mover_runner.py(check/paper/recover/synthetic),mover_simulation.py(synthetic port),config-mover.json(pre-market mechanics rule08:00|G20|V250000|any, X2, flat by 09:25 ET),README-mover.md. Shared engine modules are unchanged.mover.max_entry_notional_usdsizes entries; the ledger per-order cap is at least 10x it so any held leg can be sold in whole shares; a share beyond that is reported underunsellable_positions.mover_scan.pywrites engine-valid scan files (at most 9 decimal places).next_trial_cash_mismatch, and unfilled mover orders freeze its ledger.paperrefuses when the same state root holds an adaptive lane unless--allow-shared-account.Review
Built by an Opus worker in an owned worktree; two independent Opus reviews (correctness, trading safety) found 2 blocking and several major issues (cross-session recovery with changing symbols, recovery unable to sell winners past the per-order cap, exit budgets, sub-dollar ticks); fixed in
5438e5b0; recheck found one remaining major (a single share above the per-order cap) fixed in5819721cwith tests that fail before the fix; recheck approved. The coordinator then fixed the last minor (hard-flatten latch in one-symbol evaluations, regression test fails without it).Evidence
pnl_consistent.Test plan
test_adaptive_paper*.py725 OK (skipped 1)python3 scripts/validate.pypassed;evidence_manifest.py --checkpassedorigin/main..HEAD: no leaks🤖 Generated with Claude Code