Repository navigation
Run the mover paper fixtures on a fixed clock, out of the overnight session - #1029
Merged
seathatflowsinourveins merged 1 commit intoOct 12, 2026
Merged
Conversation
…ession From 20:00 ET on Sunday 2026-10-11 (00:00Z), 10 tests failed on main 791eab0 in a network-less jail with the pinned native runtime: 7 of 28 in tests.test_adaptive_paper_mover_native, 1 of 18 in tests.test_alpaca_admission_regressions and 2 of 61 in tests.test_adaptive_paper_fees (on 3a8a846, which adds an admission test: 7 of 28, 2 of 19 and 2 of 61). Their trials read the real clock. runner.Controller._check_overnight_status (runner.py:934-958 at 3a8a846) classifies the controller's clock, whose default is the time.time builtin bound at import (runner.py:828), with sessions.session_at. In the OVERNIGHT session, 20:00 ET on the prior civil day to 04:00 ET of an XNYS trade date, it refuses every order on an extended-hours port that lacks an Alpaca overnight asset status, and no fixture supplies one. Every entry was refused, so the trial ended completed_no_signals (runner._run_native_status) and left no residual for the forced recovery the tests expect. The boundary was measured by moving the process clock (the real clock plus an offset, on time.time, time.time_ns and the clock= defaults) to 16 instants: the three modules pass in PRE, RTH, POST and CLOSED and fail in OVERNIGHT. Friday 20:01 ET (Saturday 00:01Z) and 2026-11-04 19:30 EST (Thursday 00:30Z) pass, 2026-11-04 20:30 EST fails and the eve of Thanksgiving passes: the cause is the ET session and the trade calendar, not the UTC date. CI never ran these tests: validate.yml installs no native runtime, and the job logs of PR 1025 show them skipped. tests/adaptive_paper_hermetic.py adds FixtureClock: the wall clock moved to Wednesday 2026-10-07 10:00 ET (RTH), later than every instant the fixtures embed, still advancing in real time, with one offset per test so a test's successive commands share a timeline. patched() installs it as time.time and time.time_ns and as the clock= default of runner.Controller and mover_strategy.MoverStrategy. The controller's clock cannot move alone: the ledger compares it with each quote's timestamp. MoverNativeEndToEnd.run_trial, MoverPaperCommandWiring.run_paper (also used by the admission regressions) and MoverLaneFees.run_command run on it, and the reads that fed them from outside (the halt seed's halted_at, a resumption's datetime.now, post_fees, the checkpoint seam and the request-budget probe) read the same clock. run_trial falls back to a fresh clock for test_mover_exit_chain, which calls it with its own TestCase. No product source changes, so no trading behaviour changes. Tests: - Three fail-first tests run a trial, a paper command and a fees trial inside a clock pinned at Sunday 2026-10-11 20:05 ET (asserted OVERNIGHT). On main's runners they fail with completed_no_signals at the real clock and at instants in RTH, CLOSED and OVERNIGHT; with this change they pass. - One positive test keeps the gate itself covered on the mover path: at that instant, on the controller's own clock, MoverController refuses an entry without an alpaca_assets overnight status and reserves it once a fresh, tradable, unhalted one arrives. Runs (network-less jail, TZ=UTC, pinned native runtime): - Real clock 02:14Z to 02:16Z (OVERNIGHT), on the first base: mover_native 31/31, admission 18/18, fees 62/62, exit_chain 14/14; main at the same time 7 of 28, 1 of 18 and 2 of 61 failing. - Rebased onto 3a8a846, real clock 03:13Z to 03:15Z: admission 19/19, fees 62/62, exit_chain 14/14, mover_native everything except test_x2_exits_with_a_partial_entry_timeout_and_an_unfilled_entry, an intermittent that main shows too (see the pull request); main at the same time 7 of 28, 2 of 19 and 2 of 61 failing. - 16 instants across PRE, RTH, POST, CLOSED and OVERNIGHT (first base): fees 62/62 at all 16 in those runs; mover_native, admission and exit_chain green at all 16 before the positive test was added, and at five of them (one RTH, one CLOSED, three OVERNIGHT) after it. An independent reader, under heavier load, saw AdaptiveLaneFees.test_full_next_trial_budget_prevents_the_http_read fail on both trees: that class does not use FixtureClock and the failure predates this change. - The 42 session test modules at the real clock: the same results as main except the three fixed modules. The source-hashes.json rows of the three test files, their evidence.json entries and source-hashes.json's own entry are updated with host_receipts.register_file. tests.test_adaptive_paper_source_hashes_manifest, tests.test_evidence_manifest and tests.test_trading_gates_bindings pass (25 tests), and scripts/validate.py and scripts/final_stack.py --check pass on this tree (base 3a8a846). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
seathatflowsinourveins
marked this pull request as ready for review
October 12, 2026 06:00
seathatflowsinourveins
deleted the
claude/trading-cc-test-clock-20261012
branch
October 12, 2026 06:12
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.
Run the mover paper fixtures on a fixed clock, so the overnight session gate cannot fail them. Test-only: no product source changes.
Cause
From 20:00 ET on Sunday 2026-10-11 (00:00Z), 10 tests failed on main (
791eab026) when run locally with the pinned native runtime, in a network-less jail with TZ=UTC:tests.test_adaptive_paper_mover_native: 7 of 28tests.test_alpaca_admission_regressions: 1 of 18tests.test_adaptive_paper_fees: 2 of 61The failing assertions are
'completed_no_signals' != 'passed'and its tuple forms. On3a8a8461e, which adds #1015'stest_sell_age_reaches_paper_and_forced_recovery_without_widening_entries, the counts at the real clock (OVERNIGHT) are 7 of 28, 2 of 19 and 2 of 61.runner.Controller._check_overnight_status(runner.py:934-958at3a8a8461e) classifies the controller's own clock withsessions.session_at(sessions.py:126-172). That clock defaults to thetime.timebuiltin, bound at import (runner.py:828). In the OVERNIGHT session, 20:00 ET on the prior civil day to 04:00 ET of an XNYS trade date, the gate refuses every order on an extended-hours port that lacks an Alpaca overnight asset status. No fixture supplies one. So every entry was refused, the trial endedcompleted_no_signals(runner._run_native_status), and no residual was left for the forced recovery that several tests expect.Boundary, measured by moving the process clock to 16 instants (real clock plus an offset, applied to
time.time,time.time_nsand theclock=defaults; main791eab026; failures per module):The cause is the ET session and the trade calendar, not the UTC date. CI never ran these tests:
validate.ymlinstalls only.github/requirements-ci.txt, which has no NautilusTrader or alpaca-py, so the native classes are skipped. In #1025's job logs, shard 3 skipped all 28 mover_native tests, shard 6 skipped 15 of 18 admission tests and shard 2 skipped 23 of 61 fees tests.Fix
tests/adaptive_paper_hermetic.pyaddsFixtureClock.patched()installs it astime.timeandtime.time_ns, and as theclock=default ofrunner.Controllerandmover_strategy.MoverStrategy. Those defaults bound the builtin at import, so a plaintime.timepatch misses them.RiskLimits.quote_max_age_seconds).MoverNativeEndToEnd.run_trial,MoverPaperCommandWiring.run_paper(also used by the admission regressions) andMoverLaneFees.run_commandrun on it.halted_at, a resumption status'sdatetime.now,post_fees, the checkpoint seam and the request-budget probe.run_trialfalls back to a fresh clock fortest_mover_exit_chain, which calls it with its own TestCase.source-hashes.jsonrows of the three test files, and themanifests/evidence.jsonentries of those files and ofsource-hashes.json, were rewritten withhost_receipts.register_file.native_pre_dispatch).Red/green evidence
MoverNativeEndToEnd.test_an_extended_hours_trial_keeps_the_fixture_clock_after_20_etMoverPaperCommandWiring.test_the_paper_command_keeps_the_fixture_clock_after_20_etMoverLaneFees.test_a_mover_trial_keeps_the_fixture_clock_after_20_etcompleted_no_signals. That held at the real clock (00:48Z and 02:14Z, OVERNIGHT) and at instants in RTH (Fri 15:00 ET, Mon 09:30 ET), CLOSED (Sun 19:55 ET) and OVERNIGHT (Sun 20:05 ET, Mon 00:30Z, Tue 20:01 ET).c735e8b2e), real clock 02:14Z (OVERNIGHT): mover_native 31/31, admission 18/18, fees 62/62, exit_chain 14/14. Main at the same moment still failed 7, 1 and 2 of its tests.3a8a8461e), real clock 03:13Z to 03:15Z (OVERNIGHT): admission 19/19, fees 62/62, exit_chain 14/14, the three binding modules 25/25,scripts/validate.pyandscripts/final_stack.py --checkexit 0; mover_native 31/31 apart from the x2 intermittent below. Main (3a8a8461e) in the same minutes: mover_native 7 of 28, admission 2 of 19 (both inCashContinuity; Bound paper sell quote age independently of entry freshness #1015 added one of them) and fees 2 of 61 failing. At Mon 09:30 ET, Mon 16:00 ET and Tue 20:01 ET: admission 19/19 and fees 62/62 each, mover_native 31/31 at the first and third and 30/31 (x2) at the second.Positive overnight test
MoverNativeEndToEnd.test_the_mover_controller_admits_an_overnight_entry_only_with_an_eligible_asset_statuskeeps the gate itself covered on the mover path.LedgerandMoverControlleron the existingclock=seam at Sunday 20:05 ET (asserted OVERNIGHT).before_submitrefuses the entry (overnight_status_unknown) and nothing is reserved.trading_status(transport.normalize_overnight_status(...))withovernight_tradable, the buy is reserved.tests/test_adaptive_paper_overnight.py:154-276, which covers the baseControllerwith a mock ledger.native_adapter.py:767-784refuses OVERNIGHT dispatch by design.Checks
tests.test_adaptive_paper_source_hashes_manifest,tests.test_evidence_manifest,tests.test_trading_gates_bindings(rebased commit)python3 scripts/validate.py(rebased commit)"status": "passed"python3 scripts/final_stack.py --check(rebased commit)test_x2_exits_with_a_partial_entry_timeout_and_an_unfilled_entryAdaptiveLaneFees.test_full_next_trial_budget_prevents_the_http_read9514a87fe, read 05:27:55Z)validate,validate-shard-0tovalidate-shard-7,verdict-review-gate,sota-sources,secret-scan, the CodeQL analyses and the restOpen intermittents (both predate this PR)
test_x2_exits_with_a_partial_entry_timeout_and_an_unfilled_entryBuilder's runs (2026-10-12 02:41-02:50Z, on the first base): 40 per tree for each kind, main and this PR alternating in one job list, 20 jails at a time, each with its own scratch directory; host 1-minute load average 28.6 to 58.5.
test_periodic_reconciliation_with_snapshot_latency_while_holdingin 1 of 40)The failure exists on main at least as often, so this PR's clock does not cause it.
Checks on the rebased commit (03:13Z to 03:26Z, same jail, 8 to 12 jails at a time, host 1-minute load average 28 to 51, real clock OVERNIGHT unless stated): four whole-module runs of this PR's commit at four clocks, in which x2 failed twice (the real clock and Mon 16:00 ET) and every other test passed; then an interleaved A/B of 24 whole-module runs per tree at the real clock: this PR's commit 24 of 24 runs fully green (31/31, no x2 failure), main
3a8a8461e24 of 24 runs with exactly its 7 deterministic overnight failures and no x2 failure. Pooled with the builder's runs, whole-module x2 failures are 4 of 98 runs on this PR's tree and 5 of 95 on main; an independent reader's whole-module runs gave 0 of 30 on each tree. The runs do not separate the trees. R1 below names the experiment that would.The receipts of failing runs on both trees show the same signature: an exit order is
filledin the ledger but carries no native fill event, and the receipt'snative_fill_eventscount is short. That fitsrun_mover(mover_runner.py:381-382) ending once the ledger is flat, before the last exit's nativeOrderFilledreaches the strategy. The engine log at INFO does not record order events (log_events=False), so the ordering itself is unconfirmed. It is a separate, pre-existing issue and is left unchanged here.AdaptiveLaneFees.test_full_next_trial_budget_prevents_the_http_readAn independent reader saw
tests/test_adaptive_paper_fees.py:936-944(assertIsInstance(raised, SafetyError)getsNone) fail at 4 of 7 shifted instants under load on this PR's original tree, on791eab026too, and at the real clock with no harness on both trees; run alone it passed 6 of 6 per tree. The class does not useFixtureClockand is untouched. In the builder's runs on the first base fees was 62/62 at all 16 instants, and four runs on the rebased commit (03:13Z to 03:15Z, load 28 or more) all passed 62/62. It is load-sensitive and pre-existing; R3 below names the open question.Independent read of the original commit (Opus, read-only: verdict PASS, no blocker)
Read at
3d7e3635(parent791eab026), before the rebases; the rebased commit is checked against it by script (see Integration notes). The read reproduced the 10 failures on main at OVERNIGHT (TZ=UTC and TZ=America/New_York), the 7-instant boundary (main fails exactly in OVERNIGHT; the fix is green at all 7 for mover_native and admission), the red/green of the three fail-first tests with only the three wiring lines reverted (3 red, 1 green, at the real clock and in RTH), and a leak probe in one process (no fixture clock or default outlived its window). Follow-ups, none of which blocks and none of which this PR changes:AdaptiveLaneFees.test_full_next_trial_budget_prevents_the_http_read(tests/test_adaptive_paper_fees.py:936-944) fails under load on both trees (the reader saw it at 4 of 7 instants; alone it passed 6 of 6 per tree). The class does not useFixtureClock. The commit-message figure "fees 62/62 at all 16 instants" is the first-base runs' result.FixtureClock:runner.py:474(observed_at) andtransport.py:1242(history_start). A fixture-clock receipt can carry anobserved_atabout 4.5 days after its own trial stamps. No assertion reads them today.__kwdefaults__ofrunner.Controller.__init__andmover_strategy.MoverStrategy.__init__from tests (no product line changes), which is a third option beside "a seam the code already has" and "a minimal parameter".native_strategy.AdaptiveStrategy.__init__(native_strategy.py:26-27, defaultclock=time.time) is not patched and is imported lazily; the mover paths never import it under the patch, but a future mover-path test that first-imports it insidepatched()would bindFixtureClock.timeas its default for the rest of the process. One line inpatched()(import it first, or patch its default too) closes that.tests/adaptive_paper_hermetic.py's comment "later than every instant the fixtures embed" holds for every fixture the three modules read;tests/fixtures/adaptive_paper_citations/sources.jsoncarries 2026-10-10 and 2026-10-11 but onlytests/test_adaptive_paper_overnight.pyreads it.overnight_status_stale,overnight_not_tradableorovernight_haltedbranches or theboatsfeed entry, and no end-to-end overnight trial exists on this path (native_adapter.py:767-784refuses OVERNIGHT dispatch by design), so the unit test is the only mover-path coverage of the refusal.patch.dictrestores__kwdefaults__byclear()thenupdate()on the shared dict: a thread constructing a Controller in that window would see no clock default (test-only, not observed).Clock.new_test); cite the rc5 live-clock source or measure it.Research queue:
FixtureClockthe simulated broker stamps accepts and fills with the patchedtime.time_ns(mover_simulation.py:119,141,194,212), soOrderAcceptedandOrderFilledts_eventwould sit about 4.5 days beforets_initand before every other NautilusTrader stamp, if the engine's Rust clock stays real as the docstring says. On main both are real. Experiment, to run when the real clock is in a non-overnight session: run the sub-penny and x2 tests 100 times per tree under equal load, then once more on this tree with the fixture start moved to the real clock (offset 0). If the class vanishes at offset 0 the inversion is in the causal path and becomes a P2 onhermetic.py. The counts above do not separate the trees.test_x3_trails_out_at_085_of_the_running_highfailed once on main in the reader's runs,test_exhausted_exits_hand_off_to_recovery_soon_after_the_hard_flattenonce on this PR's tree).safety.pyrequest-budget windowing against the adaptiverun_main's clock).Integration notes
Lane
lane:trading.The commit was built on
791eab026, rebased ontoc735e8b2e(clean) and then onto3a8a8461e(after Bound paper sell quote age independently of entry freshness #1015 and Add a local Claude review worker for essential pull request heads #953). On each rebasemanifests/evidence.jsonwas rebuilt from main's copy by re-registering exactly:tests/adaptive_paper_hermetic.pytests/test_adaptive_paper_fees.pytests/test_adaptive_paper_mover_native.pyblueprints/us-equities/adaptive-paper/source-hashes.jsonThe result is byte-identical to the rebase's own merge. Checked by script against the original commit (
3d7e3635, parent791eab026): the same 5 changed paths;git patch-id --stableequal for the three test files andtests/adaptive_paper_hermetic.py; theevidence.jsonrows that differ from main are exactly the four registered paths, each equal to the sha256 and size of the file at this commit;source-hashes.jsondiffers from main in exactly the three test rows, each equal to the file's sha256; the manifest diff is 7 removed and 7 added lines, allsha256orbytes; the original and this commit differ in themover_nativeandsource-hashes.jsonrows only because main's own change to that test file (Bound paper sell quote age independently of entry freshness #1015) is now part of the base.On a later rebase, take main's
evidence.json, re-register the same four files, and recompute the three test rows insource-hashes.jsonif main touched those files.Bound paper sell quote age independently of entry freshness #1015's new admission test uses
run_paperand inherits the fix.CI does not run these tests, so a reviewer needs a local run with the pinned native runtime.
Cross-family gate (read at
9514a87fe)nas-pr1029-gate-9514a87f.md(trading-cc-readsuccess at 05:28:25Z).a59845ba…, returned 05:21:23Z by the co-op's watch). On her reconstructed merge base the real overnight session fails 7 of 28 mover-native, 2 of 19 admission and 2 of 61 fees tests; the head and the merge with main8b1ef0c3pass 31 of 31, 19 of 19 and 62 of 62; exit-chain is 14 of 14 on all three; the three fail-first tests fail withcompleted_no_signalswhen only the clock wiring is disabled in memory;validate.pyandfinal_stack.py --checkrc 0 on head and merge. Her four P3: (1) the disclosed lazy-import boundary (follow-up 3 above); (2) fixture and metadata timestamps use different clocks (follow-up 2 above); (3) 18 hostname-bearing lines in the changed files are inherited, none added, 0 username hits (permitted by the 10-11 ruling); (4) the ordinary asynchronous native commands time out in her sandbox (her complete native results use a disclosed 50 ms heartbeat harness), so the ordinary commands should be repeated in the co-op's jail.SOTA sources
3a8a8461ee60bd499efcb78156ce4c3c052430c5:blueprints/us-equities/adaptive-paper/runner.py:934-958(Controller._check_overnight_status; defaultclock=time.timeat:828)blueprints/us-equities/adaptive-paper/sessions.py:126-172(session_at)sessions.pycites (https://docs.alpaca.markets/us/docs/245-trading.md, updated 2026-07-07, fetched 2026-10-10): 20:00 to 04:00 ET, Sunday evening through Friday morning.gerrymanoim/exchange_calendarsatdbe38b1f6887434bbdd1a7d2df6ff8f1742a048a,exchange_calendars/exchange_calendar_xnys.py.unittest.mock(patch.object,patch.dict; https://docs.python.org/3.12/library/unittest.mock.html) on the functions' keyword-only defaults (__kwdefaults__; https://docs.python.org/3.12/reference/datamodel.html).MoverNativeEndToEnd.test_an_extended_hours_trial_keeps_the_fixture_clock_after_20_etMoverPaperCommandWiring.test_the_paper_command_keeps_the_fixture_clock_after_20_etMoverLaneFees.test_a_mover_trial_keeps_the_fixture_clock_after_20_etMoverNativeEndToEnd.test_the_mover_controller_admits_an_overnight_entry_only_with_an_eligible_asset_statusScope
3a8a8461ee60bd499efcb78156ce4c3c052430c5lane:tradingtests/adaptive_paper_hermetic.py,tests/test_adaptive_paper_mover_native.py,tests/test_adaptive_paper_fees.pyandblueprints/us-equities/adaptive-paper/source-hashes.json(trading);manifests/evidence.json(shared hot file, re-registration only).Evidence-class table
source_reviewrunner.py:932-956,sessions.py:126-172local_integrationlocal_integrationlocal_integrationlocal_integrationsyntheticLocal commands run
Decision record
None: a test-only change that alters no rule, doctrine or trading behaviour.
Host evidence
Not applicable: no files under
evidence/hosts/change.Checklist
permissions: {}and grant each job only what it needs (none changed).🤖 Generated with Claude Code