Skip to content

Paper engine: book realized P&L and gross loss per exact execution (realized-loss delivery-order major; follow-up to #350) - #355

Merged
seathatflowsinourveins merged 8 commits into
mainfrom
claude/paper-realized-loss-order-20260926
Sep 26, 2026
Merged

seathatflowsinourveins merged 8 commits into
mainfrom
claude/paper-realized-loss-order-20260926

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator

What

This is the follow-up to #350 (merged as dbf887e), and it closes the pre-existing realized-loss delivery-order major from the retroactive cross-family review. The accounting replay (Ledger._reconcile_execution_accounting, blueprints/us-equities/adaptive-paper/safety.py) summed a status delta's executions into one notional before computing P&L. Take a two-share sell with one fill at a gain and one at a loss inside the same delta: the replay netted the loss away. The resulting gross loss then depended on whether the stream or the REST snapshot arrived first. With two shares bought at $100 and sold at $110 and $90, REST-first delivery booked a gross loss of $0 where stream-first booked $10, so the $5 loss cap did not halt.

Replay now books each exact execution that tiles the delta separately. It falls back to the delta's notional only while the executions are still incomplete, and a later replay over the complete executions corrects it.

  • _execution_fills returns exact (qty, notional) pairs; _executions_notional keeps its old contract on top of it.
  • Average-cost basis per closing fill; losses accumulate separately from net P&L.

Base: main. This replaces #353, which GitHub closed when #350 merged and its base branch was deleted. The branch then merged main (2e29a0c). In that merge, git's automatic merge re-inserted the pre-fix _reconcile_execution_accounting after the fixed one, and Python would have bound the later definition. safety.py was restored from the reviewed c81a3d5, so this PR's delta against main is exactly the reviewed c1539cd1..c81a3d52 change (checked with a diff of diffs). The suites were re-run after the merge: 407 OK.

Evidence

  • Test-first regressions (tests/test_adaptive_paper_safety.py):
    • test_mixed_profit_gross_loss_is_exact_across_delivery_sequences covers stream-first, REST-first, duplicate and restart deliveries. It checks cash, realized P&L, cumulative loss, gross loss, the halt, redelivery after reopening, read_ledger and the blocked intents.
    • test_mixed_profit_replay_preserves_basis_at_each_booking covers a basis change between executions of one sell.
  • Mutation check (coordinator): with only safety.py reverted, 4 cases fail: rest_first, duplicates, restart and the basis test. stream_first passes on both versions, as expected.
  • Paper suites (coordinator, pinned runtime adaptive-paper-20260921, private TMPDIR): tests.test_adaptive_paper_safety, _runner, _native and _transport ran 407 tests, all OK. tests.test_trading_gates: 26 OK. scripts/validate.py passed.
  • Pins: the safety.py entry in adaptive-paper/source-hashes.json moves 72abaaca… → e0ba9233…, and manifests/evidence.json re-registers the 3 files. Historical receipts keep the hashes of the code they ran.
  • Authorship and review: GPT-6 (gpt-6-astra, effort max, workspace-write sandbox) implemented it test-first. The coordinator (Claude) reviewed the diff and re-ran every check above. An independent read-only Claude review follows in a comment.

Limitations

  • Ledgers that the old implementation already marked reconciled are not repaired automatically. A replay recomputes them only when a new observation triggers reconciliation.
  • Synthetic ledger tests only. This PR places no broker order, and paper keys currently return 401.

SOTA sources

🤖 Generated with Claude Code

Scout and others added 4 commits September 26, 2026 11:32
#242 and #284

GPT-6 (gpt-6-astra, effort max) reviewed three merged paper-engine PRs on main ca8e37f; #243 passed.
- #221: activity-based gap recovery booked fills natively without persisting them to the durable ledger (native 3,
  durable 1); a continuing LULD pause auto-cleared after twelve minutes and let cancel_expired cancel the exit it
  protected; cash and realized P&L depended on REST-snapshot versus stream delivery order.
- #242: replay_compare scheduled a named cancel stamped before its submit and silently skipped it; cancelling the
  transport task cleared the cancel identity before the HTTP worker sent the DELETE.
- #284: a clean exporter stop released the port before deregistering, so it could remove a replacement exporter's
  target; configure.py's initialization could overwrite a concurrent registration; malformed nested labels passed
  validation and would make Prometheus reject the whole target list.

