Refusing to Trade a Stale Book: Reconciliation as a Pre-Trade Guard
Jonny Bravo
-Refusing to Trade a Stale Book: Reconciliation as a Pre-Trade Guard
Here's a failure that has blown up real trading desks. Your signal engine believes the portfolio is flat. The exchange, however, holds a large short — maybe someone placed a manual hedge, maybe a prior run partially filled and the rest never completed, maybe a signal went stale and the world moved. Your engine, working off its stale picture, computes a target of "go long" and sends an order sized as if from flat. It isn't from flat. It's from a big short. The order it sends is the wrong size, in the wrong context, and now you've doubled a position you didn't know you had.
Nobody did anything obviously wrong. The bug is that intended state and actual state diverged, and the system traded on intended. In automated execution, that divergence isn't an edge case — it's a Tuesday. Partial fills happen. People touch accounts. Signals lag. The only safe assumption is that what you think you hold and what you actually hold will sometimes disagree, and that trading through the disagreement is how you turn a small problem into a large one.
So before any live run places a real order, the executor reconciles the two and refuses to trade if they've drifted too far apart.
Intended vs. actual, as a first-class check
The reconciliation module is built around exactly this divergence — and it's used in two complementary ways, per its own docstring:
"""B3: position reconciliation — intended (mandate targets) vs actual (broker positions synced to fund_account_positions). Used two ways: * a periodic job / endpoint that records drift to fund_lab_reconciliations, and * a pre-flight guard the executor calls before placing real orders, refusing to trade when the book has drifted too far from what we think we hold (a stale signal, a manual trade, or a prior partial fill)."""
The second use is the safety-critical one. It's not a report you read after the fact — it's a gate the executor passes through before it commits capital. If the book is too far from expectation, the run aborts instead of trading into a state it doesn't understand.
The thresholds are explicit, tunable, and chosen with real-world texture:
# Relative drift (|actual-intended| / |intended|) above which a live run is blocked. RECON_BREACH_PCT = float(os.environ.get("FUND_RECON_BREACH_PCT", "0.10")) # 10% # Ignore tiny absolute positions (dust) so they don't trip the percentage check. RECON_MIN_NOTIONAL = float(os.environ.get("FUND_RECON_MIN_NOTIONAL", "25.0"))
The two-parameter design is the whole craft
A naive drift check uses a single percentage and immediately becomes useless, because of dust. A position that's supposed to be flat but holds $0.50 of leftover base currency is infinite percent drift — |0.50 - 0| / |0| blows up — and a pure percentage guard would block every run over rounding crumbs. You'd disable it within a day.
The fix is the second parameter. RECON_MIN_NOTIONAL says "ignore anything smaller than $25" so dust doesn't trip the percentage check. Now the guard fires on real divergence — a position that's materially different from what you expected — and stays quiet about the inevitable rounding residue of live exchanges. That's the difference between a guard people keep on and a guard people switch off in frustration. The relative threshold catches meaningful drift; the absolute floor suppresses the noise that would otherwise make it unusable.
And both are environment-overridable, because the right tolerance depends on the book. A tight, frequently-rebalanced strategy might want 5%; a coarse one might accept 15%. The default is a sensible 10%, and you tune it to your risk appetite without touching code.
Pure logic, separately testable
There's a structural detail worth calling out, because it's why you can trust the guard: the drift computation is a pure function, separated from all the database I/O.
def compute_drift(intended, actual, threshold_pct=RECON_BREACH_PCT, min_notional=RECON_MIN_NOTIONAL) -> dict: ...
compute_drift takes two dictionaries and returns a verdict — no Supabase, no network, no side effects. That means the exact logic deciding whether to halt a live trade is unit-testable in isolation, with hand-crafted edge cases: dust on both sides, a sign flip, a partial fill, a totally stale book. The I/O wrapper (portfolio_drift) does the Supabase fetch and calls the pure core. You never have to spin up a database to convince yourself the safety logic is correct, which means the safety logic actually gets tested — thoroughly — instead of being the scary untested function everyone's afraid to touch.
Why "refusing to trade" is a feature, not a failure
It feels counterintuitive to brag that your execution engine sometimes won't trade. Isn't the whole point to execute? But "execute no matter what" is exactly the behavior that turns a stale-book moment into a doubled position and a margin call. The valuable behavior is judgment: trade when the world matches your model, and stop when it doesn't.
A platform that reconciles before every live run, refuses to trade through material drift, and records that drift for you to investigate is a platform built by people who've watched automated execution go wrong and decided to engineer against it. For a fund manager handing real capital to an automated system, "it knows when not to act" is the single most reassuring thing the system can do.
The bravest thing an execution engine can do is decline. Ours declines when the book has moved out from under the signal — and tells you why.
Every live run reconciles intended against actual positions and halts on material drift — no surprise doublings. See live execution →