Core Entities
Account: account_id, user_id, account_type (CASH, MARGIN), buying_power, portfolio_value, status. Order: order_id, account_id, symbol, side (BUY/SELL), type (MARKET, LIMIT, STOP, STOP_LIMIT), quantity, limit_price, stop_price, time_in_force (DAY, GTC, IOC, FOK), status (PENDING, OPEN, PARTIALLY_FILLED, FILLED, CANCELLED, REJECTED), filled_quantity, avg_fill_price, created_at, updated_at. Fill: fill_id, order_id, quantity, price, counterparty_order_id, timestamp. Position: position_id, account_id, symbol, quantity, avg_cost_basis, current_price, unrealized_pnl. OrderBook: in-memory data structure, not persisted — reconstructed from exchange feed. Quote: symbol, bid_price, bid_size, ask_price, ask_size, last_price, last_size, timestamp.
Order Matching Engine
The order book maintains two priority queues per symbol: bids (sorted by price descending, then time ascending) and asks (sorted by price ascending, then time ascending). Price-time priority: the highest bid and lowest ask are matched first; among equal prices, the earlier order is matched first.
import heapq
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class Order:
order_id: str
side: str # BUY or SELL
price: float
quantity: int
timestamp: int
filled: int = 0
def remaining(self) -> int:
return self.quantity - self.filled
class OrderBook:
def __init__(self):
# bids: max-heap by price (negate price for max-heap)
self.bids: list = [] # (-price, timestamp, order)
# asks: min-heap by price
self.asks: list = [] # (price, timestamp, order)
def add_limit_order(self, order: Order) -> list[dict]:
fills = []
if order.side == "BUY":
while self.asks and order.remaining() > 0:
ask_price, ts, ask = self.asks[0]
if ask_price > order.price:
break
fill_qty = min(order.remaining(), ask.remaining())
fill_price = ask_price # ask price (maker sets price)
order.filled += fill_qty
ask.filled += fill_qty
fills.append({"price": fill_price, "qty": fill_qty,
"buyer": order.order_id, "seller": ask.order_id})
if ask.remaining() == 0:
heapq.heappop(self.asks)
if order.remaining() > 0:
heapq.heappush(self.bids, (-order.price, order.timestamp, order))
else: # SELL
while self.bids and order.remaining() > 0:
neg_price, ts, bid = self.bids[0]
bid_price = -neg_price
if bid_price 0:
heapq.heappush(self.asks, (order.price, order.timestamp, order))
return fills
Pre-Trade Risk Checks
Every order must pass risk checks before reaching the order book: Buying power check: for BUY orders, estimated_cost = quantity * (limit_price or last_price * 1.05 for market orders). Reject if estimated_cost > account.buying_power. Position limit check: total position in symbol after this order <= max_position_size (risk-configured per symbol or account type). Day trading buying power: for margin accounts, day trades use 4:1 intraday leverage but 2:1 overnight — check which limit applies. Order rate limit: max N orders per second per account to prevent runaway algorithms. Market order protection: reject market orders when the spread is > X% (protects against flash crashes filling at extreme prices).
Settlement and Position Tracking
On each fill: Update buying power: for BUY, deduct filled_qty * fill_price from buying_power. For SELL, add to buying_power (after T+1 or T+2 settlement period for equities). Update position: for BUY, add filled_qty to position.quantity; recalculate avg_cost_basis = (old_qty * old_avg + fill_qty * fill_price) / new_qty. For SELL, reduce position.quantity; calculate realized PnL = (fill_price – avg_cost_basis) * fill_qty; record in realized_pnl. Portfolio value: recomputed periodically by multiplying all positions by current market prices. All position updates happen in a transaction with the fill record insert — no fill without a corresponding position update.
See also: Coinbase Interview Prep
See also: Stripe Interview Prep
See also: LinkedIn Interview Prep