Algo Trading

C/C++ in Finance: Code Techniques Explained

By Alex Pierrefeu14 min read
C/C++ in Finance: Code Techniques Explained

C and C++ are the languages finance reaches for when the cost of a microsecond, a memory allocation or an unpredictable pause is real money: market data feed handlers, execution gateways, matching engines, pricing libraries and the risk engines that must revalue a book before the market moves again. They earn that role through properties no interpreted language offers, compilation to machine code, explicit control of memory layout and lifetime, and the absence of a garbage collector, and they charge for it in development time and in the severity of mistakes. This guide explains the techniques that matter in practice: how to represent prices and time so they cannot silently go wrong, how to keep the hot path free of allocations and locks, how to move data between threads without a mutex, how to measure latency honestly, and how to test code whose bugs cost money. It closes with where Quant Charts fits for the part of the work that should happen before any C++ is written, with Quant, our coding agent, turning a strategy idea into testable Pine Script in minutes.

Key points:

  • Use C++ where determinism pays. Feed handling, order gateways and pricing loops; research and most retail bots belong in Python or on a chart.
  • Integers for money, chrono for time. Prices in ticks, quantities in lots, timestamps in std::chrono; doubles are for models, not for ledgers.
  • Nothing surprising on the hot path. No heap allocation, no locks, no exceptions, no synchronous logging between packet arrival and order departure.
  • Measure, then optimise. Percentiles from a monotonic clock and a profiler decide what is slow; intuition about C++ performance is usually wrong.

Video: Designing Low-Latency Systems in C++

David Gross's Meeting C++ 2022 talk on designing low-latency trading systems covers the engineering culture behind the techniques below: measure everything, keep the hot path boring, and treat every allocation as a decision.

Where C and C++ Sit in the Finance Stack

ComponentWhy C or C++What else is used
Market data feed handlersDecode binary exchange protocols at line rate with predictable latencyFPGA for the hottest path; Java at some venues
Order gateways and execution enginesDeterministic tick-to-trade; pre-trade risk checks in nanosecondsJava and C# in less latency-sensitive desks
Matching enginesMillions of messages per second with bounded latencyRust in newer venues, notably crypto
Pricing and risk librariesNumerical throughput for Monte Carlo, curves and Greeks; QuantLib is the open-source referencePython wrappers over C++ cores; GPU kernels
Kernel bypass drivers and hardware glueC for the driver boundary and embedded controlVendor SDKs
Strategy research and backtestingRarely; iteration speed matters more than execution speedPython, R, Pine Script on a chart

The last row matters. The work of finding a strategy is not C++ work, and firms that write research code in C++ move slowly. Our language comparison covers the division of labour, and the latency standards guide explains why microsecond engineering only pays when the venue is close enough for it to matter.

Toolchain

NeedToolsNotes
CompilerGCC, Clang, MSVCTarget C++17 or C++20; build with -O2 and warnings as errors; enable -march only for a known deployment CPU
BuildCMakeCross-platform; separate debug, release and sanitizer configurations
CorrectnessAddressSanitizer, UndefinedBehaviorSanitizer, ThreadSanitizer, ValgrindRun the test suite under sanitizers in CI; undefined behaviour is where trading bugs hide
TestingGoogleTest, Catch2Unit tests plus deterministic replay of recorded market data
MeasurementGoogle Benchmark, perf, std::chronoMicrobenchmarks for functions; perf for the whole process; chrono for in-production percentiles
Finance librariesQuantLib, BoostPricing engines, term structures and dates; general infrastructure
ConnectivityQuickFIX; exchange binary SDKsFIX for order routing where latency is not extreme; native binary protocols where it is

Project Layout

A layout that separates the hot path from everything else makes performance work tractable. Keep the feed decoder, strategy evaluation and order encoder in one module with no dependencies on logging, configuration or persistence; those live in adjacent modules and communicate through queues. Public headers go in include, implementation in src, tests in tests, and recorded market data for replay in a fixtures directory that CI can read. Name financial concepts precisely in code, Ticks not double, Qty not int, so that a wrong unit fails to compile instead of failing in production.

Technique 1: Represent Money and Time Exactly

