Code Optimization, Replay & Capstone Pipeline
Bonus: C++ in High-Frequency Trading

12.5 Code Optimization, Replay & Capstone Pipeline

Let's look at the last mile: micro-optimizations, replay, testing, benchmarking honesty, pipeline architecture, and a design challenge. Measure before you get clever.

Prefetching and SIMD: Measure Before Using

The CPU often prefetches sequential access just fine. Manual prefetching can help in some pointer-heavy workloads, but it can also:

  • Fetch data that is never used
  • Evict useful cache lines
  • Consume bandwidth
  • Add complexity
  • Reduce portability

Likewise, SIMD can accelerate parallel arithmetic, but many trading operations are dominated by branching, data dependencies, and memory access rather than vector-friendly computation.

SIMD is often more suitable for:

  • Batch signal calculations
  • Statistical feature generation
  • Portfolio calculations
  • Backtesting
  • Risk scenarios
  • Large-array transforms

It may be less helpful for:

  • One message at a time
  • Pointer-heavy book updates
  • State-machine transitions
  • Highly branch-dependent gateway logic

The correct workflow is boring and correct:

Measure-change-verify optimization loop.

Avoid Accidental Copies

A large event structure copied repeatedly can increase memory traffic.

void process(MarketEvent event); // Copies
Pass-by-value copy of a market event.

Prefer:

void process(const MarketEvent& event) noexcept;
Const-reference hot-path parameter.

Or, when ownership and movement matter:

void enqueue(MarketEvent&& event) noexcept;
Move enqueue of a market event.

References are not automatically faster everywhere. Small trivially copyable types can be cheaper to pass by value and may optimize better.

For example, passing a small integer ID by value is appropriate:

void process_instrument(std::uint32_t instrument_id) noexcept;
Pass small IDs by value.

Use a profiler and inspect generated code only when this is a demonstrated hotspot.


Virtual Dispatch and Type Erasure

Virtual functions can be perfectly acceptable in initialization, configuration, monitoring, and low-rate paths.

In a tight loop, virtual dispatch may inhibit inlining and introduce an indirect branch:

class Strategy {
public:
    virtual ~Strategy() = default;
    virtual void on_market_event(const MarketEvent&) = 0;
};
Runtime virtual strategy interface.

A compile-time strategy can enable inlining:

template <typename Strategy>
void dispatch_event(
    Strategy& strategy,
    const MarketEvent& event
) noexcept {
    strategy.on_market_event(event);
}
Compile-time strategy dispatch.

This trade-off is between runtime flexibility and optimization.

A practical architecture may use:

  • Runtime polymorphism to select a strategy at startup
  • A specific concrete strategy instance in the hot loop
  • Templates for reusable hot-path components
  • Virtual interfaces for administrative systems and test doubles

Avoid template complexity when it does not improve a measured critical path. Cleverness without a profile is just homework.


Compile for the Deployment CPU Carefully

Compiler flags can substantially affect generated code.

Common release settings may include:

-O3
-DNDEBUG
Common release optimization flags.

Target-specific optimization may use:

-march=native
Native architecture march flag.

But -march=native compiles for the build machine. Ship that binary to a different CPU generation and it may fail or sulk.

For reproducible deployments, use an explicit architecture target appropriate for production hardware.

Also consider:

  • Compiler version
  • Standard library version
  • Link-time optimization
  • Profile-guided optimization
  • Debug-symbol retention for incident analysis
  • Sanitizer builds for testing, not production hot paths

Do not use -ffast-math casually in financial systems. It can relax floating-point semantics in ways that may violate assumptions. Fixed-point arithmetic is generally preferable for venue prices and quantities regardless.


Deterministic Replay Is Essential

A live trading bug is miserable to investigate if you cannot replay the event sequence.

A robust replay system records enough information to reconstruct the relevant state:

  • Raw inbound market-data packets
  • Raw inbound execution reports
  • Outbound order messages
  • Receive and send timestamps
  • Session-state events
  • Configuration version
  • Strategy version or build identifier
  • Risk-limit version
  • Operational actions, such as kill-switch activation

A replay pipeline:

Deterministic replay pipeline.

Determinism requires controlling hidden sources of nondeterminism:

  • Wall-clock reads
  • Random numbers
  • Thread scheduling
  • Unordered-container iteration order
  • Shared mutable state
  • Floating-point environment
  • External service calls

Inject dependencies where practical:

class Clock {
public:
    virtual ~Clock() = default;
    virtual std::uint64_t now_ns() noexcept = 0;
};
Injected clock interface for replay.