Each fix was implemented test-first by a GPT-6 implementer in its own worktree; every new regression failed before
its fix. Coordinator integration: source-hashes.json and evidence pins refreshed; the 20260923g sim-paper-compare
receipt replayed from its retained pages with the new code (identical apart from run-local fields and runner_sha256,
updated as in #242); sim-capacity's frozen replay_compare source re-pinned with a dated limitation (AST check: the
8 definitions sim-capacity uses are unchanged).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ort's real observation hook

Independent verification of cad7faf found #221 finding 1 unresolved in production: reconcile_fills persisted recovered
fills only through a port.controller attribute that no real Alpaca port has (AlpacaPaperTransport and the native-faults
FaultTransport are wired with sink_observation=controller.observe); only the test-only SimulatedPort carried it.
- native_adapter.py: persist through the port's sink_observation (the hook the transport already uses for stream rows),
  after validating that the recovered activities tile the fill contiguously (fail closed otherwise).
- simulation.py: SimulatedPort exposes the same sink_observation hook, so tests and production share one path.
- Tests: a real-shaped port (no .controller) regression that failed on cad7faf with Decimal('1') != Decimal('3'), restart
  persistence, incomplete tiling; the two accounting-replay guards in safety.py; fast build_decisions-level tests for
  the cancel-before-submit refusal and its equality boundary.
Implemented by a GPT-6 implementer test-first; 514 tests across the five affected modules pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
… accounting replay

The accounting replay summed a status delta's executions into one notional
before computing P&L, so a sell filled at a gain and a loss inside one delta
netted the loss away, and the result depended on whether the stream or the
REST snapshot arrived first. Replay now books each execution that tiles the
delta separately (QuantConnect/Lean 985ef30: SecurityPortfolioModel.cs per-fill
closing P&L; TradeStatistics.cs accumulates losses separately), falling back to
the delta notional only while executions are incomplete.

Two regressions: mixed-profit gross loss across stream-first, REST-first,
duplicate and restart deliveries, and basis preservation at each booking.
With the fix reverted, 4 cases fail (rest_first, duplicates, restart, basis).
Paper suites (safety, runner, native, transport): 407 tests OK.
Implemented by GPT-6 (gpt-6-astra, effort max) test-first; verified by the
coordinator. Pins: source-hashes.json safety.py and manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Conflicts came from #350's squash: this branch already held its commits.
tests/test_adaptive_paper_safety.py keeps this branch's side. source-hashes.json
and manifests/evidence.json take main's side, then re-pin only this PR's files.
Git's automatic merge of safety.py re-inserted the pre-fix
_reconcile_execution_accounting after the fixed one, and Python would bind that
later definition. safety.py is restored from c81a3d5 (main's copy equals its
parent c1539cd), so the delta against main is exactly the reviewed
c1539cd..c81a3d5 change. Paper suites: 407 OK; validate.py passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@seathatflowsinourveins seathatflowsinourveins added the lane:trading Trading lane: sim, paper and live north star label Sep 26, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

Independent read-only Claude review of c81a3d5 (evidence-reviewer, Opus): needs_changes.

  • Major (pre-existing, not a regression): replay books fills in observation order, not execution order. A buy interleaved with a multi-fill sell in one symbol can still make gross loss depend on delivery order.
  • Minor: adopted open orders never reach the per-fill replay; the replay marker has no version, so ledgers replayed before this fix keep the netted loss; the uncovered fallback is untested.
    The reviewer confirmed no side effects on net P&L, cash or cost, and that the three changed pins match the committed blobs.
    Repair round in progress: execution-order replay, the versioned marker and the fallback test, all test-first. The adopted-order coverage is handled with the startup-fill durability check. This PR won't merge before that round is verified.

…on order, versioned marker

Independent review of c81a3d5 (needs_changes):
- Finding 1 (major): the replay booked fills in observation order. Stream fills
  now carry Alpaca's own trade_updates timestamp (event_at_ns; FILL recovery
  forwards transaction_time_ns), the ledger keeps it in a new optional
  executions.at_ns column (added on open for existing ledgers), and replay
  fills each symbol's timed execution slots in execution order, tie-broken by
  event id then cumulative quantity. Adoptions, untimed executions and
  uncovered provisional bookings keep their journal positions.
- Finding 3: the marker is execution_accounting:v2:<client_id>, so ledgers
  replayed under the observation-order rule re-derive once.
- Finding 4: a test for an uncovered REST-only booking beside another order's
  late executions.
- Finding 2 (adopted open orders) is covered by #356's startup persistence.

The A/B/C case gives cash 70, realized -15, gross loss 25, and 1 share at 115
under stream, REST-ahead, recovered, late-buy and nanosecond deliveries.
Reverting the three production files fails 10 cases (4 new tests, 2 strengthened).
Paper suites: 411 OK; trading gates 26 OK; validate.py passed.
Implemented by GPT-6 (gpt-6-astra, effort max) test-first; verified by the
coordinator. Pins: source-hashes.json (safety, transport, native_adapter) and
manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