Floating-point prices are the most common correctness bug in trading code. A double cannot represent most decimal prices exactly, so comparisons drift, sums accumulate error and two systems disagree about whether an order is marketable. Represent prices as integers in units of the instrument's tick size, quantities as integers in lots or shares, and convert to and from decimals only at the edges. For time, use std::chrono: a steady clock for latency measurement, because it never jumps, and the system clock for wall-clock timestamps that must line up with the venue's.

#include <chrono>
#include <cstdint>

using Ticks = std::int64_t;        // price in minimum increments, never a double
using Qty   = std::int64_t;        // quantity in lots or shares
using Nanos = std::chrono::nanoseconds;

struct Order {
    std::uint64_t id;
    Ticks price;
    Qty   qty;
    bool  is_buy;
    std::chrono::steady_clock::time_point created;   // monotonic, for latency
};

constexpr Ticks to_ticks(double px, double tick_size) {
    return static_cast<Ticks>(px / tick_size + (px >= 0 ? 0.5 : -0.5));
}

Strong types pay for themselves the first time someone passes a price where a quantity was expected. Wrapping the aliases in small structs with explicit constructors makes that a compile error; the aliases above are the minimum.

Technique 2: No Allocation on the Hot Path

Heap allocation is slow, its latency is unpredictable, and in a long-running process it fragments memory. The rule in latency-sensitive code is that everything the hot path needs is allocated at start-up: pre-sized vectors with reserve, object pools for orders and messages, fixed-capacity ring buffers for queues. Prefer contiguous containers, std::vector and std::array, over node-based ones such as std::map and std::list, because the CPU's cache rewards data that sits together. Where the operating system allows, lock the process's memory with mlock so pages are never swapped, and pin hot threads to dedicated cores.

Modern C++ features are not the enemy here. Smart pointers, RAII and lambdas belong in the control plane, where they prevent leaks and make ownership explicit. On the hot path, the discipline is simply that no code between packet arrival and order departure calls new, throws an exception, takes a mutex or writes to a file. Logging is queued to a separate thread; configuration is read at start-up; errors are flagged and handled after the order is out.

Technique 3: Move Data Between Threads Without Locks

Most trading processes have a natural pipeline: a thread that reads the network, a thread that runs the strategy, a thread that logs and persists. The standard way to connect them is a single-producer, single-consumer ring buffer built on std::atomic with acquire and release ordering. It is wait-free, involves no system calls, and its two indices sit on separate cache lines so the producer and consumer do not stall each other.

#include <array>
#include <atomic>
#include <cstddef>
#include <optional>

template <typename T, std::size_t N>
class SpscQueue {                          // exactly one producer thread and one consumer thread
    static_assert((N & (N - 1)) == 0, "N must be a power of two");
public:
    bool push(const T& item) {
        const auto head = head_.load(std::memory_order_relaxed);
        const auto next = (head + 1) & (N - 1);
        if (next == tail_.load(std::memory_order_acquire)) return false;     // full
        buffer_[head] = item;
        head_.store(next, std::memory_order_release);
        return true;
    }

    std::optional<T> pop() {
        const auto tail = tail_.load(std::memory_order_relaxed);
        if (tail == head_.load(std::memory_order_acquire)) return std::nullopt;   // empty
        T item = buffer_[tail];
        tail_.store((tail + 1) & (N - 1), std::memory_order_release);
        return item;
    }

private:
    std::array<T, N> buffer_{};
    alignas(64) std::atomic<std::size_t> head_{0};   // own cache line: no false sharing
    alignas(64) std::atomic<std::size_t> tail_{0};
};

The correctness argument is short: the producer publishes an item by storing head with release semantics after writing the slot, and the consumer's acquire load of head guarantees it sees that write. Anything more complicated, multiple producers, blocking waits, dynamic resizing, deserves a well-tested library rather than hand-written code, and every such queue should run under ThreadSanitizer in CI.

Technique 4: Decide at Compile Time, Branch Predictably

Virtual calls, exceptions and runtime polymorphism are fine in the control plane and costly on the hot path, where the compiler cannot see through them. Templates and constexpr move decisions to compile time, so a strategy parameterised on instrument type or message layout produces straight-line code. Keep branches predictable: the common case first, the error case marked with the C++20 unlikely attribute, and no branch on data that changes every message where a table lookup would do. Read the generated assembly for the few functions that matter; compilers are excellent, but only for the code they can see.