For the most latency-critical loops, a virtual clock call may not be desirable. A template parameter, function object, or replay-specific build can provide the same testability with different performance trade-offs.


Testing a Feed Handler

Feed handlers should be tested against ordinary inputs and adversarial nonsense.

Test cases should include:

  • Empty packet
  • Truncated header
  • Truncated message body
  • Invalid message length
  • Unknown message type
  • Invalid side value
  • Zero or negative quantity where forbidden
  • Price outside configured range
  • Duplicate sequence number
  • Out-of-order message
  • Sequence gap
  • Snapshot followed by incremental updates
  • Recovery during active trading
  • Maximum-size packet
  • Malformed packet with valid-looking prefix

Example property:

For every accepted order-book update, the book remains internally consistent.

Possible invariants include:

assert(best_bid_ticks < best_ask_ticks);
assert(total_displayed_quantity >= 0);
assert(order.leaves_quantity >= 0);
assert(order.cumulative_quantity >= 0);
assert(
    order.cumulative_quantity + order.leaves_quantity
    <= order.original_quantity
);
Order-book invariant assertions.

Assertions are useful during development and test. Production handling of an invariant violation should be deliberate: often marking state invalid, disabling quoting, alerting, and entering recovery is safer than continuing.


Fuzzing Binary Decoders

Binary protocol decoders are strong candidates for fuzzing.

A fuzz target can pass arbitrary bytes into the decoder:

extern "C" int LLVMFuzzerTestOneInput(
    const std::uint8_t* data,
    std::size_t size
) {
    const auto bytes = std::span{
        reinterpret_cast<const std::byte*>(data),
        size
    };

    auto result = decode_add_order(bytes);

    // The key property: no out-of-bounds access,
    // no undefined behavior, no crash.
    (void)result;

    return 0;
}
LibFuzzer entry for binary decoder.

Fuzzing does not prove the protocol implementation is correct. It is effective at finding:

  • Out-of-bounds reads
  • Integer overflows
  • Infinite loops
  • Parser crashes
  • Unhandled malformed input
  • Unexpected state transitions

Use sanitizers in test environments:

-fsanitize=address,undefined
Sanitizer flags for decoder tests.

Thread sanitizers can help find data races, though they substantially change timing and are not suitable for latency benchmarking.


Benchmarking Without Fooling Yourself

A benchmark should answer a specific question, not flattery.

Bad benchmark:

auto start = now();
for (...) {
    strategy.process(event);
}
auto end = now();
Naive closed-loop microbenchmark.

Potential problems:

  • Compiler optimized the work away
  • Input stayed entirely in cache
  • No realistic branch distribution
  • No queueing or synchronization
  • No market-data decode cost
  • No warm-up
  • No contention
  • Timer overhead dominates
  • Test data is unrealistically repetitive

Better practices:

  • Use realistic recorded or synthetic distributions
  • Ensure results are consumed so work cannot be removed
  • Separate warm-up from measured execution
  • Pin benchmark threads where production threads are pinned
  • Record percentile distributions
  • Measure under load and in idle conditions
  • Compare before and after with identical conditions
  • Keep benchmark code in version control

A benchmark result is a statement about a particular workload on a particular machine. Treat it as evidence, not scripture.


Coordinated Omission

A benchmark can hide latency spikes if it only measures work when the system is ready to accept more work.

Suppose a system stalls for 10 ms10\ ms. A closed-loop benchmark might stop sending requests during that period, reporting deceptively good percentiles.

This is called coordinated omission: timing the finish line only when the runner feels ready.

An open-loop benchmark sends or models arrivals according to an external schedule, even when the system is slow. It better reveals queueing delay and tail behavior.

In market systems, real arrivals are external. Market data does not pause because your feed handler is overloaded.

Your test harness should model bursts, packet loss, replay recovery, and periods of extreme activity.


Backpressure Is a Design Decision

Every bounded queue can fill.

if (!queue.try_push(event)) {
    // What now?
}
Queue-full backpressure decision point.

Possible policies:

SituationPossible response
Market-data analytics queue fullDrop optional analytics event
Audit queue fullPersist through a separate guaranteed mechanism or enter controlled failure
Strategy input queue fullDisable quoting, resynchronize, or apply strategy-specific safety policy
Order gateway queue fullReject new orders locally and alert
Risk event queue fullFail closed; do not silently lose safety-relevant state

There is no universal correct answer. The response depends on whether dropped work affects:

  • Market-state correctness
  • Position correctness
  • Regulatory records
  • Risk-control integrity
  • Client obligations
  • Ability to cancel orders