Repair round pushed as cc6d7cb.

The reviewer's A/B/C example (hold 2 at $100; sell 1 at $110, buy 1 at $130, sell 1 at $90) now gives the same result under every delivery order tested: cash 70, realized −15, loss 25, and 1 share at $115.
Coordinator checks: 411 tests OK. Reverting the 3 production files fails 10 cases (the 4 new tests and 2 strengthened ones). Trading gates: 26 OK. validate.py passed. The same independent reviewer is re-checking the delta 2e29a0cb..cc6d7cba.

Scout and others added 3 commits September 26, 2026 13:43
source-hashes.json also pins test files; CI's
test_adaptive_paper_source_hashes_manifest caught the stale entries. Pins only,
no code change: the changed tests' entries and their manifests/evidence.json
registration.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The re-check of cc6d7cb found two new majors in the execution-order replay:
- Moving an earlier-executed fill into another order's slot can make an
  uncovered REST-only sell go short. The replay then raises, the fill rolls
  back and the transport freezes, although the real history is consistent.
- Removing the early exit let that failure block fills in every symbol and
  added a full replay to every fill.
Under the bounded review loop (one review, one repair, then land with
residuals), this commit lands the parts both reviews confirmed.

It keeps:
- c81a3d5's per-execution P&L and gross loss within a delta;
- the versioned execution_accounting:v2 marker (F3);
- the uncovered-fallback test (F4), with its oracle stated for
  observation-order replay.

It reverts:
- the slot-substitution replay and the early-exit removal;
- the executions.at_ns column, which also avoids the unversioned schema
  change (re-check F3);
- the stream and recovery timestamp plumbing.

Execution-time ordering is a recorded follow-up, with the reviewer's
constraints: never reorder into a short (fall back to observation order), a
targeted replay trigger, and a schema version bump.

Revert checks: the v1 marker fails the F3 test; skipping the uncovered
fallback errors the F4 test. Paper suites: 409 OK; source-hashes and trading
gates: 33 OK; validate.py passed. Pins: safety.py and its test file only.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

# Conflicts:
#	manifests/evidence.json
@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

Re-check of cc6d7cb: needs_changes. The execution-order replay added two majors:

  • Moving an earlier-executed fill into another order's slot can make an uncovered REST-only sell go short. The replay then raises, the fill rolls back and the transport freezes, although the real history is consistent.
  • Removing the early exit spread that failure to every symbol and added a full replay to every fill.

Under the bounded review loop (one review, one repair, then land with residuals), e8a382a reduces this PR to the reviewed-safe subset:

  • Kept: c81a3d5's per-execution P&L and gross loss within a delta, the versioned execution_accounting:v2 marker (F3), and the uncovered-fallback test (F4), with its oracle stated for observation-order replay.
  • Reverted: the slot-substitution replay, the early-exit removal, the executions.at_ns column (which also removes the unversioned schema change) and the timestamp plumbing.

