Clearing and the ledger¶
Ledger holds the account balances and settles every fill the engine
produces.
Model¶
Double entry, multi-asset. Every account holds, per asset, an available and a
reserved balance in raw fixed point (1e-8 units) stored as __int128. The
width means a notional cannot overflow, and integer money means there is no
rounding drift to accumulate.
Ledger led;
led.deposit(acct, USD, amountOf(Volume::fromDouble(10'000)));
led.reserve(acct, USD, amt); // available -> reserved (fails if short, no partial)
led.release(acct, USD, amt); // reserved -> available
led.spendReserved(acct, USD, amt); // reserved is paid out on a fill
led.credit(acct, BTC, amt); // signed
led.debit(acct, USD, amt); // all-or-nothing
Attach it to the engine and settlement happens automatically:
Lifecycle of buying power¶
- Order entry reserves. A bid reserves quote (
limitPrice * quantity), an ask reserves base. If the reservation fails the order is rejected; this is the pre-trade buying-power gate. - A fill settles. Base and quote change hands, the over-reservation (price improvement) is released, fees move to the venue account.
- Every exit releases. Cancel, IOC/FOK/MARKET residual, post-only rejection, LULD rejection, GTD expiry, STP, mass-cancel, liquidation, last-look reject: each returns the unspent reservation.
That last point is where venues leak. A reservation stranded in reserved is
invisible to a conservation-of-total check, because the money stayed in the
account; it is simply frozen. The fuzz therefore asserts a second property:
after the book is drained, every account's reserved is zero. See
Verification.
Conservation¶
Assets are only ever exchanged, so each asset's total across all accounts plus the venue account is invariant. The conservation fuzz checks this after every command over a random stream of all order types.
Cash and inventory are conserved exactly. Total mark-to-market equity moves with the last price, which is valuation, not a leak; the multi-agent demo prints both so the difference is visible.
Per-symbol scale¶
The default fixed-point scale (1e8) caps an int64 quantity at about 9.2e10
whole units. That is too small for tokens whose circulating supply runs to
1e14. Such a symbol sets priceScale/qtyScale on its SymbolConfig (and on
configureSymbol for cross margin), and its price and quantity raws are then
interpreted in those units end to end: matching, reservation, settlement,
margin, PnL, funding, fees, and metrics.
Two invariants keep the money model simple:
- Money never changes scale. Every quote amount in the ledger is at the
fixed 1e-8 scale (
venue::kMoneyScale) regardless of the symbol that produced it, so balances from symbols with different scales add up safely. - A base-asset balance is the symbol's qty raw. All symbols sharing a base
asset must therefore use one
qtyScale.
Scales must be positive, and qtyScale must divide or be a multiple of the
money scale; any power of ten works. venue::scalesValid checks the pair. At
the default scale every formula is bit-identical to the fixed-scale code it
replaced, so determinism hashes and replays are unaffected.
Fees¶
Fees flow through flox::FeeSchedule (maker rebate / taker fee, volume tiers).
The venue gate reserves the notional, and the taker fee is charged
post-settlement. A taker holding exactly the notional therefore ends fee-sized
negative instead of being pre-rejected. Conservation still holds, the venue
still collects, and the deficit is bounded by the fee; a test pins this
behaviour as the intended policy.
Two margin models¶
flox::venue::CrossMarginManager and flox::LiquidationEngine
(flox/backtest/) both liquidate. They answer different questions:
LiquidationEngine (backtest) |
CrossMarginManager (venue) |
|
|---|---|---|
| Money | double equity |
Ledger-backed __int128 |
| Driven by | a mark tape | live venue state |
| Optimised for | speed, cascade modelling, ADL ranking, mark impact | exactness: conservation per asset, multi-asset collateral haircuts, segregation |
Collapsing them would either put double money on the venue path or force
ledger machinery into backtests. It is the same split FLOX makes between
SimulatedClock and SystemClock: one concept, two drivers. Pick by what you
are building, and keep one account on one model.
Segregation¶
SegregationReport answers the compliance question: is client money fully
backed by the segregated custody balance?
SegregationReport seg(ledger, /*house accounts*/ {VENUE, INSURANCE});
seg.clientTotal(USD); // sum of each client's POSITIVE balance
seg.fullyBacked(USD, custody);
seg.shortfall(USD, custody); // > 0 is a reportable breach
Client money owed is the sum of positive obligations per account. A client's debit balance is the firm's receivable; netting it against other clients' balances would understate the requirement and let a real custody shortfall report as fully backed.