What is unacceptable is an unspecified overload policy. Silence is not a design.


A Practical Pipeline Architecture

A simplified process architecture can look like this:

Simplified market-data-to-order pipeline.

Separate paths commonly exist for:

Execution report path into risk state.

And:

Control plane responsibilities.

The control plane should not share unnecessary locks or allocations with the critical data plane.

A healthy design separates:

PlaneTypical work
Data planeDecode, book update, decision, risk gate, order send
Control planeDeployments, configuration, limits, session administration
Observability planeMetrics, logs, traces, alerts, replay capture
Recovery planeSnapshots, retransmissions, state reconciliation

A Minimal Event Model

A compact internal event model avoids tying strategies directly to every venue’s wire protocol.

enum class MarketEventType : std::uint8_t {
    add,
    modify,
    cancel,
    trade,
    status
};

enum class Side : std::uint8_t {
    buy,
    sell
};

struct MarketEvent {
    std::uint64_t sequence;
    std::uint64_t exchange_timestamp_ns;
    std::uint32_t instrument_id;
    std::int64_t price_ticks;
    std::int64_t quantity;
    MarketEventType type;
    Side side;
};
Normalized internal market event model.

The feed adapter converts venue-specific messages into this internal representation:

Venue message to normalized event flow.

This makes strategy logic easier to test and reuse. However, do not normalize away venue-specific semantics that matter for correctness, such as auction states, implied liquidity, trade conditions, or order priority rules.


Strategy Code Should Be Explicit About Validity

A strategy should not trust a market state merely because the bytes exist in RAM.

struct MarketView {
    std::int64_t best_bid_ticks;
    std::int64_t best_ask_ticks;
    std::int64_t bid_quantity;
    std::int64_t ask_quantity;
    bool synchronized;
    bool instrument_tradeable;
};
Strategy-visible market view.

Then:

bool may_quote(const MarketView& market) noexcept {
    return market.synchronized &&
           market.instrument_tradeable &&
           market.best_bid_ticks < market.best_ask_ticks &&
           market.bid_quantity > 0 &&
           market.ask_quantity > 0;
}
Conservative may_quote validity check.

This is intentionally conservative. A real strategy may choose differently, but validity must be an explicit part of the interface.


Handling Stale Data

A feed can be connected but stale.

Track the last valid update time:

struct FreshnessState {
    std::uint64_t last_update_ns{0};
};

bool is_stale(
    std::uint64_t now_ns,
    std::uint64_t last_update_ns,
    std::uint64_t maximum_age_ns
) noexcept {
    // Same monotonic clock domain required. Guard unsigned wrap if clocks jump.
    if (now_ns < last_update_ns) {
        return true;
    }
    return now_ns - last_update_ns > maximum_age_ns;
}
Stale-data freshness check.

This assumes one monotonic clock domain. If now_ns can move backwards relative to last_update_ns, treat the feed as stale rather than underflowing unsigned math into a huge age that looks freshly updated.

If market data becomes stale, a safe response may include:

  • Stop sending new orders
  • Cancel resting quotes if policy requires
  • Mark the instrument unavailable
  • Trigger alerts
  • Begin feed recovery
  • Require explicit revalidation before resuming

The correct behavior depends on strategy, venue mechanics, and risk requirements. Design it before an incident designs it for you.


Cancellations Are Part of Risk Control

Many naive systems worship new orders because they look like opportunity. In stress, cancellation capacity can matter more.

Examples:

  • Data feed becomes stale
  • Price moves through a resting quote
  • Risk limit is breached
  • Exchange status changes
  • Strategy is disabled
  • Network recovery begins

A robust order gateway needs priority rules. For example:

Cancel priority over new orders.

Actual venue rules differ. Some venues impose message-rate limits, cancellation semantics, or special mass-cancel facilities. Implement the documented venue behavior, and test it under simulated disconnects and delayed acknowledgements.


Graceful Degradation and Fail-Safe Behavior

A trading system should be designed around failure modes, not only the happy path brochure.

Examples:

FailureSafe response
Market-data sequence gapMark state invalid and recover
Order-session disconnectStop new flow; reconcile outstanding orders
Risk service unavailableFail closed for new orders
Telemetry overloadDrop low-priority diagnostics, preserve critical records
Clock health degradedRaise alert; restrict operations if timestamps are safety-critical
Internal invariant breachStop affected strategy or process safely and investigate
Unknown execution reportReconcile; do not ignore silently

A kill switch should be:

  • Fast
  • Auditable
  • Tested
  • Authoritative
  • Observable
  • Durable enough for its role
  • Able to prevent new risk immediately
  • Designed with a clear policy for existing live orders