Technique 5: Measure Before You Optimise

LuxAlgo SuperTrend indicator on Quant Charts with a trailing stop line below price in an uptrend
The LuxAlgo SuperTrend on Quant Charts. A rule this simple should be prototyped and backtested on a chart in minutes before anyone decides it deserves a C++ implementation.

Latency is a distribution, and the average is the least useful number in it. Wrap the hot path in steady_clock timestamps, collect samples, and report the median, the 99th percentile and the maximum, then group the tail by time of day to find whether it comes from bursts, from the operating system or from your own allocations. For individual functions, Google Benchmark gives repeatable microbenchmarks; for the whole process, perf shows where cycles actually go, which is rarely where the developer guessed.

#include <algorithm>
#include <chrono>
#include <vector>

struct LatencyStats { double p50_us; double p99_us; double max_us; };

LatencyStats summarise(std::vector<double> samples_us) {
    std::sort(samples_us.begin(), samples_us.end());
    auto pct = [&](double p) {
        return samples_us[static_cast<std::size_t>(p * (samples_us.size() - 1))];
    };
    return { pct(0.50), pct(0.99), samples_us.back() };
}

// Around the hot path (samples is pre-reserved; never grows during trading hours):
const auto t0 = std::chrono::steady_clock::now();
handle_update(update);                                   // decode, decide, send
const auto t1 = std::chrono::steady_clock::now();
samples.push_back(std::chrono::duration<double, std::micro>(t1 - t0).count());

Two habits keep measurements honest. Take the timestamps at the same boundaries every time, so numbers are comparable across builds, and never compare a debug build with a release build. Optimisations should be accepted only when the 99th percentile moves; a change that improves the median and lengthens the tail has made the system worse.

Technique 6: Make Wrong Code Fail Loudly

C++ gives you the tools to write code that cannot leak, cannot overflow silently and cannot pass a price as a quantity; the discipline is to use them. RAII for every resource, the C++ Core Guidelines as the house style, sanitizers on every CI run, and unit tests with GoogleTest for every pricing and risk function. The most valuable test in a trading system is deterministic replay: record a day of market data, feed it through the system, and assert that the orders produced are byte-identical to the recorded output. Any change that alters the output is either a deliberate strategy change or a bug, and the test cannot tell the difference, which is exactly the point.

Finally, the controls regulators require of professional algorithmic traders belong in the gateway regardless of jurisdiction: price collars, maximum order size, a message-rate limit and a kill switch that cancels everything. They cost nanoseconds and prevent the failure that ends a firm. Our latency standards guide summarises what MiFID II's RTS 6 asks for.

QuantLib, Boost, and C Versus C++

QuantLib is the open-source C++ library for quantitative finance: instruments, pricing engines, term structures, calendars and day-count conventions, with bindings for Python and other languages. Use it for the derivatives mathematics rather than reimplementing it, and profile it before assuming it is fast enough for a hot path; it is designed for correctness and generality first. Boost supplies the general-purpose infrastructure, lock-free containers, interprocess communication, date and time utilities, that the standard library lacks or added late.

Plain C persists at the boundaries: kernel-bypass network drivers, hardware interfaces and the glue between an FPGA and the host. Its role is the driver boundary, reached from C++ through extern "C" interfaces; writing a whole trading system in C gives up RAII, templates and the standard library for no gain in speed.

Where Quant Charts Fits

The expensive mistake in this field is engineering a strategy in C++ before knowing whether the rule is worth anything. Quant Charts is where that question gets answered first. Describe the rule to Quant, a SuperTrend flip with a volatility filter, a crossover with a time-of-day restriction, whatever the C++ system is meant to trade, and Quant writes it in Pine Script and plots it on the active chart. Open Code to read the logic, click Run, and the Backtest Summary reports net profit, trade count, win rate, maximum drawdown and profit factor with the commission and slippage you set in the properties. If the rule cannot survive realistic costs there, no amount of C++ will rescue it, and if it can, the Pine Script is a precise specification for the engineering team. The Making Strategies with Quant guide shows the workflow.

