Skip to content

fix(execution): cancel a resting SELL whose position has left the account (#668) - #671

Merged
eaitbrahim merged 1 commit into
mainfrom
fix-668-orphan-bracket
Sep 1, 2026
Merged

fix(execution): cancel a resting SELL whose position has left the account (#668)#671
eaitbrahim merged 1 commit into
mainfrom
fix-668-orphan-bracket

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

Closes #668. Stacked on #670 (targets fix-667-sell-clamp) — the sweep reads executor._held_base, and a second reader of the same venue fact would be two things to keep in agreement.

Why nothing caught this

_clear_resting_bracket fails closed before an exit. reconcile_unbracketed_positions heals a position that has no bracket. Both are position-driven — they start from a tranche and ask what protects it. A protective order whose position has left the account has no tranche to be walked from, so neither can reach it. That is the structural reason the gap existed, not an oversight in either sweep.

sweep_orphan_brackets runs the other way: from keel's own resting SELLs to the account.

How an orphan appears

The shape is not obvious on a venue with native brackets, so it is worth being concrete:

  • the operator sells or transfers the asset at the venue — keel's bracket keeps resting against inventory that left without telling it;
  • scale_out and _roll_stop both cancel before they place — a process dying inside that window can leave a replacement resting over a resolved position;
  • a venue without a native trigger-bracket needs two legs, and a filled target leaves a live stop behind. Not reachable on the venue that trades live today, where BracketGTC is one order — and reachable the moment one is not.

Left alone, the market reverses through the stop and the venue is asked to sell an asset that is not there. Cash account: a rejection. Margin-enabled (#666): a short, produced entirely by a missing cancel.

The venue decides, never the ledger

This is a safety property, not a preference. Cancelling a protective order over a position that really exists strips a live holding of its only stop, so the trigger has to be the account's own statement about itself. keel's ledger can be stale in exactly the dangerous direction — saying zero while the venue holds the position — and a sweep driven from it would cancel precisely the brackets that were working.

An unreadable balance therefore cancels nothing. A venue that omits empty accounts is saying nothing, not saying zero.

Balance.total, not Balance.available, for the same reason as #667's clamp: a resting SELL holds the base it commits, so available reads ~0 for exactly the products this sweep looks at. Reading it would cancel every protective order keel has ever placed, on the first cycle, and call the result a fix. There is a test for that specific inversion.

Dust

Strictly below base_increment counts as nothing held — a residue the venue cannot express as a size is not a position. Exactly one increment is the smallest real position there is and keeps its bracket. With no increment known there is no threshold and none is invented: _base_increment_for returns None for a venue error as much as for an unknown product, and a guessed floor would cancel protective orders over a number nobody supplied.

Where it runs, and why there

Last of the three reconciliation passes. reconcile_unbracketed_positions will place a bracket for a tranche whose position left out of band, so sweeping afterwards makes the cancel the cycle's final word and nothing re-creates within one cycle what it just removed. Running it first would leave a fresh orphan resting for a full day — this deployment cycles once per UTC day.

#667's rail 21 stops that re-place at source, which makes the churn rare rather than the ordering optional: the rail refuses to place into an empty holding, this cancels what already rests, and neither subsumes the other.

What it deliberately does not do

The tranche is left open. Closing it would book a realized outcome at a price nobody observed — what the position was worth when it left the account is a question only an operator can answer. doctor's new bracket.orphan finding and the existing ledger.unbooked_exit both surface it.

It also never raises into the cycle: per-row isolation, an accepted-but-unsettled cancel is not recorded as done, and a refused cancel leaves the local row untouched (a venue that refuses may have already filled it).

Verification

11 mutants, 11 killed — including the wiring (a sweep nothing calls reports nothing) and its position in the cycle, which a presence-only test would miss.

Full suite: 5105 passed, 3 skipped. ruff and mypy clean.

…ount (#668)

`_clear_resting_bracket` fails closed before an exit. `reconcile_unbracketed_
positions` heals a position that has no bracket. Both are POSITION-DRIVEN: they
start from a tranche and ask what protects it. A protective order whose position
has left the account has no tranche to be walked from, so neither can reach it,
and nothing else in the engine looked. That is the structural reason the gap
existed rather than an oversight in either sweep.

`sweep_orphan_brackets` runs the other way -- from keel's own resting SELLs to
the account -- and cancels any whose product the venue no longer holds. Three
ways one appears: the operator sells or transfers the asset at the venue and
keel's bracket keeps resting against inventory that left without telling it;
`scale_out` and `_roll_stop` both cancel before they place, and a process dying
inside that window can leave a replacement resting over a resolved position; and
a venue without a native trigger-bracket needs two legs, where a filled target
leaves a live stop behind -- not reachable on the venue that trades live today,
where `BracketGTC` is one order, and reachable the moment one is not.

Left alone the market reverses through the stop and the venue is asked to sell an
asset that is not there. On a cash account that is a rejection; on a
margin-enabled one (#666) it is a short produced entirely by a missing cancel.

THE VENUE DECIDES, NEVER THE LEDGER, and that asymmetry is a safety property
rather than a preference. Cancelling a protective order over a position that
really exists strips a live holding of its only stop, so the trigger has to be
the account's own statement about itself. keel's ledger can be stale in exactly
the dangerous direction -- saying zero while the venue holds the position -- and
a sweep driven from it would cancel precisely the brackets that were working. An
unreadable balance therefore cancels NOTHING.

`Balance.total`, not `Balance.available`, for the same reason as #667's clamp: a
resting SELL holds the base it commits, so `available` reads ~0 for exactly the
products this sweep looks at. Reading it would cancel every protective order keel
has ever placed, on the first cycle, and call the result a fix.

Dust strictly BELOW the venue's `base_increment` counts as nothing held -- a
residue the venue cannot express as a size is not a position. Exactly one
increment is the smallest real position there is and keeps its bracket. With no
increment known there is no threshold and none is invented: `_base_increment_for`
returns None for a venue error as well as an unknown product, and a guessed floor
would cancel protective orders over a number nobody supplied.

Runs LAST of the three reconciliation passes, and the order is load-bearing:
`reconcile_unbracketed_positions` will PLACE a bracket for a tranche whose
position left out of band, so sweeping afterwards makes the cancel the cycle's
final word and nothing re-creates within one cycle what it just removed. #667's
rail 21 stops that re-place at source, which makes the churn rare rather than the
ordering optional -- the rail refuses to PLACE into an empty holding, this
cancels what already rests, and neither subsumes the other.

The tranche is deliberately left open. Closing it would book a realized outcome
at a price nobody observed; `doctor`'s new `bracket.orphan` and the existing
`ledger.unbooked_exit` both surface it for a human instead.

11 mutants, 11 killed -- including the wiring (a sweep nothing calls reports
nothing) and its POSITION in the cycle, which a presence-only test would miss.

Stacked on #667: the sweep reads `executor._held_base`, and a second reader of
the same venue fact would be two things to keep in agreement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL
@eaitbrahim eaitbrahim self-assigned this Sep 1, 2026
Base automatically changed from fix-667-sell-clamp to main September 1, 2026 11:42
@eaitbrahim
eaitbrahim merged commit 5b91e16 into main Sep 1, 2026
4 checks passed
@eaitbrahim
eaitbrahim deleted the fix-668-orphan-bracket branch September 1, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No sweep for a resting SELL whose position is gone — a filled target leaves the stop live

1 participant