“Fail closed” generally means that uncertainty prevents additional risk from being created. It does not always mean indiscriminately killing a process, because abrupt termination can make order reconciliation harder. The correct incident response depends on the state and venue protocol.


A Reference Order Intent

Separate strategy intent from exchange-specific wire messages.

enum class OrderType : std::uint8_t {
    limit,
    market
};

struct OrderIntent {
    std::uint64_t internal_order_id;
    std::uint32_t instrument_id;
    Side side;
    OrderType type;
    Price price;
    Quantity quantity;
};
Internal OrderIntent separated from wire format.

The path can then be:

Strategy intent to venue transmit path.

Benefits:

  • Strategy tests do not need network protocol fixtures
  • Risk logic can be tested independently
  • Venue adapters remain isolated
  • Audit records can capture a stable internal representation

The gateway still must validate venue-specific constraints after generic risk checks.


Example: A Simple Quote Decision

This is not a profitable trading strategy. It is a bounded decision interface for learning. Do not confuse the two.

struct QuoteDecision {
    bool should_send{false};
    Side side{Side::buy};
    Price price{};
    Quantity quantity{};
};

QuoteDecision decide(
    const MarketView& market,
    Quantity desired_quantity
) noexcept {
    if (!may_quote(market)) {
        return {};
    }

    QuoteDecision decision{};
    decision.should_send = true;
    decision.side = Side::buy;
    decision.price = Price{market.best_bid_ticks};
    decision.quantity = desired_quantity;

    return decision;
}
Simple bounded quote decision.

Before sending, the order still passes through:

  1. Strategy-level controls
  2. Pre-trade risk validation
  3. Venue-specific validation
  4. Session-state validation
  5. Rate limiting and order-capacity controls

Do not bury risk logic only inside strategy code. Strategies evolve quickly; safety boundaries should stay independently enforceable.


Build a Latency Budget Before Optimizing

Write down a budget for the full path.

StageTargetMeasured
NIC to application????
Decode????
Book update????
Decision????
Risk checks????
Order encoding????
Application to NIC????
Total????

The targets need not be fantasy nanoseconds. The purpose is to discover where time actually goes.

If market-data transport consumes most of the budget, micro-optimizing a strategy comparison may have no meaningful impact.

If tail latency is caused by allocator stalls, rewriting an arithmetic expression will not solve it.

Optimization should follow evidence, not ego.


A Rough Investigation Order

Profiling and measurement decide what to do next. The list below is only a rough order of investigation for beginners, not a universal checklist or a substitute for evidence:

  1. Establish correctness and recovery behavior.
  2. Record representative latency distributions.
  3. Remove obvious hot-path allocations and blocking I/O.
  4. Reduce unnecessary shared mutable state.
  5. Fix cache-unfriendly data layouts.
  6. Pin threads and verify NUMA placement.
  7. Tune queues and batching behavior.
  8. Investigate network architecture.
  9. Apply micro-optimizations only where the profile points.
  10. Re-run replay, fuzzing, and operational tests after each meaningful change.

Skip steps that the data says are irrelevant. The most successful low-latency systems are usually not clever everywhere. They are disciplined everywhere. Discipline scales. Cleverness leaks.


Common Mistakes

Using `double` for tradable prices

Use integer ticks or a reviewed decimal/fixed-point representation.

Treating `volatile` as synchronization

Less common in modern codebases that already use atomics, but still wrong when it appears. Use std::atomic and correct memory ordering.

Putting `std::async` or `std::future` on the hot path

They can hide thread creation, allocation, and blocking waits. Prefer explicit SPSC queues and owned worker threads with measured scheduling.

Assuming coroutines are allocation-free

Coroutine frames often allocate unless the implementation and promise type carefully avoid it. Measure frame allocation before putting suspension on a latency-critical path.

Assuming a standard hazard-pointer helper is hot-path ready

Facilities such as make_hazard_pointer may allocate. Stack-friendly or pooled protection is typical in high-performance reclaim; measure the library you ship (see Lesson 11.1).

Assuming lock-free means fast

Minimize sharing first. Use locks where they are appropriate and off the critical path.

Ignoring sequence gaps

A gap may invalidate the local market state. Recover according to venue rules.

Logging formatted strings per event

Record compact telemetry asynchronously.

Allocating per packet or per order

Use stack objects, fixed-capacity buffers, or reviewed pools.

Optimizing only the average

Measure percentiles and investigate tail latency.

Benchmarking on a laptop and extrapolating

Measure on representative production hardware and topology.

