Reconciliation

Reconciliation, in a trading system, is the periodic proof that your own record of your orders, fills, positions and cash still matches what the venue itself says. It is necessary because a live book is built from messages: a message can be lost, arrive twice, or arrive while your process was down, and every one of those leaves a book that is quietly wrong in a way nothing else will tell you about.

Vike Trader runs it as a first-class engine rather than as a startup nicety. On a schedule — and, for the venues wired for it, immediately on a reconnect — it fetches each venue's own order, fill and position reports, diffs them against local state, classifies every disagreement into one of 9 named kinds, and folds or holds each one under an explicit policy you chose. The diff and the resolution are pure functions: no input, output or clock, which is what makes them testable against golden fixtures and what lets them run on the same thread as the state they read.

Why a live book needs proving

Everything an execution core knows about your account arrived as a message over a socket. That is fine until it is not:

  • A fill executes while your process is restarting, so the order is still working locally and the position is a fill behind.
  • A websocket drops mid-session and reconnects; the gap is exactly the messages you needed.
  • Somebody closes a position from the venue's own web interface, and nothing tells your process.
  • An order ends — filled, cancelled, expired — and the terminal message never lands, so your core holds a working order that no longer exists.

None of those is exotic and none of them produces an error. They produce a book that looks fine and is wrong, and a strategy sizing off that book will keep making decisions from it. Reconciliation is the only mechanism that finds them, because it is the only one that asks the venue rather than trusting the stream.

What one pass does

Three fetches are required of every venue's report client — order status, fills, and position status. Two more are optional and default to answering "nothing", so a venue that has not wired them stays inert rather than failing the pass: the venue's absolute cash balance, and the account-actual maker and taker fee schedule. A venue that reports no balance still has its orders, fills and positions diffed.

Where that work runs is the design decision worth stating. The manager runs on its own thread and does only the blocking network fetch — it must never touch the latency-gated fold. It then enqueues the raw reports as a command; the fold thread reads its own view of local state, runs the pure diff and the pure resolution there, and folds each synthesized event through the same publish path a real venue event takes. Diff and resolve therefore sit on the same thread as the state they read: no cross-thread snapshot, and no window in which the state moved underneath the comparison.

Three cadences share one pass — a startup pass after a configurable delay, an on-demand pass whenever a bridge pokes the reconnect trigger, and a fixed interval — which makes idempotency load-bearing rather than convenient. Fills are deduplicated by the venue's own trade id against the ids the engine has already seen, so a pass that finds nothing new folds nothing new. A fourth arm runs no reconcile pass at all and instead pokes the core's stuck-order watchdog on its own cadence.

The pass deliberately avoids the snapshot path. Applying an all-empty-except-balance snapshot through the engine's ordinary snapshot arm would trip its order reap, which terminalizes any live local order absent from the snapshot — cancelling every live order on the venue as a side effect. Balance is seeded directly instead: the narrowest path that updates cash without touching positions or the order registry.

The divergence kinds

A disagreement is never "an error". It is classified, and the classification decides what may be done about it. The origin column is the axis the policies key on: a local-origin divergence is one our own activity explains, an external one is foreign activity nothing local accounts for, and an absence is the weakest evidence there is — a report that omits something is not a venue denying it.

KindWhat it meansOriginResolves to events
Missing fill The venue reports a fill local state has never seen — an execution happened and the message never arrived, or arrived while the process was down. reconciliation-materialized yes
Missing terminal The venue says an order is finished and local state still has it working. The order ended — filled, cancelled or expired — and the ending never reached the core. reconciliation-materialized no
Orphan local order Local state holds a working order the venue does not report. Evidence of an absence, which is the weakest kind there is: a report that omits an order is not a venue denying it. absence no
Unknown order The venue reports an order local state has no record of — another operator's order on the same account, or one of ours from before the local record began. external yes
Position drift The venue and local state disagree about position size. The one no-local-origin kind the hybrid policy folds without an operator, because its evidence is a positive venue row rather than an absence. external yes
Position only external The venue reports a position on an instrument local state holds none of — a position that arrived by a route the local core never saw. external yes
Orphan local position Local state holds a position the venue does not report. The exempt kind: it folds nothing under any policy, and its sweep is gated on the venue reporting at least one position row at all, because an empty report is an outage rather than an assertion that you are flat. absence no
Balance drift Venue cash and the realized-profit-corrected local balance disagree — cash as a diffed dimension rather than a silent authoritative overwrite. external yes
Journal divergence The command journal and the reconciled state disagree. Held before the policy is even consulted: a journal that disagrees with reality is a question for a human. absence no

Auto-applying is not the same as doing something, and the last column is why it is printed. Several kinds are classified as auto-apply under a policy and still resolve to an empty event list — nothing is folded, nothing changes, and naming them in a list of "what this policy folds" would misinform in exactly the direction the classification exists to stop. The platform's own code makes this computable rather than quotable: the fold set is derived from the classification and logged at startup by every binary, because "is my configuration safe?" used to be answerable only by reading the resolver — and two independent reviews of that resolver once reached opposite answers about one kind, one of which had already propagated into six documents and a rollout policy.

The four policies

A policy answers one question per divergence: fold it now, or hold it for an operator? There are 4, selected by one setting, one spelling each and no aliases.

