feat(paper): synthetic account + Rail 11 drawdown enforcement + sizing fix - #136
Merged
Conversation
…ion_usd) Adds the paper-forward account model config block ahead of paper-mode fidelity wiring: a fallback equity seed (0 = no fallback, primary seed is live mark-to-market equity) plus an optional monthly contribution. No behavior wired yet -- config field, parser, templates, and golden fixtures only.
…toff Gives PaperTrader a persisted synthetic cash balance (paper_cash_usdc), a funding check in _enter that rejects a fill when cash is insufficient, an equity() method built on a new shared keel.execution.equity.mark_positions helper (cash + mark-to-market positions, cost-basis fallback on stale/missing prices), and an epoch cutoff (paper_ledger_start_ts) so rehydration ignores legacy pre-epoch orders written before the synthetic account existed.
…ual fill cost Fix 1: add costed:bool to _OpenPaperPosition so the "was this position debited?" decision is recorded at open time and read back (not re-evaluated) at close and in equity(). Without this, a position opened while cash was unseeded, then seeded before it closed, credited cash with no matching debit and inflated equity() by marking an uncosted position -- both manufactured equity from nothing. Rehydrated positions are always post-epoch (the ledger-start cutoff already excludes anything earlier), so they're marked costed=True. Fix 2: the funding check in _enter now gates on the actual debit (entry_fill*qty + fee) instead of the coarser intent notional (entry*qty), so cash can no longer go negative for a seed strictly between the two -- per spec Sec 4.2's stated purpose for the check.
…lars in paper Wires the synthetic paper account into run_once's equity block so Rail 11's drawdown scalars advance during paper trading instead of being hard-set to None. Adds _seed_paper_account_if_needed: stamps/clears the shared equity_high_water_mark/drawdown_total_pct/drawdown_weekly_pct/equity_history keys on a paper<->live mode flip, then seeds paper_cash_usdc once from real mark-to-market equity, falling back to config.paper.starting_equity_usd. The symmetric live-side stamp/clear only fires right before a successful update_drawdown call, so an unreadable broker still leaves the previous cycle's scalars untouched (test_run_once_skips_the_drawdown_update_when_the_ quote_balance_is_unreadable is unaffected).
_paper_enter now takes paper_equity and passes it as _build_intent's equity_override, then fills the trader with intent.qty instead of a fixed 1 unit. run_once captures the paper branch's equity_now into a paper_equity local and skips paper entries for the cycle (logged) when it is None, rather than sizing off an unknown equity.
…-rebased) Adds an optional recurring deposit to the synthetic paper account: applied once per UTC calendar month (guards._utc_month_bounds), tracked via new state key paper_last_contribution_month, and rebased through equity.record_external_flow so the deposit is never misread as a drawdown recovery. Default is 0 (disabled).
Adds the P4 Task 8 acceptance tests: guards-level test_paper_drawdown_halt_vetoes_buys / test_paper_weekly_drawdown_halt_vetoes_buys (offline=True, equity_state_mode=paper) and a full-loop test that drives a real drawdown through run_once (seed cash, open a paper position, mark it far down) and proves the next paper entry attempt is vetoed by account_dd_breaker_total -- confirming Tasks 5-7's wiring end-to-end.
…/logs Add optional paper_equity/drawdown_total_pct/drawdown_weekly_pct fields to LoopResult (None-defaulted, so existing constructions still compile), populate them from repo state right after update_drawdown in the paper branch of run_once, log them via agent.paper_equity, and print them in _print_loop_result -- the paper-forward observability for Rail 11's drawdown scalars (a dedicated `keel status` command is deferred).
…rred) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ring - C1 (critical): _load_open_positions used paper_ledger_start_ts (wall-clock now_ts at seed) against an order's BAR timestamp, which always predates wall-clock time -- dropping any position opened during the seeding cycle on the next rehydration while its cash debit persisted. Switched the epoch cutoff to a new paper_ledger_start_order_id (max paper order id at first seed), stamped once by seed_cash and compared by id, not by clock. - I1: added a loop-level test proving the drawdown breaker vetoes a paper ENTER through the real run_once path, not only via a direct _paper_enter call. - Item 7: clarified _paper_enter's no-fill reason string to cover both the already-open and insufficient-synthetic-cash cases. - Item 5: documented (comment only) the known pre-live-arming asymmetry in the live-side mode stamp/clear; no behaviour change. Co-Authored-By: Claude Opus 4.8 <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.
Paper-mode fidelity: synthetic account + Rail 11 enforcement + sizing fix
Makes paper trading a faithful rehearsal of live so the paper-forward produces a trustworthy out-of-sample track record for the promotion gate.
Spec:
docs/superpowers/specs/2026-07-23-paper-mode-fidelity-design.mdPlan:
docs/superpowers/plans/2026-07-23-paper-mode-fidelity.mdWhat & why
Two coupled defects in the paper path, fixed together (Rail 11 needs an equity denominator, which needs real sizing, which needs per-position qty):
equity_now = Nonein paper mode, soupdate_drawdownnever ran and the breaker read a frozen 0. A catastrophic drawdown could run a paper strategy into the ground uncapped, polluting the OOS record.How
A synthetic paper account (cash + qty-bearing positions, persisted in
agent_state) is seeded once from real broker mark-to-market equity (fallbackpaper.starting_equity_usd), marked to market each cycle, and fed into the existingequity.update_drawdownproducer — which writes the same global scalarsguards.py's Rail 11 already reads.guards.pyand the DB schema are unchanged. Fills are sized off account equity via_build_intent(equity_override=...)(live path unchanged). Mode-flip clears the shared HWM so a synthetic HWM can't poison live equity. Halt = veto new buys; open positions ride to their stops (mirrors live).Design decisions
$5k max_exposureproxy.LoopResultfields +_print_loop_result+ anagent.paper_equitylog event (a dedicatedkeel statuscommand is deferred).Testing
1447 tests pass,
ruff checkclean. Includes an end-to-end acceptance test: a paper account driven to −20% / −8% through the realrun_onceloop gets its buys vetoed by Rail 11.Process
Built subagent-driven, 10 TDD tasks, each spec+quality reviewed. The final whole-branch review found and fixed a Critical the per-task reviews missed (an epoch cutoff that mixed bar-time and wall-clock, dropping first-cycle positions on rehydration and desyncing cash) — resolved with an id-based epoch and independently re-verified.
Deferred follow-ups (non-blocking)
== "paper"and hoist before the broker read before arming live. TODO left inagent.py.$5k-proxy sizing to use real equity; unifySimAccountand the paper account.paper_ledger_start_tsfield, a few test-hygiene nits.🤖 Generated with Claude Code