High-frequency trading is software that buys and sells on exchanges by itself. It reads live market prices (market data), decides in a few microseconds whether to trade, and sends the order; no human clicking. A person needs hundreds of milliseconds just to move a mouse; an HFT system tries to finish that whole decode-decide-send path thousands of times per second.
Those decision times are measured in microseconds () or nanoseconds (). In a web app, a 10-millisecond pause is invisible. On a latency-critical trading path, 10 milliseconds is an eternity: the difference between a fair fill and eating a stale quote.
HFT is not just "C++ with fast computers." It is the discipline of making packet decoding, market-state updates, risk checks, and order transmission predictable when the clock is angry.
Median latency alone can lie to you. A system that usually answers in but occasionally stalls at can be more dangerous than one that consistently answers in . Occasional lateness means adverse fills, missed opportunities, and uncontrolled risk.
HFT systems are regulated financial systems. Examples here teach memory predictability, hardware awareness, and safe software design. They are not trading advice, exchange-protocol specs, or substitutes for venue certification and mandatory risk controls.
Let's look at the critical path, why C++ is common here, how to think about latency tails, and the data layouts that keep the hot path honest.
The Trading Critical Path
Think of the hot path as a clear runway. Everything that must finish before an order can leave the building lives on that runway. Extra luggage belongs somewhere else.
A simplified trading loop looks like this:
The critical path is the chain of work that must finish before the order can be sent.
A rough latency budget can be expressed as:
In a colocated environment, individual stages can be measured in nanoseconds or microseconds. But the most important metric is generally a distribution, especially the upper tail:
For every item on the hot path, interrogate it like a suspicious suitcase:
- Does it allocate memory?
- Does it acquire a lock?
- Does it make a system call?
- Does it touch cold memory?
- Does it create unpredictable branches?
- Can it block?
- Can it throw an exception?
- Does it log synchronously?
- Does it call a virtual function?
- Does it access a remote NUMA node?
Here is the runway rule I want you to remember:
Put complex, blocking, failure-prone work on control paths. Keep hot paths bounded, local, and simple.
Why C++ Is Common in HFT
C++ is popular here because it lets you control the messy parts of machines:
- Object lifetime
- Memory allocation
- Data representation
- Alignment
- CPU instructions and compiler optimization
- Threading and atomic memory ordering
- Kernel and networking integration
- Zero-copy processing
- Compile-time abstraction
But C++ is not a latency spell. A C++ application using std::unordered_map, shared ownership, mutex contention, logging, and frequent heap allocation may be slower and less predictable than a carefully designed application in another language.
The advantage comes from deliberate design, not from the file extension.
A typical hot-path C++ profile might favor:
Rather than:
The first shape is compact, predictable, allocation-free, and cache-friendly. The second can be fine for a UI, research tool, or admin service. It is a terrible default for a latency-critical event loop.
Latency Is a Distribution, Not One Number
Judging latency by the median alone is like judging a highway by average speed while ignoring the pileups. Let's look at two systems:
System A wins the median race. System B may be much safer if you care about stale data or late cancellations.
Tail latency can come from all the usual machine gremlins:
- Context switches
- Interrupt handling
- Page faults
- Lock contention
- Dynamic allocation
- Cache misses
- TLB misses
- Garbage collection in adjacent processes
- NUMA remote-memory access
- CPU frequency changes
- Kernel networking queues
- Logging or disk I/O
- Branch mispredictions
- Scheduler migration
- Hardware contention
A useful measurement table for a production system includes:
Never compare benchmark results without confirming:
- CPU model and clock configuration
- Compiler and optimization flags
- CPU affinity
- NUMA configuration
- Whether debug logging was enabled
- Whether the benchmark included I/O
- Whether warm-up occurred
- Whether the data set fit in cache
- Whether the system was otherwise idle
The Floating-Point Price Hazard
Binary floating-point is excellent for scientific computing. It is a trap for most decimal market prices.
For example, decimal has no exact finite representation in binary floating point. This can produce problematic comparisons and price-level identities.
Trading systems generally represent prices as integer multiples of a defined tick size.
If a product has a tick size of , then:
For example:
A lightweight representation:
Now a price is clearly not an arbitrary floating-point soup.
Overflow matters
The maximum value of a signed -bit integer is:
Before multiplying quantities and prices, reason about bounds. A check like max() / b is clear, but when b is not a compile-time constant it hides a runtime division on the hot path. Prefer overflow intrinsics or domain bounds known at initialization (tick size, max quantity):
For signed financial arithmetic, use a carefully reviewed arithmetic utility, known-bound domain checks, or compiler overflow intrinsics. Overflow in position, notional, or risk math is a correctness and safety defect, not a cute edge case.
Market Data Is a Stateful Stream
Market-data feeds are not a pile of independent JSON blobs. They are a stateful stream of binary messages:
- Add order
- Modify order
- Cancel order
- Trade
- Auction event
- Instrument status update
- Snapshot
- Heartbeat
- Sequence or recovery control message
Venue details differ. One principle does not:
A feed handler must know whether its local state is complete and trustworthy.
Many feeds provide sequence numbers:
A basic sequence check:
A sequence gap is not a shrug emoji. Later incremental updates may produce a fantasy book that looks real and is wrong.
A typical recovery process is:
Do not keep quoting from a stale or incomplete book unless the strategy and risk design explicitly allow it. Hope is not a recovery protocol.
Parse Binary Protocols Safely
A tempting but unsafe move is casting raw network bytes straight to a C++ struct:
That can explode because of:
- Alignment requirements
- Compiler-inserted padding
- Endianness differences
- Strict-aliasing issues
- Untrusted or truncated packet data
- Protocol layout changes
A safer pattern checks boundaries and copies scalar values with std::memcpy. Advance the remaining span instead of tracking a separate offset. When returning std::optional<T>, construct a named optional and return it so NRVO can apply; returning a bare T into an optional<T> return type can force an extra copy or move. Hot paths often prefer a bool plus out-parameter or an explicit error enum instead.
For a little-endian protocol on a little-endian host:
For real protocols, create clear decode functions per message type. Validate all lengths, type fields, reserved fields when required, and protocol-specific constraints.
Avoid treating #pragma pack as a universal fix. Packing can create unaligned loads and glue your in-memory C++ layout to someone else's wire format. Decode explicitly.
Cache Behavior Is Often More Important Than Big-O Notation
An algorithm with prettier Big-O can lose to a simpler layout if it pointer-chases through cold memory.
Modern CPUs are absurdly fast at arithmetic on data already in registers or nearby cache. They get slow and sulky when waiting on DRAM.
A rough hierarchy is:
Exact numbers depend on hardware. The relationship matters more than any fixed cycle count from a blog post.
Consider an array of prices and quantities:
This is contiguous. Iteration is friendly to cache lines and prefetchers.
Compare it with a linked structure:
The deeper problem with linked lists is the loop-carried dependency of iteration: each step waits on the previous node's next pointer before the CPU can issue the next load. That stalls the pipeline even when nodes happen to sit in contiguous memory. Scattered heap nodes add cache misses on top of that dependency chain.
Linked structures are not always wrong. Your access pattern and layout still have to earn their keep under measurement.
Structure of Arrays vs Array of Structures
Suppose a strategy scans quote data.
An array-of-structures layout:
A structure-of-arrays layout:
If you only need prices, structure-of-arrays can cut unnecessary memory traffic.
The right choice depends on access patterns:
A common optimization is hot/cold splitting:
Keep the hot structure compact. Large, rarely used metadata should not pollute the cache lines you touch on every update.
A Cache-Conscious Price Ladder
For instruments with bounded price ranges and discrete ticks, an indexed price ladder can be faster and more predictable than a map.
Do not expose an unchecked quantity_at for ticks outside the ladder. Prefer get/set helpers that return false on a miss, assert in debug builds, or force callers through contains first. Out-of-range indexing is undefined behavior dressed as market data.
This offers direct access:
It only works well when the price range is reasonably bounded. A huge sparse tick range can waste memory and hurt cache locality.
A hybrid design may use:
- Dense arrays near the current market
- Sparse storage outside the active region
- A configured maximum depth
- Per-instrument data structures selected by product characteristics
Do not optimize from vibes. Use historical data and realistic replay.
Order Books Need Clear Invariants
A local limit order book must keep its promises. For example:
unless a crossed book is explicitly possible during recovery, aggregation, or feed-specific sequencing.
Basic level state:
For every update, validate domain constraints:
After reducing a level:
A practical order book often separates:
- Feed reconstruction state
- Strategy-visible market state
- Recovery status
- Derived analytics, such as imbalance or microprice
That separation matters because a feed can still be receiving packets while the reconstructed book is garbage.
The Takeaway
Keep the runway clear, measure the tail not just the median, store prices in ticks, treat feeds as stateful streams, decode bytes safely, and make cache-friendly layouts earn their keep.
Practice: sketch a MarketEvent and a FeedState for one instrument, then write down which fields belong on the hot path and which belong in cold metadata.