hybrid
The default, and per kind rather than blanket: local-origin kinds fold, no-local-origin kinds are held. Exactly two kinds fold anything under it.
synthesize
Fold everything, no overrides. Every divergence that resolves to events applies immediately.
quarantine
Hold everything. Nothing folds without a person; every divergence surfaces as an alert proposing what it would have done.
external-quarantine
The hybrid preset with every external-origin kind forced to hold — foreign venue activity nothing local explains waits for an operator to claim it. It is computed from the origin tag over the hybrid rows rather than enumerated, so a future external kind is held by construction rather than by somebody remembering to add it.

Here is what each policy does with each kind, straight from the same export the table above renders from:

Kindhybridsynthesizequarantineexternal-quarantine
Missing fill foldfoldholdfold
Missing terminal foldfoldholdfold
Orphan local order holdfoldholdhold
Unknown order holdfoldholdhold
Position drift foldfoldholdhold
Position only external holdfoldholdhold
Orphan local position holdfoldholdhold
Balance drift holdfoldholdhold
Journal divergence foldfoldholdfold

One instance-level refinement exists and only the external-quarantine policy opts into it. A fill the venue echoes with no client order id names no local order — it is a foreign order's fill inside the lookback — so under that policy it is held while fills linked to our own orders keep folding. Every other policy is byte-identical by construction rather than by test, because the refinement is definitionally the plain per-kind answer unless a policy asks for it.

A misspelt policy name gives you more auto-folding, not less. An unrecognized value falls back to the default exactly as an absent one does, so a mis-typed hold-first policy folds more than the operator intended. That is why every binary logs its resolved fold set at startup — at a raised level when the set is non-empty, naming the kinds. It is the check that a spelling took.

Which venues reconcile

10venues wired for live execution and reconciliation
3more behind their own build feature
13reconciled in total, counting the gated ones

The reconcile client is built per venue at mount time and is deliberately not shared with that venue's execution path — a fresh signer and transport dedicated to reconcile reads, so a problem on one side cannot take out the other. Three shapes exist. A shared dispatch covers the venues whose report client is an ordinary keyed REST client. One venue is built together with its execution client, because its report client cannot be constructed until an instrument fetch has resolved the product. And the rest build theirs inline at their own mount, each needing a handshake the shared dispatch cannot produce: a dedicated authenticated order websocket, a bespoke signer, a protobuf socket, a second token lifecycle, a fresh logged-in session.

Several of those inline clients perform a blocking authenticated handshake merely to be constructed, so the mount gates them on the resolved reconcile verdict: with reconciliation off they are never called and the venue does no authenticated network work at mount at all.

A venue mounted paper reconciles nothing, and there are two independent ways to be one — the per-venue arming ceiling, read first, and absent credentials behind it. Both are on automated trading. The per-venue capability detail is on supported venues.

The per-venue health gate

Reconciling against a venue whose feed is currently broken produces divergences that are artefacts of the outage rather than facts about your account. So a health probe is consulted before each venue's leg of every pass — startup, trigger and interval alike — and a degraded venue's leg is deferred.

Two properties make that safe rather than merely cautious. Suppression is per venue: it defers that venue's leg only, sets no pending flag, and never blocks a different healthy venue in the same pass. And a venue with no probe wired reads healthy, which is how the execution-only venues — the ones with no market feed of their own — keep reconciling instead of being blocked by a signal that does not exist for them. The probe itself is fail-soft: only a genuine error state, or a disconnected state that says so, reads as degraded.

What an operator sees

Events the policy auto-applies fold immediately. Everything held becomes an alert carrying its proposed events — the events the resolver would have folded — and confirming one folds them later through the same publish path, byte-identically to what an immediate fold would have produced. That equality is not an implementation detail: it is what makes "hold it for me to look at" a safe default rather than a different code path with its own bugs. The resolver computes a divergence's events before consulting the policy, which is why the held alert can propose exactly what folding would have done.

A held divergence is a state, not an event, and it took a live incident to make that distinction. Under a hold-everything policy a divergence that nothing heals is re-raised every interval forever, and a live box logged hundreds of identical warnings a day — every one of them the same external-position divergence. The log now announces transitions: one line when a divergence enters the held set, one when it leaves, with a much slower periodic summary so a standing backlog stays visible without being repeated at pass cadence. The held store has the same shape — a re-raise refreshes its row in place — so it is bounded by distinct divergence identities rather than by uptime.

Adoption of an unknown venue order is off by default and deliberately narrow when armed: only a terminal unknown order whose fills fell outside the lookback synthesizes anything that folds, so a live or fill-lane-covered unknown order can never book the same activity twice.

Rolling out a newly wired venue: start it on quarantine. Position drift is the one no-local-origin kind the default policy folds with no operator in front of it, and the fold is destructive — it rewrites local position size onto the venue's number and books realized profit at the venue's average price. What an unproven position fetch costs you is a row that arrives and is wrong, not one that never arrives: a row the venue simply omits raises the exempt orphan kind instead, which folds nothing under any policy, precisely because most position fetches are symbol-scoped and "no row" routinely means "not asked about" rather than "flat".

Run it yourself.

Every venue mounts paper by default. Nothing below trades anything until you say so.

Free. Source-available under FSL-1.1; every release becomes Apache-2.0 two years on.

Trading involves substantial risk of loss and is not suitable for every investor. Nothing on this page is investment, financial or trading advice, and no result shown or described — backtested, simulated, paper or live — is a promise of future performance. Backtested and simulated results in particular have inherent limitations: they are produced with hindsight, do not reflect every real-market condition, and no representation is made that any account will achieve results similar to them. Vike Trader is software; orders sent from your accounts, the credentials that authorize them and the decisions behind them remain yours. Consider your circumstances, and never trade money you cannot afford to lose.