CUDA Orderbook
A GPU pipeline that reconstructs a live limit order book from raw NASDAQ ITCH data and backtests 10,000 trading strategies against it in parallel.
CUDA · C++
GitHub ↗A GPU pipeline that parses raw NASDAQ TotalView ITCH 5.0 market data, reconstructs a live limit order book for every traded symbol, and sweeps 10,000 trading strategy configurations against the reconstructed book in parallel on the GPU.
Results
Tested against the 2019-08-30 NASDAQ TotalView-ITCH 5.0 file from the NASDAQ Public Archive, run on an NVIDIA H100 SXM cloud instance:
- 305,105,310 messages reconstructed in 69.5s (~4.4M msgs/sec)
- 10,000 strategy configs backtested in 83.8s
Pipeline
Parsing. ITCH files can be 10+ GB, so a custom ItchReader/ItchDecoder
streams fixed-size chunks from disk instead of loading the whole file, decoding
messages into a struct-of-arrays layout so data stays contiguous for the GPU.
GPU hash table. Reconstruction needs to look up, insert, and delete order state hundreds of millions of times from inside a kernel running thousands of warps at once, so the hash table lives entirely on the GPU (avoiding host round-trips) and uses atomics so concurrent warps agree on its state. It’s sized for a ~50% load factor with two sentinel values — empty vs. tombstoned — since a lookup has to keep scanning past a tombstone but an insert can claim either.
Compacting by symbol. The raw ITCH stream interleaves every symbol in
timestamp order. SymbolCompactor uses cub::DeviceRadixSort plus a
thrust::gather pass to regroup it into contiguous per-symbol blocks in device
memory, so each warp can read a sequential slice instead of scanning past every
other symbol’s messages.
Reconstruction. One warp per symbol, one warp per block. A symbol’s messages
are causally ordered (a Cancel can’t run before its Add), so a symbol has to
process sequentially — but symbols are independent of each other, so the
parallelism comes from running thousands of symbols’ warps concurrently, not
from splitting one message across threads. All 32 threads in a warp collaborate
on the book itself: each thread owns a slice of price levels via a hash
function, and after every message a 5-round warp shuffle finds the top 5 bid/ask
levels.
Backtesting. 10,000 strategy configs, one per block, each walking the tick
stream on a single thread since a strategy’s decision at tick N depends on its
decision at tick N-1. The parallelism is entirely across the 10,000
independent walks. Ticks come out of reconstruction claimed via a global atomic
counter (so write order isn’t chronological), so TickCompactor re-sorts them
by symbol using the same sort/gather/run-length-encode approach as
SymbolCompactor before backtesting runs.
Challenges
The main constraint was shared memory. The per-thread price-level array started at 16 entries, which failed on heavily-traded symbols against real data; raising it to 64 still wasn’t enough, and pushing further hit the GPU’s 49,152 bytes/block shared memory ceiling outright — the kernel failed to link at 96. The real ceiling on this hardware works out to around 94 price levels per thread.
Limitations
One symbol out of ~9,000 in the test file still produces a small number of
reconstruction failures (99 out of 305,105,310 messages) even at the shared
memory ceiling. There’s also some deliberate over-allocation: SymbolCompactor’s
offset tables are sized to the full message count because the true unique-symbol
count isn’t known until after the run-length-encode pass, and ticks_ is
similarly over-allocated since the exact number of ticks written isn’t known
until after reconstruction finishes.
Full write-up, code, and more profiling detail in the repository.