Adding indicators in Quant Charts. Prototype and test the rule on the chart; reserve C++ for the parts of the system that need it.

Two further bridges. PineTS, LuxAlgo's TypeScript implementation of the Pine Script® language, lets the same logic run outside TradingView, which is useful when a research prototype needs to move into a service before the C++ port is justified. And the Library's Execution Cost Modeling indicator estimates the spread, slippage and commission a round trip really costs, which is the number a latency budget should be compared against. One boundary: the LuxAlgo platform does not place orders for you at a broker or venue, which is precisely the job the C++ gateway in this article exists to do.

Conclusion

C and C++ remain the languages of the finance hot path because they let an engineer decide exactly what the machine does and when. The techniques that make that power safe are not exotic: integers for money and chrono for time, no allocation or locking between packet and order, single-producer queues with acquire and release ordering, decisions moved to compile time, percentiles measured from a monotonic clock, and tests that replay a recorded day and demand identical output. Apply them to the components that need them, feed handlers, gateways, pricing loops, and leave research where it belongs, in Python or on a chart, where Quant can write and test the rule before anyone opens a C++ editor.

Key Takeaways

  • Right tool, right layer. C++ for deterministic hot paths; Python, R or Pine Script for research.
  • Exact types. Ticks and lots as integers, std::chrono for time, strong types so wrong units fail to compile.
  • Boring hot path. Pre-allocated, lock-free, exception-free, with logging queued to another thread.
  • Percentiles decide. steady_clock samples, p99 and maximum, Google Benchmark and perf.
  • Prototype on Quant Charts. Quant writes the Pine Script, Code shows it, Run backtests it; port to C++ only what survives.

FAQs

Why are C and C++ used in finance?

Because they compile to machine code, give explicit control of memory layout and lifetime, and have no garbage collector, which together make latency predictable. Those properties matter in market data feed handlers, order gateways, matching engines and pricing libraries. They matter far less in strategy research and retail bots, where iteration speed counts for more and Python or a chart platform is the better choice.

Should prices be stored as doubles in C++?

No. Most decimal prices have no exact binary representation, so doubles drift in comparisons and sums. Store prices as integers in units of the instrument's tick size and quantities as integers in lots or shares, converting to decimals only for display and external interfaces. Use doubles for model mathematics, not for ledgers or order books.

What does "no allocation on the hot path" mean?

Everything the code between packet arrival and order departure needs is allocated at start-up: pre-sized vectors, object pools and fixed-capacity ring buffers. During trading the hot path calls no new, throws no exceptions, takes no mutex and writes no files. Logging and persistence run on other threads fed by queues. This removes the unpredictable pauses that heap allocation and locking introduce.

How do I pass data between threads without locks in C++?

With a single-producer, single-consumer ring buffer built on std::atomic. The producer writes a slot and then stores the head index with release ordering; the consumer loads the head with acquire ordering, which guarantees it sees the write. Keep the two indices on separate cache lines with alignas(64) to avoid false sharing, and run the code under ThreadSanitizer. For multiple producers or blocking, use a tested library instead.

How should I measure latency in a C++ trading system?

Timestamp the hot path with std::chrono::steady_clock, collect samples into a pre-reserved vector, and report the median, 99th percentile and maximum rather than the mean. Use Google Benchmark for individual functions and perf for the whole process. Accept an optimisation only if it improves the tail; a change that helps the median but lengthens the 99th percentile has made the system worse.

How does Quant Charts relate to C++ trading systems?

It is where the strategy should be proven before engineering begins. Describe the rule to Quant and it writes it in Pine Script, which you can read in Code and run with Run; the Backtest Summary shows whether the rule survives commission and slippage. A surviving rule becomes the specification for the C++ implementation. The LuxAlgo platform does not place orders for you, so the gateway remains your own code.

References

LuxAlgo Resources

External Resources

Learn to trade smarter.

Market analysis and techniques that build your edge, one email a week.

Don’t worry, no spam here. See our privacy policy for more info.

Alex Pierrefeu
Alex Pierrefeu

CPO & Co-founder at LuxAlgo. 7+ years background of developing technical trading tools, Alex is one of the very few highlighted "Pine Script Wizards" on TradingView.

Read next