You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A definite TradeScopeDenied on a bracket leg is booked state-unknown, parking exits on a phantom pending row #945
Found in the end-to-end review of the sell-side build (e97141e..origin/main, #889–#939): reviewer suggestion S-6, one of the rulings the reviewer would overturn (R5's unknown-state bucket), accepted by the operator.
Problem
executor.place_bracket runs after the entry has already filled, so it must never raise (#799, plan R5). Its recovery books the failure by STAGE:
raise before the bracket's orders row exists → unbracketed: retry record, the sweep retries next cycle;
raise after it exists (place_order threw) → the pending row stays, the retry record is CLEARED, and a CRITICAL executor.bracket_state_unknown parks the product's exits until a human reconciles the row -- because the venue MIGHT be holding the order, and a retry could double-commit the base.
TradeScopeDenied lands in the second bucket today (executor.py, the broad except Exception; the docstring even says it is included on purpose). But a permissions refusal is a DEFINITE answer: the venue did not take the order. Nothing rests at the exchange. The position is left unprotected behind a phantom pending row that only a human can clear, when the honest record is "rejected, retry when you can".
The row status already exists: _run_order writes rejected for a broker-rejected placement (PlaceResult(success=False)), and reconcile's own docstring says "a broker rejection writes rejected" -- a permissions refusal is the same fact.
Expected
TradeScopeDenied on the bracket leg: the written row is marked rejected, the unbracketed: retry record is ARMED (not cleared), a CRITICAL names the refusal and says the position is unprotected -- and the next cycle's reconcile_unbracketed_positions sweep retries instead of waiting for a human.
Any other raise after the row exists (timeout, network) keeps today's state-unknown handling unchanged -- there the venue really may be holding the order.
executor.place_bracket: dedicated except TradeScopeDenied ahead of the broad handler; new executor.bracket_refused CRITICAL; docstring bullet.
tests/execution/test_bracket_downgrade.py: split the parametrized place-raise test (timeout keeps state-unknown); new test pins rejected-row + armed retry + no unknown event + the sweep healing with a working broker.
Fixed in PR #948 (merged to main): R80 a TradeScopeDenied on a bracket leg marks the row rejected, arms the unbracketed: retry, and the sweep heals; ambiguous raises keep R5's state-unknown. Ships in 0.21.0.
Found in the end-to-end review of the sell-side build (
e97141e..origin/main, #889–#939): reviewer suggestion S-6, one of the rulings the reviewer would overturn (R5's unknown-state bucket), accepted by the operator.Problem
executor.place_bracketruns after the entry has already filled, so it must never raise (#799, plan R5). Its recovery books the failure by STAGE:ordersrow exists →unbracketed:retry record, the sweep retries next cycle;place_orderthrew) → thependingrow stays, the retry record is CLEARED, and a CRITICALexecutor.bracket_state_unknownparks the product's exits until a human reconciles the row -- because the venue MIGHT be holding the order, and a retry could double-commit the base.TradeScopeDeniedlands in the second bucket today (executor.py, the broadexcept Exception; the docstring even says it is included on purpose). But a permissions refusal is a DEFINITE answer: the venue did not take the order. Nothing rests at the exchange. The position is left unprotected behind a phantompendingrow that only a human can clear, when the honest record is "rejected, retry when you can".The row status already exists:
_run_orderwritesrejectedfor a broker-rejected placement (PlaceResult(success=False)), andreconcile's own docstring says "a broker rejection writesrejected" -- a permissions refusal is the same fact.Expected
TradeScopeDeniedon the bracket leg: the written row is markedrejected, theunbracketed:retry record is ARMED (not cleared), a CRITICAL names the refusal and says the position is unprotected -- and the next cycle'sreconcile_unbracketed_positionssweep retries instead of waiting for a human._run_order, Venue visibility must be capability-based, not key-presence-based — a read-only key looks identical to a working one #233): a placement-path refusal is a credential fact; only its row bookkeeping changes.Fix plan
executor.place_bracket: dedicatedexcept TradeScopeDeniedahead of the broad handler; newexecutor.bracket_refusedCRITICAL; docstring bullet.tests/execution/test_bracket_downgrade.py: split the parametrized place-raise test (timeout keeps state-unknown); new test pins rejected-row + armed retry + no unknown event + the sweep healing with a working broker.