Experimenting with Simulated Order Books
Peter Bieda
Author
I’ve always believed the best way to understand something is by actually building it. That applies to trading systems even more than anything else. You can read papers about market microstructure all day, but none of it makes sense until you get your hands dirty and watch an order book behave under real-time conditions.
A few months ago, I decided I wanted to get a better feel for how liquidity actually forms, disappears, and moves around. I didn’t want charts, I didn’t want summaries, and I didn’t want high-level explanations. I wanted to see the mechanics. So I opened a blank Python file and started writing my own small matching engine. No dependencies. No fancy frameworks. Just raw logic.
The result ended up being around 300 lines of code. Nothing production-ready, but accurate enough to show me how real order books “breathe.” And honestly, writing it taught me more in a single weekend than reading multiple books.
Below, I’m walking through what I built, why I built it, and the exact test scripts I used. This is the kind of thing that directly carries over to real trading infrastructure—pipelines, matching logic, latency behavior, simulation accuracy, everything.
Why I Built My Own Order Book
I wanted answers to simple questions:
- What actually happens inside the spread when aggressive orders hit?
- How fast can liquidity evaporate under pressure?
- What does queue position really look like?
- How much does microsecond latency matter in a matching engine simulation?
- How does a simple execution algorithm behave under different liquidity densities?
You don’t get that intuition from datasets. You get it from watching a system you built react.
So the idea was simple:
Simulate real-time order flow → Match orders → Track microstructure → Test strategies → Learn from the results.
That’s it.
Basic Data Structures
I started with the core data structures:
- Buy side = max-heap
- Sell side = min-heap
This lets the book always surface the best bid and best ask instantly.
Here's the core structure:
import heapq
import time
class Order:
def __init__(self, order_id, side, qty, price, ts=None):
self.id = order_id
self.side = side
self.qty = qty
self.price = price
self.ts = ts if ts else time.time()
class OrderBook:
def __init__(self):
self.bids = [] # max-heap (store negative price)
self.asks = [] # min-heap
self.order_map = {}
I store the order objects in a map so I can reference them during matching or cancel operations.
Adding Orders and Matching
I kept matching simple, but realistic:
- Price-time priority
- FIFO queueing
- Partial fills
- Multiple match passes until quantity is zero
Here’s the core logic:
def add_order(self, order):
if order.side == "BUY":
self._match_buy(order)
if order.qty > 0:
heapq.heappush(self.bids, (-order.price, order.ts, order))
else:
self._match_sell(order)
if order.qty > 0:
heapq.heappush(self.asks, (order.price, order.ts, order))
def _match_buy(self, order):
while self.asks and order.qty > 0:
best_price, _, ask = self.asks[0]
if best_price > order.price:
break
heapq.heappop(self.asks)
traded = min(order.qty, ask.qty)
order.qty -= traded
ask.qty -= traded
if ask.qty > 0:
heapq.heappush(self.asks, (ask.price, ask.ts, ask))
This is not an exchange-grade engine, but it’s realistic enough to see all the weird market behavior you normally only see in real tick data.
Visualizing Book Behavior
I added a helper to quickly see what’s happening:
def top_of_book(self):
best_bid = -self.bids[0][0] if self.bids else None
best_ask = self.asks[0][0] if self.asks else None
return best_bid, best_ask
Seeing the spread jump in microseconds when liquidity disappears is extremely valuable.
Random Order Flow Generator
To stress test everything, I wrote a simple stochastic order flow generator.
import random
def random_order(book, order_id):
side = random.choice(["BUY", "SELL"])
price = random.randint(98, 102)
qty = random.randint(1, 5)
order = Order(order_id, side, qty, price)
book.add_order(order)
Running this 10,000 times shows patterns:
- Spread compresses
- Spread widens
- Liquidity appears in waves
- Large market sweeps drain the book
- The system sometimes stabilizes, sometimes collapses
Watching it in motion made microstructure feel less abstract and more like a living system.
Simulating Liquidity Crashes
One of the coolest tests I wrote simulates a sudden wall of market sells.
def liquidity_crash(book, count=5000):
for i in range(count):
crash_order = Order(
order_id=f"crash-{i}",
side="SELL",
qty=1,
price=book.top_of_book()[0] - 1, # hit through bid
ts=time.time()
)
book.add_order(crash_order)
After around a few hundred orders, liquidity starts to vanish. After a thousand, the entire bid side collapses. This is exactly how real markets behave under stress.
Testing Queue Position Logic
Queue position is one of the most underrated concepts in trading.
To test how it works, I created 100 buy orders at the same price and timestamp offsets.
def queue_test(book):
base_price = 100
for i in range(100):
order = Order(
order_id=f"q-{i}",
side="BUY",
qty=1,
price=base_price,
ts=time.time() + (i * 0.000001) # micro separation
)
book.add_order(order)
Then throw a massive sell wall at it:
for i in range(200):
sell = Order(
order_id=f"killer-{i}",
side="SELL",
qty=1,
price=100,
ts=time.time()
)
book.add_order(sell)
This clearly shows:
- Early orders fill first
- The tail of the queue gets nothing
- Latency differences on the order of microseconds matter
This is the kind of intuition quants rely on.
Simple Execution Algorithm Test
Next, I built a tiny execution algo to see how slippage behaves.
def execute_buy(book, target_qty):
filled = 0
while filled < target_qty:
bid, ask = book.top_of_book()
if ask is None:
break
order = Order("exec", "BUY", 1, ask)
book.add_order(order)
filled += 1
return filled
You can run this after liquidity-crash scenarios and watch the slippage get worse every time.
Full Sample Script I Used to Test Everything
This is the exact script I ran during development:
from orderbook import OrderBook, Order
import random
import time
book = OrderBook()
# seed initial liquidity
for i in range(50):
book.add_order(Order(f"seed-bid-{i}", "BUY", random.randint(1, 5), 99))
book.add_order(Order(f"seed-ask-{i}", "SELL", random.randint(1, 5), 101))
print("Initial top of book:", book.top_of_book())
# random order flow
for i in range(2000):
side = random.choice(["BUY", "SELL"])
price = random.randint(98, 102)
qty = random.randint(1, 3)
o = Order(f"r-{i}", side, qty, price)
book.add_order(o)
print("After random flow:", book.top_of_book())
# liquidity crash test
for i in range(500):
bid, _ = book.top_of_book()
o = Order(f"lc-{i}", "SELL", 1, bid - 1)
book.add_order(o)
print("After crash test:", book.top_of_book())
Running this script shows the following:
- top-of-book moves constantly
- spread compresses and explodes
- liquidity gets consumed fast
- execution quality changes based on book density
- doing the same test twice never gives the same outcome
This is exactly why simulation matters.
Key Things I Learned
After spending time with this system, a few things became clear:
1. Market microstructure is extremely dynamic
If you only look at historical data, you miss the entire behavior inside the spread. Simulating it makes you realize how fragile liquidity can be.
2. Small design choices matter (a lot)
FIFO handling, timestamp resolution, heap update patterns—these small decisions change outcomes.
3. Latency has a real impact
Even microsecond differences change queue outcomes and fill probabilities.
4. Execution algorithms behave differently under different liquidity regimes
If the book is thin, everything breaks.
5. Simulated data is useful, but only if the engine is correct
A sloppy simulation gives misleading results. Building it myself made me much more sensitive to that risk.
Where I Want to Take This Next
This 300-line matching engine is only the start. I’m planning to add:
- UDP-style market data distribution
- Per-order latency models
- C++ rewrite for speed
- Event-driven architecture
- Strategy hooks for backtesting
- Load tests using millions of orders
The goal is simple:
Build intuition by building systems.
Why This Matters for Quant + Trading Engineering
This small project directly overlaps with real-world trading infrastructure:
- market data pipelines
- execution systems
- matching logic
- simulation engines
- strategy development
- liquidity modeling
It gave me the same type of insight you usually only get after working inside a trading firm.
And that’s exactly why I built it.