Version: 1.0.0
Status: FROZEN
Authority: Global Spec
This document defines the complete lifecycle of a trade (match execution) in the distributed exchange, from matching event to final settlement. A trade is created when two orders match.
Trade: An atomic exchange of assets between two parties (maker and taker) at a specific price and quantity.
Trade {
trade_id: UUID, // Unique trade identifier
sequence: u64, // Global trade sequence number
symbol: String, // Trading pair (e.g., "BTC/USDT")
maker_order_id: UUID, // Passive order (provided liquidity)
taker_order_id: UUID, // Aggressive order (took liquidity)
maker_account_id: UUID, // Maker account
taker_account_id: UUID, // Taker account
side: TradeSide, // BUY or SELL (from taker perspective)
price: Decimal, // Execution price
quantity: Decimal, // Matched quantity
maker_fee: Decimal, // Fee charged to maker
taker_fee: Decimal, // Fee charged to taker
executed_at: i64, // Execution timestamp (nanos)
settled_at: i64, // Settlement timestamp (nanos)
state: TradeState, // Current trade state
}
Trigger: Matching engine finds compatible orders
Duration: < 100μs
Output: TradeExecuted event
- Price compatibility:
taker_price >= maker_price(for buy) - Quantity availability: Both orders have sufficient remaining quantity
- Self-trade check:
maker_account != taker_account - Symbol match: Both orders for same trading pair
- Assign unique TradeID (UUID v7)
- Assign global sequence number (monotonic counter)
- Calculate matched quantity (min of both remaining quantities)
- Lock execution price (maker's order price)
- Emit
TradeExecutedevent - State → MATCHED
Failure: If any action fails, rollback entirely (no partial state)
Trigger: Immediately after matching
Duration: < 50μs
Determinism: Pure function of trade parameters
maker_fee = quantity × price × maker_fee_rate
taker_fee = quantity × price × taker_fee_rate
Fee Rates (from fee system spec):
- Maker: -0.01% (rebate) to 0.10%
- Taker: 0.02% to 0.30%
- Calculate with full precision (18 decimal places)
- Round HALF_UP to 8 decimal places for settlement
- Never round down (prevents fee evasion)
Trigger: After fee calculation
Duration: < 100μs
Atomicity: Must update both orders or rollback
- Increment
filled_quantityby matched quantity - Decrement
remaining_quantityby matched quantity - Update order state if necessary:
- If
remaining_quantity = 0→ FILLED - Else if
filled_quantity > 0→ PARTIAL
- If
- Release margin for filled portion
- Append trade_id to order's
trades[]array
Consistency Check:
filled_quantity + remaining_quantity = total_quantity
Trigger: After order update
Duration: < 200μs
Atomicity: All position updates must succeed
If maker side = SELL:
base_balance -= quantity
quote_balance += quantity × price - maker_fee
If maker side = BUY:
base_balance += quantity
quote_balance -= quantity × price + maker_fee
If taker side = BUY:
base_balance += quantity
quote_balance -= quantity × price + taker_fee
If taker side = SELL:
base_balance -= quantity
quote_balance += quantity × price - taker_fee
Invariant: Sum of all account balances remains constant (minus fees)
Trigger: After position update
Duration: < 100μs
Finality: T+0 (immediate settlement)
- Persist position changes to account ledger
- Record fee collection
- Update
settled_attimestamp - State → SETTLED
- Emit
TradeSettledevent
- No reversals or chargebacks
- Final and irreversible
- Byzantine fault tolerant (if using consensus)
Trigger: After settlement
Duration: Asynchronous (non-blocking)
- Update market data (last price, volume, OHLCV)
- Emit
MarketDataUpdateevent - Update account transaction history
- Trigger risk engine recalculation (if applicable)
Failure Handling: Retry indefinitely (eventually consistent)
| State | Description | Terminal |
|---|---|---|
| MATCHED | Trade created, pending settlement | No |
| SETTLED | Fully settled to accounts | Yes |
| FAILED | Settlement failed (rare) | Yes |
Note: FAILED state only occurs during catastrophic system failure
null → MATCHED (matching completed)
MATCHED → SETTLED (settlement completed)
MATCHED → FAILED (settlement error - requires manual intervention)
Normal Path: null → MATCHED → SETTLED (< 1ms total)
{
"event_type": "TradeExecuted",
"trade_id": "01939d7f-8e4a-7890-a123-456789abcdef",
"sequence": 123456789,
"symbol": "BTC/USDT",
"maker_order_id": "...",
"taker_order_id": "...",
"price": "50000.00",
"quantity": "0.5",
"side": "BUY",
"executed_at": 1708123456789000000,
"maker_account": "...",
"taker_account": "..."
}{
"event_type": "TradeSettled",
"trade_id": "01939d7f-8e4a-7890-a123-456789abcdef",
"sequence": 123456790,
"maker_fee": "1.25",
"taker_fee": "12.50",
"settled_at": 1708123456790000000
}- Same order book state → same trades
- Price-time priority strictly enforced
- No randomness in matching algorithm
- Sequence numbers strictly increasing
- Pure mathematical function
- No floating-point arithmetic (use fixed-point decimals)
- Identical fee rates across all nodes
- Rounding mode: HALF_UP (deterministic)
Given identical:
- Order book state
- Timestamp source
- Sequence counter
Replay produces identical trades with identical:
- TradeIDs (if using deterministic UUID generation)
- Prices
- Quantities
- Fees
- Sequence numbers
Recovery:
- Matching engine replays order book from journal
- Recomputes matches deterministically
- Emits missed TradeExecuted events
Idempotency: TradeID prevents duplicate matches
Recovery:
- Query trade state from database
- If MATCHED, retry settlement
- Settlement is idempotent (use trade_id as idempotency key)
Double-Settlement Prevention:
UPDATE accounts
SET balance = balance + delta
WHERE account_id = ? AND NOT EXISTS (
SELECT 1 FROM settlements WHERE trade_id = ?
)Recovery:
- Reporting is asynchronous and eventually consistent
- Retry failed reports from event log
- Non-critical path (doesn't block trading)
- Matching to settlement: < 1ms (p99)
- Trade throughput: 100,000 trades/sec per symbol
- Event emission latency: < 100μs (p99)
- Settlement finality: T+0 (immediate)
- Conservation of Value: Total value in system constant (minus fees)
- Trade-Order Consistency: Every trade references valid orders
- Sequence Uniqueness: Each trade has unique sequence number
- Price Validity: Trade price = maker order price (price-time priority)
- Quantity Validity: Trade quantity ≤ min(maker_remaining, taker_remaining)
- No Self-Trades: maker_account ≠ taker_account
- State Progression: Trades always progress from MATCHED → SETTLED
- Sort orders by price (best first)
- Within same price level, sort by timestamp (earliest first)
- Match incoming order against best counterparty
- Repeat until incoming order filled or no matches
- At same price level, allocate proportionally
- Minimum allocation: 1 base unit
- Remainder to earliest order (time priority tiebreaker)
Default: Price-Time Priority (most exchanges use this)
- Infinite retention (never delete trades)
- Indexed by: trade_id, maker_order_id, taker_order_id, symbol, executed_at
- Partitioned by time for performance
GetTrade(trade_id) → Trade
GetTradesByOrder(order_id) → Trade[]
GetTradesByAccount(account_id, start_time, end_time) → Trade[]
GetTradesBySymbol(symbol, start_time, end_time) → Trade[]
GetRecentTrades(symbol, limit) → Trade[] // For market data
Every trade MUST be:
- Logged to immutable journal
- Cryptographically signed (optional but recommended)
- Timestamped with exchange time
- Linked to source orders
- Reconstructible from event log
Type: Real-Time Gross Settlement (RTGS)
Delay: None (T+0)
Batching: None (each trade settles individually)
Finality: Immediate and irreversible
Alternative (for high-frequency systems):
- Micro-batching: Settle every 100ms
- Netting: Aggregate offsetting trades
- Trade-off: Latency vs throughput
Current Version: v1.0.0
Breaking Changes: New major version required
Field Additions: Append-only (maintain backward compatibility)
Deprecated Fields: Mark deprecated, never remove