0fb2ef2 merges main (#351, #356). The code delta against main equals the reviewed reduced delta, with an empty diff of diffs, and there is no duplicated method.

Checks:

  • Paper suites: 416 OK. Source-hashes plus trading gates: 33 OK. validate.py passed.
  • Revert checks: the v1 marker fails the F3 test, and skipping the fallback errors the F4 test.

Residuals for a follow-up PR:

  1. Execution-order replay. The reviewer's constraints: never reorder into a short, so fall back to observation order per symbol; replay only when the triggering order was provisional or has an execution earlier than an already-booked timed fill; bump the schema version when at_ns returns.
  2. Stale marker at open (review F5): re-derive at open when a v1 marker lacks a v2 counterpart. Today it happens on the next fill.
  3. Paper engine: persist startup FillReports through the observation hook (closes #350's unverified startup-reconciliation boundary) #356 review item 2: once timestamps return, send event_at_ns on the startup path.

The reviewer is confirming the reduction.

@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

The reviewer confirmed the reduction at 0fb2ef2: VERDICT: accept. It checked:

  • the diff of diffs (286 lines each);
  • production is c81a3d5 plus the v2 key, with the early exit restored and no at_ns;
  • all 57 source-hashes.json pins and 187 evidence.json entries;
  • the F4 observation-order oracle, traced in memory: Y is 70 / −20 / 20, and the full ledger ends with 1 share at $110.

The reviewer's verification gap is closed. Only test ledgers ever ran the reverted at_ns code: two temporary pre.sqlite3 files under the coordinator's private test TMPDIR. A scan of the state directories finds no persistent paper ledger modified after 17:30Z, and no process runs from the worktree.

Residuals, updated with the reviewer's notes:

  • F3 wording: with the early exit back, the v2 key re-derives only when the netted order itself gets another fill observation. For retained ledgers, the F5 residual (re-derive at open) is what actually closes F3.
  • F4 test (optional): in this fixture the provisional state equals the replay result, so the numbers alone don't show that the replay ran. The continue mutation pins it. A stronger fixture would use prices where splitting per fill changes the loss.
  • Execution-order replay: the follow-up keeps the constraints already recorded above.

@seathatflowsinourveins
seathatflowsinourveins merged commit 460034e into main Sep 26, 2026
24 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/paper-realized-loss-order-20260926 branch September 26, 2026 18:18
seathatflowsinourveins added a commit that referenced this pull request Sep 26, 2026
…351, #355, #356) (#361)

- paper lane: the cross-family engine fixes (#350), startup-fill durability
  (#356) and per-execution loss booking (#355) are merged. The 1x ladder
  stays blocked on the paper keys (HTTP 401). Flat.
- mover-early-entry-studies gate: v3 round 17 is GPT-6 ready_to_freeze
  (#351) but not frozen; the dry run needs data keys. evidence_ref now
  points at the v3 README.

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
seathatflowsinourveins added a commit that referenced this pull request Sep 29, 2026
…a short (#355 follow-up) — Claude review pending (#362)

* Paper engine: replay fills in execution order per symbol, never into a short (#355 follow-up)

Implements the #355 reviewers' constraints for execution-time replay:
- Stream fills carry Alpaca's top-level trade_updates timestamp, and FILL
  recovery and startup persistence carry transaction_time. All are stored
  losslessly as executions.execution_time_ns (integer ns). The ledger
  schema_version goes to 2, so a previous release refuses to open a migrated
  ledger, and the execution insert names its columns.
- Per-symbol replay: timed fills are substituted only into timed journal
  slots; adoptions, untimed fills and uncovered provisional bookings keep
  their positions. If the time-ordered pass would go short, that symbol
  falls back to observation order, which is always feasible. It raises only
  if the observation pass itself fails.
- Targeted trigger: replay only when the triggering order was provisional or
  has an execution earlier than an already-booked timed fill of the same
  symbol. Money is kept per symbol, so one symbol's journal cannot block
  another symbol's fills.
- Open-time re-derivation when the accounting rule marker is not current,
  so _begin_trial sees corrected loss before any new fill.

Tests (all fail on 460034e where new):
- A/B/C gives cash 70, realized -15, loss 25, 1 @ 115 under five deliveries.
- B2 is identical in both arrival orders (flat, cash 75, realized -25,
  loss 35), with no rollback and no freeze.
- An unreplayable journal cannot block another symbol's fill.
- Stream, recovery and startup paths carry exact ns.
- The schema migration is refused by the previous open check.
- Old markers re-derive at open.
Reverting the three production files gives 18 failures and 1 error.
Paper suites: 425 OK; source-hashes and trading gates: 33 OK; validate.py
passed.

Implemented by GPT-6 (gpt-6-astra, effort max) test-first, verified by the
coordinator. The independent Claude review is pending (Claude session limit
until 22:10Z).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* Paper engine: never backfill times onto untimed executions; isolate unreplayable symbols at open (#362 review repair)

Repairs the independent Claude review of 20a3296 (1 major, 3 minor).
- major: _record_execution no longer backfills execution_time_ns onto an
  existing untimed row. Untimed executions keep NULL times and their journal
  positions, and a timestamped re-delivery is a no-op, so booked money and the
  loss-cap halt no longer depend on delivery order (startup, recovery, stream
  and migrated ledgers).
- minor: a marker subtest where the rule key is present but out of date.
- minor: re-derivation at open isolates an unreplayable symbol (both replay
  SafetyErrors), keeps its booked money and records the symbol and reason.
  Corrupted legacy symbols without a money checkpoint still fail atomically.
- minor: the RFC 3339 parser accepts a lowercase z and truncates fractions
  beyond nanoseconds.
8 new regression methods fail on 20a3296 and pass here. The paper suites (435)
and the hash and gate tests (45) pass with TMPDIR outside the worktree, and
validate.py passes. Implemented test-first by GPT-6 (gpt-6-astra, max); the
coordinator verified it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_012qwXNtFyUhY5x2dtrm7tG5

---------

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:trading Trading lane: sim, paper and live north star

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant