Skip to content

Order-throughput harness: user-gated live capacity mode (never runs without the user's live key and go file) - #191

Closed
seathatflowsinourveins wants to merge 4 commits into
mainfrom
claude/live-capacity-mode-20260924
Closed

seathatflowsinourveins wants to merge 4 commits into
mainfrom
claude/live-capacity-mode-20260924

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator

What

capacity.py live adds a live capacity mode to the #165 order-throughput harness, so the Alpaca Elite live limit (1,000 calls/min) can be measured once the user supplies a live key and an explicit go.

Gates (every one refuses before any request):

  • a 0600 go file ~/.config/codex-ecosystem/secrets/alpaca-live-go.json with an expiry of at most 24 h and its own caps;
  • an alpaca-live-<label>.env credential name (paper names refused both ways);
  • the --i-understand-live-orders flag;
  • regular trading hours only;
  • zero open orders at start;
  • both STOP files absent.

Probes:

  • SPY/QQQ/IWM/DIA only, qty 1, DAY limit buys at least 500 bps below a bid quote that is at most 10 s old, checked again at the wire;
  • at most $1,000 per order and $2,000 open;
  • budget = floor(min(cap, observed limit) × 0.9), with headroom fixed at 0.9.
  • Any fill or position change stops the run without selling (needs_attention).
  • SIGINT/TERM/HUP/QUIT all run cleanup.
  • A host clock more than 1 s off the broker clock refuses the run.
  • A 429 on the position read feeds the rate governor before the freeze.
  • Journals carry the mode and are refused across modes. Journal reads are guarded (regular file only, 64 MiB cap).

Review trail

  1. f4d505a: live mode.
  2. 29c5189: five live-safety minors (symbol allowlist and quote age, signals, fill precedence, journal mode, headroom). Independent review: merge-ready, no blockers.
  3. 9b7fee3: that review's five minors (quote age at the wire, SIGHUP exit code, clock skew, 429 on the position read, journal guards). Each new test fails on 29c5189: 8 failures and 5 errors on the base.
  4. Merge of main (Native paper capacity evidence for the order-throughput harness (180 actions/min passed) + --stream-timeout #184): the README evidence rows are combined. Paper config_from_args now applies the paper default cap of 200 itself, and a fixture order id is no longer UUID-shaped, which publication validation flagged.

Evidence

  • python3 -m unittest tests.test_order_throughput: 122 OK (2 SDK tests skipped without alpaca-py). With the pinned alpaca-py 0.44.0 venv: 122 OK.
  • Full python3 -m unittest: 3,993 OK (421 skipped).
  • scripts/validate.py and evidence_manifest.py --check: passed.
  • capacity.py offline: capacity criteria met (evidence class offline_fixture, never a pass).
  • Guarded gitleaks on origin/main..HEAD: no leaks.
  • Live mode has never run against Alpaca: there is no live key, no go file, and no network in these tests.

Not authorized by this PR

Any live order. A live run needs the user's live key and a go file written by the user.

🤖 Generated with Claude Code

capacity.py live reuses the paper engine (run loop, rate governor,
trade_updates observation, cleanup, journal, reconciliation) against
https://api.alpaca.markets. Execution-capacity probes only: DAY limit BUY
qty 1 at least 500 bps below the bid, cancelled individually; never
strategy trades.

Gates before any request, with recorded refusal reasons, before the live
credentials are read: --i-understand-live-orders; the user's go file
(regular file, owner-only, mode 0600, go true, at most 24 h validity,
unexpired; read, never written); an alpaca-live-<label>.env name (paper env
files refused in live, live env files refused by paper/recover/audit);
live config (band >= 500 bps, no cancel-all, zero acknowledged open
orders); regular trading hours from the session calendar; the host and
alpaca-live STOP files. Then: no paper base URL in the live env file, zero
open orders at preflight and an open broker clock.

Caps are min(CLI, go file, live ceilings): per-order <= 1000 USD, open
<= 2000 USD, total submits+cancels <= max_order_actions with 6 cancels
reserved per open probe so cleanup always fits. Any fill or position change
(stream, periodic governed position read) stops submissions, cancels this
run's probes and never sells; the run ends needs_attention with the fill
quantity and price. The go file is re-read every 10 s; a revoked or changed
go stops submissions. Receipt: evidence_class native_live, go file sha256,
caps used, no account id or balance.

Paper and offline behaviour is unchanged: CapacityConfig.validate stays
paper-only, the paper engine refuses a live-origin port, all 68 existing
tests pass unchanged, and offline/paper-refusal receipts are identical to
the base commit apart from provenance and the random prefix. The live port
was not run against Alpaca.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…edence, journal mode, headroom

Review findings on f4d505a, each with a test that fails on that commit:

1. Live probes use SPY, QQQ, IWM or DIA only (config gate
   live_symbol_not_allowed before any request; the live port refuses any
   other symbol at construction and at submit). A probe is priced only from
   a quote at most 10 s old: live max_quote_age_seconds <= 10 and a refresh
   at least every 5 s are enforced; once the price's quote is older than
   10 s, submissions hold and the refresh repeats at most once a second; a
   stale refresh is refused and counted, and the first fresh quote reprices
   and resumes submissions. The REST worker re-checks the age immediately
   before each submit. Probes stay >= 500 bps below that fresh bid.
2. SIGHUP (closed terminal or WSL session) and SIGQUIT now take the same
   request_stop path as SIGINT/SIGTERM, in every command
   (install_stop_signals), so cleanup, reconciliation, the receipt and the
   journal end record still run.
3. In live, any fill or position change ends needs_attention even when a
   port error was recorded (error_type stays in the receipt). live.fills has
   probe_fill, other_order_fill (symbol, side, quantity, average price from
   the stream and the listing; never ids) and position_change entries.
4. Journal start records carry mode and trading_origin. Paper recover/audit
   read the journal before any credential and refuse a live run's journal
   (also one without the new fields, judged by config.base_url); the engine's
   recover checks it too. A run refuses any file at its journal path before
   any request, naming another mode's journal.
5. Live headroom is held at 0.9 (live_headroom_must_be_0_9), so the budget is
   floor(min(--cap, observed limit) * 0.9).

Also: the periodic position check now fails closed on any failed read, which
makes its docstring true (the native port raises on 429/5xx; there is no
retry), and the README evidence table no longer contradicts itself about
alpaca-py 0.44.0: SDK objects are built under the pinned venv, while the
order request, the SDK-client calls and a running stream are not exercised.

Offline receipts are unchanged apart from provenance and the random prefix.
No broker, network or credential was used.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1. Quote age at the wire: LiveCapacityRun passes the price's quote wall time
   to the live port, whose final submit boundary (_before_request) re-checks
   age <= 10 s next to the STOP files and go expiry, after pool.get() and
   immediately before the POST. A refusal is not sent and is counted
   (live.quote_freshness.stale_at_wire).
2. SIGHUP exit code: a summary print that fails with OSError (EIO on a closed
   terminal) no longer raises; stdout's descriptor is pointed at /dev/null so
   the interpreter's final flush cannot turn the status into 120, and main
   returns the run's own exit code.
3. Clock skew: the live preflight brackets GET /v2/clock with host wall times
   and refuses (host_clock_skew) when |host - broker| plus half the round trip
   exceeds 1 s; an unreadable timestamp refuses (broker_clock_unreadable). The
   measurement is in live.host_clock.
4. 429 on the position read: the native port's positions() raises ReadFailed
   carrying its sanitized Response; the periodic check and _sync feed it to
   the rate governor before freezing, so cleanup cancels honour Retry-After.
5. Journal guards: read_journal reuses the lstat/S_ISREG/64 MiB guard of
   existing_journal_refusal, opens with O_NOFOLLOW|O_NONBLOCK, re-checks the
   opened inode and reads at most 64 MiB, so recover/audit refuse a FIFO,
   symlink or oversized journal (journal_not_regular_file, journal_too_large).

Offline tests only (fake broker, stub SDK client, temp files); no network,
broker call or credential read.

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

# Conflicts:
#	blueprints/us-equities/order-throughput/README.md
#	tests/test_order_throughput.py
@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.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

CI note (2026-09-25). validate-macos became a required check on main today (#226 and ruleset 23739774). This PR's latest CI run predates that change, so it has no validate-macos status, and GitHub shows the check as expected and waiting.

Nothing in the PR needs to change. The next push, or a close and reopen, runs the current adoption-bootstrap.yml, which reports validate-macos on every pull request.

🤖 Generated with Claude Code

@seathatflowsinourveins seathatflowsinourveins added the lane:trading Trading lane: sim, paper and live north star label Sep 25, 2026
@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

Closing: live-trading modes are out of scope for this repository. The owner's standing rule is that live is handled elsewhere and this repo resolves the paper lane only; see docs/paper-lane-policy.md and the lane split in docs/lanes.md. The branch is kept, so the work can be reopened here or carried to the private trading repository. Part of the 2026-09-25 noise cleanup approved by the repository owner; the trading-lane session acknowledged.

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