Let's look at the systems side of the runway: risk gates, reservations, recovery, rate limits, order and session state machines, CPU pinning, NUMA, networking choices, timestamps, and logging. This is where clever C++ still loses to bad operational design.
Pre-Trade Risk Checks: The Blast Door
Think of pre-trade risk as a locked blast door. It must be fast, and when unsure it must fail closed. These checks are not optional strategy flavoring.
Common checks include:
- Maximum order quantity
- Maximum order notional
- Maximum position
- Maximum gross exposure
- Maximum open orders
- Price collars
- Order-rate limits
- Credit or margin limits
- Instrument trading status
- Kill-switch state
- Duplicate client-order-ID protection
- Session state and exchange connectivity
A simple quantity and notional check:
This simplified example still omits important production concerns:
- Multi-currency valuation
- Partial fills and replace races
- Exchange acknowledgement delays
- Concurrent order paths
- Market-specific self-trade prevention
- Regulatory controls
- Persistence across restarts
The next sections cover outstanding exposure reservations. Keep the blast-door principle in mind:
A risk check must be authoritative, deterministic, observable, and unable to be silently bypassed.
Outstanding Exposure and Risk Reservations
A simple filled-position check is not enough for real pre-trade risk.
Multiple live orders can each look valid on their own while their combined exposure blows past the intended limit. If a strategy fires several buys before any fills arrive, the filled position can still look safe while the outstanding book is already too large.
A better model reserves exposure when an order is accepted locally. Then risk is measured against filled position plus outstanding reservations.
A projected exposure helper can include both filled position and reserved quantity:
The key idea is simple:
If the order is allowed to exist, it must already be counted in the risk picture.
Reservations should be adjusted when the order is accepted, rejected, cancelled, replaced, partially filled, or fully filled.
Remember: a cancel request is not a cancel confirmation. Until the venue confirms the cancel, keep the reservation. Hope is not a risk release.
Reservation Updates on Order Events
When an order event arrives, update reservations immediately and consistently.
A practical rule set is:
- New accepted order: reserve full remaining quantity
- Partial fill: reduce reserved quantity by executed amount
- Full fill: clear remaining reservation
- Cancel acknowledgement: clear remaining reservation
- Reject: clear reservation
- Replace: update reservation to match the new working quantity
Centralize this logic. Spreading exposure arithmetic across strategy code, order handlers, and risk listeners is how double counting appears.
Then projected exposure comes from one source of truth.
The goal is not to over-engineer the model. The goal is to stop the system inventing extra buying power just because two asynchronous events arrived in the wrong order.
Order State Is a State Machine
An order is not merely “sent” or “filled.” Its life is an asynchronous state machine with opinions.
A simplified state model:
A compact representation:
An order record might contain:
Transitions must be explicit and validated:
Do not assume a cancel request means the order is dead. Until the venue confirms it, that order may still execute and embarrass your position math.
This matters operationally:
Your position and exposure accounting must remain correct in every branch.
Exchange Connectivity Is Also a State Machine
The session itself has states:
Important session events may include:
- TCP or UDP transport loss
- Login accepted or rejected
- Heartbeat timeout
- Sequence gap
- Trading halt
- Protocol-level reject
- Exchange maintenance state
- Recovery snapshot completion
- Manual kill switch
A strategy should not confuse “the gateway process is alive” with “it is safe to trade.” Alive and tradable are different states.
At minimum, it needs a well-defined answer to:
Production systems usually need more conditions, but the principle stands: trading permission should be derived from explicit state, not vibes.
Order Recovery and Reconciliation
When a trading system disconnects, the hard part is often not reconnecting. The hard part is knowing what happened while you were away.
An order gateway must reconcile its local view with the venue's view after a disconnect or session reset.
That means answering questions like:
- Did the order reach the venue?
- Was it acknowledged before the disconnect?
- Was it partially filled?
- Was it cancelled, rejected, or replaced?
- Did the venue process an action the local system never saw?
- Is the local position still correct?
A practical recovery flow may look like this:
- Detect session loss or sequence failure.
- Stop sending new risk-bearing orders.
- Mark affected instruments or strategies as not tradable.
- Request recovery data from the venue, if supported.
- Rebuild order state from execution reports, open-order snapshots, or drop copies.
- Compare local order records with venue reality.
- Resolve unknown or ambiguous states conservatively.
- Resume trading only when the system is synchronized and validated.
A useful internal representation may include an explicit recovery state:
A gateway should not assume that "no acknowledgement received" means "order not accepted." Network loss, processing delay, and venue-side buffering can all produce ambiguity.
A safer rule is:
If order status is uncertain, treat it as live until proven otherwise.
That does not mean leaving all exposure open forever. It means reconciliation drives the next action, not guesswork. During uncertainty, restrict new risk and prioritize reconciliation and cancellation as appropriate.
Rate Limiting as a Safety Control
Rate limiting is not only an exchange compliance checkbox. It is also a safety control.
A strategy that can generate too many messages too quickly can overload the venue, the gateway, internal queues, recovery logic, audit logging, and risk handling.
A practical gateway usually separates limits for:
- New orders
- Cancels
- Replaces
- Session messages
- Recovery requests
One simple approach is a token bucket.
A refill function may look like this:
And a consume step:
This is intentionally simple. Production code may separate buckets by order type or instrument class, and it must use a monotonic clock for now_ns.
The important policy decision is what to do when the bucket is empty:
- Reject the request locally
- Prioritize cancel messages over new orders
- Queue only if the queue is bounded and the delay is acceptable
- Fail closed for safety-critical paths
A good rule is:
A rate limit should protect the system, not quietly defer danger.
Message Priority Under Stress
Not all messages have the same urgency.
Under normal conditions, a gateway may process everything in order. Under stress, it should prefer safety-critical work.
A practical priority order may be:
- Risk controls
- Cancels
- Session recovery
- Execution report handling
- New orders
- Telemetry
- Diagnostics
That ordering is not universal, but the idea is important: if the system is overloaded, the first thing to preserve is the ability to reduce risk.
For example, a cancel request may beat a new quote, an execution report may beat a diagnostic log, and a kill switch may beat an analytics update.
This is why overload behavior must be designed before a market move designs it for you.
CPU Affinity and Core Isolation
A general-purpose operating system can move a thread between CPU cores. Migration can disturb cache locality and add jitter.
On Linux, a thread may be pinned to a logical CPU:
Pinning is not a complete latency spa day. A disciplined deployment may also consider:
- Reserving isolated CPU cores
- Avoiding unrelated workloads on those cores
- Binding memory allocations to the local NUMA node
- Managing interrupt affinity
- Setting appropriate power-management policies
- Disabling or controlling frequency scaling where operationally justified
- Avoiding oversubscription
- Maintaining realistic failover behavior
Be cautious with hyperthreading or simultaneous multithreading. Two logical CPUs may share execution resources on the same physical core. Whether it helps depends on workload and hardware.
Always validate operational changes with infrastructure and security teams. Low-latency tuning that wrecks stability or monitoring is not a win. It is a fancy outage.
NUMA: Local Memory Matters
On a multi-socket server, memory is often Non-Uniform Memory Access (NUMA).
A CPU core can usually access memory attached to its own socket faster than memory attached to another socket.
A latency-sensitive thread pinned to a core on socket should ideally allocate and access its hot data on NUMA node .
Bad pattern:
Potential consequences:
- Higher memory latency
- More interconnect traffic
- Increased tail latency
- Less predictable performance
First-touch allocation can matter: on many systems, physical memory is assigned to the NUMA node of the thread that first writes to it.
That means initialization placement can affect runtime latency.
NUMA behavior is platform-specific. Measure it using the deployment environment, not a laptop benchmark.
Busy Polling vs Interrupt-Driven I/O
Normal networking often uses interrupts: the network interface receives a packet and the operating system schedules work.
This saves CPU when traffic is low, but can introduce variable wake-up latency.
Busy polling means a dedicated thread repeatedly checks for work:
Potential benefits:
- Reduced wake-up latency
- Fewer scheduler interactions
- More predictable timing
- Lower jitter under sustained load
Costs:
- Consumes a full CPU core
- Can worsen system-wide contention
- May increase heat and power use
- Requires careful deployment isolation
- Is wasteful during quiet periods
Busy polling is a trade-off, not a personality trait. It is often appropriate only for carefully isolated latency-critical processes.
Kernel Bypass and Network Architecture
Traditional sockets involve several layers:
Kernel-bypass frameworks can allow an application to interact more directly with network-interface queues, often through shared memory and polling.
Potential benefits include:
- Fewer copies
- Fewer system calls
- Less kernel scheduling variability
- More direct packet handling
- Better control over receive/transmit queues
But kernel bypass increases complexity:
- Driver and hardware dependencies
- Operational monitoring challenges
- Different security and isolation considerations
- More responsibility for packet handling
- More difficult upgrades and debugging
- Possible incompatibility with existing tooling
Use kernel bypass only when measured end-to-end gains justify the operational cost. Complexity is not free just because it is fashionable.
Timekeeping and Timestamping
A trading system needs several different notions of time:
A simple latency decomposition:
And:
But timestamps are only meaningful if their clocks are understood.
Potential issues:
- Clock drift
- Clock steps caused by time synchronization
- Different clock domains
- Unstable cycle counters on old or virtualized systems
- Cross-core timestamp consistency
- Exchange timestamps representing different events than assumed
Use a monotonic clock for measuring intervals:
For operational latency analysis, hardware timestamping and carefully synchronized clocks can be necessary. The exact approach depends on infrastructure and regulatory requirements.
Timestamps Must Not Be Mixed Blindly
A trading system often sees several timestamps for the same event. Those timestamps are not interchangeable.
Distinguish at least:
- Exchange timestamp
- NIC receive timestamp
- Application receive timestamp
- Decision timestamp
- Send timestamp (handed to kernel or NIC)
- Acknowledgement receive timestamp
Use a monotonic local clock for elapsed-time measurements.
Do not subtract an exchange timestamp from a local NIC or application timestamp unless the clocks are synchronized and the semantics are known. Mixing clock domains invents a fictional latency number with confidence.
The safe mental model is:
Use one clock for durations, and treat cross-domain timestamps as separate observations.
If the infrastructure supports precision time protocols such as PTP, analysis can improve. Synchronization still does not erase the need to understand what each timestamp actually means.
The Sync Log Pitfall
This is the classic Sync Log Pitfall:
It may allocate, format strings, grab locks, write buffers, wake another thread, or do I/O. Congratulations: you just invited a novelist onto the runway.
Prefer stuffing compact binary telemetry into a bounded buffer:
Then pass it to an asynchronous telemetry thread.
Even asynchronous logging must have defined overload behavior. If telemetry is overloaded, decide what happens:
- Drop low-priority diagnostics
- Sample events
- Aggregate counters
- Preserve risk and audit events
- Trigger an alert
- Never block the matching or order-send path unexpectedly
Telemetry Is Not Audit
Telemetry and audit are related, but they are not the same thing.
Telemetry is for observing the system. Audit is for proving what happened.
That distinction matters because telemetry can often be sampled, aggregated, or dropped under pressure. Audit records usually cannot be silently lost.
The practical lesson is:
Do not let a normal logger become the only record of a critical control event.
If the event affects money, compliance, or safety, the design should say so explicitly. Do not accidentally put compliance-grade durability requirements into an ordinary best-effort logger.
Branch Prediction and Data-Dependent Work
Modern CPUs predict branches. A frequently executed branch with predictable outcomes is cheap. An unpredictable branch can cause a pipeline flush.
If event types follow a stable pattern, this may be efficient. If the pattern is highly unpredictable, it may be costly.
Do not turn every branch into branchless arithmetic as a fashion statement. Branchless code can add instructions, reduce clarity, and perform worse.
Useful principles:
- Keep common paths simple
- Put rare error handling out of the primary path
- Order conditionals by likely outcome when known
- Use profiles to identify genuine hotspots
- Benchmark with representative event distributions
Compiler-specific branch hints exist, but they should be applied only after profiling:
Such hints are not portable and can bite you when the workload changes its mind.
The Takeaway
Fail closed at the risk gate, reserve outstanding exposure, reconcile after disconnects, rate-limit with cancel priority, pin and place memory deliberately, keep clocks honest, and never confuse best-effort telemetry with audit.
Practice: write may_send_orders for your own imaginary gateway and list three session events that must flip it false immediately.