Treating cancel requests as immediate cancels

Orders remain live until confirmed otherwise.

Allowing stale data to drive new orders

Make freshness and synchronization explicit trading prerequisites.

Coupling strategy directly to a venue protocol

Use a normalized internal model while preserving meaningful venue semantics.

Designing recovery after deployment

Recovery and reconciliation are core functionality, not optional features.



Recovery Must Be Tested Like a First-Class Feature

Recovery code is often treated as a side path. That is a mistake.

If recovery is not tested, it is a theory.

Test scenarios should include:

  • Disconnect during active trading
  • Sequence gap while orders are live
  • Partial fills during recovery
  • Cancel acknowledgement arriving late
  • Unknown execution report after reconnect
  • Duplicate execution report
  • Recovery snapshot inconsistent with local order state
  • Recovery request timeout
  • Restart while open orders exist

A useful invariant is:

Recovery must never create new unaccounted risk.

That means replay and reconciliation should be checked against position, outstanding orders, reservations, venue state, and strategy visibility.

A system that can trade fast but cannot recover safely is not production-ready. It is just fast at becoming wrong.

The Challenge: Market-Data-to-Order Pipeline

Your challenge is a simulated pipeline with no live exchange connectivity. Build the skeleton of predictability, not a trading firm in a weekend.

Requirements

Build:

  • A binary market-data decoder
  • A sequence validator
  • A simple top-of-book model
  • A bounded SPSC queue
  • A deterministic strategy interface
  • A pre-trade risk gate
  • An order state machine
  • A simulated exchange acknowledgement stream
  • Latency histograms
  • A replay mode

Suggested flow

Capstone suggested pipeline flow.

Core invariants

Your implementation should enforce:

quantity>0\text{quantity} > 0
best bid<best ask\text{best bid} < \text{best ask}
leaves quantity0\text{leaves quantity} \ge 0
cumulative quantity+leaves quantityoriginal quantity\text{cumulative quantity} + \text{leaves quantity} \le \text{original quantity}
projected positionmaximum position\left| \text{projected position} \right| \le \text{maximum position}

Test scenarios

  • Normal feed flow
  • Sequence gap during active processing
  • Invalid packet length
  • Stale-market timeout
  • Risk-limit rejection
  • Partial fill followed by cancel acknowledgement
  • Fill received while cancellation is pending
  • Queue-full behavior
  • Restart and replay from recorded events

Acceptance checklist

A final checklist turns abstract design goals into pass/fail conditions:

  • No allocations in the measured data path
  • Every queue has a defined full behavior
  • Every inbound message is bounds checked
  • Sequence gaps invalidate trading eligibility
  • Orders remain live until confirmed otherwise
  • Risk reservations include outstanding exposure
  • Cancel priority is explicit under overload
  • Replay of the same input produces the same state transitions
  • Decoder fuzzing runs under sanitizers
  • Concurrency tests run under ThreadSanitizer
  • Benchmarks include sample count and percentile data (at least p50p_{50}, p99p_{99}, p99.9p_{99.9})
  • Recovery paths are tested with live-order scenarios
  • Telemetry overload cannot silently remove risk or audit events

If the project does not meet these conditions, it is still a draft.


The objective is not to found an HFT firm from a tutorial. The objective is to learn how predictable C++ systems are designed: explicit state, bounded resources, validated transitions, measured performance, and safe behavior under uncertainty.


Conclusion

C++ in high-frequency trading is fundamentally about engineering predictable systems. Speed without predictability is just expensive randomness.

The key ideas are:

  • Represent prices and quantities with exact, bounded types.
  • Treat market data as a stateful stream requiring gap detection and recovery.
  • Keep the hot path allocation-free, bounded, and free of blocking work.
  • Optimize memory layout and ownership before applying clever micro-optimizations.
  • Use atomics and memory ordering correctly; never use volatile for synchronization.
  • Build clear order and session state machines.
  • Make pre-trade risk checks authoritative and fail closed.
  • Measure latency distributions, especially tail latency.
  • Record enough information for deterministic replay and incident analysis.
  • Design explicitly for disconnections, stale data, overload, and recovery.
  • Reserve outstanding exposure and reconcile after disconnects.
  • Separate best-effort telemetry from durable audit and risk records.

The fastest system is not the one with the most assembly tricks. It is the one that stays correct, observable, and predictable when the market, network, operating system, and hardware are all having a bad day.

Practice: pick one stage from your latency budget table and write down how you would measure p50p_{50} and p99p_{99} for it using a recorded feed replay.

Finished reading this lesson?