C++ for Quants
  • Home
  • News
  • Contact
  • About
Author

Clement D.

Clement D.

smart pointers in C++ for financial data
Data Structures

Smart Pointers for C++ Financial Data Structures: An Overview

by Clement D. June 29, 2025

Since C++11, smart pointers have become essential tools for managing memory safely and efficiently.
They eliminate the need for manual new/delete while enabling shared or exclusive ownership semantics. What are smart pointers for C++ financial data?

1. Raw Pointers vs Smart Pointers: The Evolution

C++ traditionally relied on raw pointers (T*) for dynamic memory management. While flexible, raw pointers come with risks: memory leaks, dangling references, and double deletes — especially in complex quant systems where ownership isn’t always clear.

Smart pointers, introduced in C++11, offer a safer alternative by wrapping raw pointers with automatic lifetime management.

C++11 introduced smart pointers, which wrap raw pointers with automatic memory management based on ownership models:

  • std::unique_ptr<T>: sole ownership, cannot be copied.
  • std::shared_ptr<T>: shared ownership via reference counting.
  • std::weak_ptr<T>: non-owning reference to break cycles.
  • std::make_shared<T>() / std::make_unique<T>(): factory functions for safe allocation.

Consider this classic raw pointer pattern, here with a dummy YieldCurve object:

YieldCurve* curve = new YieldCurve(...);
// use curve
delete curve; // ❌ must remember to call this, or risk a leak

With std::shared_ptr, you simplify and secure ownership:

auto curve = std::make_shared<YieldCurve>(...); // ✅ deleted automatically when last owner goes out of scope

2. Heap-Allocated Financial Structures and Smart Pointers

In financial systems, data structures like price histories, risk vectors, or trade buckets are often heap-allocated.

It’s either because of their size or because they need to be shared across multiple components or threads. Managing their lifetime correctly is crucial to avoid memory leaks or invalid access.

Let’s take a practical example of such structures: std::vector. A vector is stored on the stack, while its internal elements are managed on the heap.

void computePnL() {
    std::vector<double> pnl = {120.5, -75.2, 30.0};  // stack-allocated vector
    double total = 0.0;
    for (double p : pnl) {
        total += p;
    }
    std::cout << "Total PnL: " << total << std::endl;
} // ✅ `pnl` is automatically destroyed when it goes out of scope

So a value-based vector would look like this in memory:

The stack contains basic metadata about the vector and a pointer to the heap block where the values are actually defined.

How would a vector smart shared pointer look like?

It would be a pointer defined on the stack pointing to a vector object on the heap that contains a pointer towards the actual values also on the heap. Yes, it’s a bit nested!

When the last std::shared_ptr to a heap-allocated object goes out of scope or is reset, the internal reference count drops to zero.

At that moment, unlike raw pointers, the managed object (in this case, the std::vector<double>) is automatically destroyed, and its memory is released. This ensures safe, deterministic cleanup without manual delete calls.

Let’s see with an example for shared pointers:

#include <iostream>
#include <memory>
#include <vector>

void createVector() {
    auto pnl = std::make_shared<std::vector<double>>(std::initializer_list<double>{120.5, -75.2, 30.0});
    std::cout << "Inside createVector(), use_count: " << pnl.use_count() << std::endl;
} // 🔥 When pnl goes out of scope, vector is destroyed (ref count drops to 0)

int main() {
    createVector();
    std::cout << "Back in main()" << std::endl;
}

When pnl goes out of scope, vector is destroyed (ref count drops to 0) automatically.

Now, let’s talk about std::unique_ptr<T>:

While shared_ptr supports shared ownership with reference counting, unique_ptr enforces strict single ownership.

It cannot be copied, only moved making ownership transfer explicit and safe.

#include <iostream>
#include <memory>
#include <vector>

std::unique_ptr<std::vector<double>> createVector() {
    auto pnl = std::make_unique<std::vector<double>>(std::initializer_list<double>{100.0, -20.0, 50.0});
    std::cout << "Inside createVector(), vector size: " << pnl->size() << std::endl;
    return pnl; // ownership is transferred (moved) to the caller
}

int main() {
    auto vecPtr = createVector(); // vecPtr now owns the vector
    std::cout << "Back in main(), first value: " << vecPtr->at(0) << std::endl;

    // auto copy = vecPtr; ❌ This won't compile: unique_ptr cannot be copied
    auto moved = std::move(vecPtr); // ✅ Ownership moved
    if (!vecPtr)
        std::cout << "vecPtr is now null after move.\n";
}

This shows how unique_ptr ensures exclusive ownership: once moved, the original pointer becomes null, and the resource is safely destroyed when the final owner goes out of scope.

Now, let’s talk about std::weak_ptr<T>:

Unlike shared_ptr, a weak_ptr does not contribute to the reference count. It’s designed for non-owning references that safely observe a shared_ptr, managed resource, especially useful to break cyclic references in shared ownership scenarios (like parent ↔ child graphs or caches).

Here’s a minimal example:

#include <iostream>
#include <memory>
#include <vector>

void observeVector() {
    std::shared_ptr<std::vector<int>> data = std::make_shared<std::vector<int>>(std::initializer_list<int>{1, 2, 3});
    std::weak_ptr<std::vector<int>> weakData = data; // 👀 weak observer

    std::cout << "use_count: " << data.use_count() << std::endl;

    if (auto locked = weakData.lock()) { // Try to temporarily access
        std::cout << "Accessed value: " << locked->at(0) << std::endl;
    }
    
    data.reset(); // destroy the shared_ptr

    if (auto locked = weakData.lock()) {
        std::cout << "Still alive.\n";
    } else {
        std::cout << "Resource expired.\n";
    }
}

std::weak_ptr is ideal for temporary, non-owning access to a shared resource. It lets you safely check if the object is still alive without extending its lifetime: perfect for avoiding cyclic references and building efficient observers or caches.

Said differently: std::weak_ptr is a smart pointer specifically designed to observe the lifecycle of a std::shared_ptr without affecting its ownership or reference count.

3. Overhead of Using Smart Pointers in HFT

While smart pointers offer safety and convenience, they do introduce runtime overhead, especially std::shared_ptr.

🏎️ unique_ptr, by contrast, is zero-overhead in release builds — it’s just a thin RAII wrapper around a raw pointer with no reference counting.

🔁 shared_ptr maintains a reference count (typically via an internal control block). Each copy or reset updates this count atomically, which adds thread-safe synchronization costs.

🔒 weak_ptr shares this control block, adding some memory overhead, though access via .lock() is generally efficient.

In performance-critical, low-latency systems (e.g. HFT), overuse of shared_ptr can introduce cache pressure and latency spikes. It’s crucial to profile and use them only where ownership semantics justify the cost.

Each std::shared_ptr has an associated control block (on the heap) storing:

  • use_count (shared references)
  • weak_count

Every copy, assignment, or destruction updates use_count atomically:

std::shared_ptr<T> a = b;  // atomic increment

These atomic ops:

  • May invalidate CPU cache lines
  • Cause false sharing if multiple threads access nearby memory
  • Require synchronization fences, introducing latency

Each shared_ptr object requires a heap allocation for its control block. This:

  • Adds malloc/free overhead
  • Increases memory fragmentation
  • May cause non-contiguous accesses, leading to cache misses

Also, destroying a shared_ptr with many references (e.g., in a tree or graph) may lead to:

  • Long chain deletions
  • Blocking deallocation spikes
  • Potential STW-like moments under multi-threaded pressure

4. Summary

Here’s a clear summary table of the key points we’ve covered:

TopicKey PointsNotes / Code Snippet
Smart Pointer Typesunique_ptr, shared_ptr, weak_ptrmake_shared<T>() preferred over raw new
unique_ptrSingle ownership, zero overhead, auto-destroyCannot be copied, only moved
shared_ptrReference-counted ownership, auto-destroyCopies increment ref count atomically
weak_ptrNon-owning observer of a shared_ptrUse .lock() to access if still alive
Stack vs Heap Allocationstd::vector<T> is heap-allocated but wrapper can be stack-allocatedstd::array<T, N> is truly stack-allocated
Why Smart Pointers?Automate memory management, avoid leaks, support ownership semanticsUseful when passing around heap structures like vectors or trees
Ownership Model ExampleReturn shared_ptr from a function, pass to othersReuse across functions and threads safely
Dangling Pointer ExampleRaw pointer to stack-allocated object leads to UBstd::shared_ptr copies avoid this
Diagram Conceptsshared_ptr → vector → heap dataMemory is layered; ownership tracked by control block
Control Block (shared_ptr)Stores ref counts, lives on heapIntroduces atomic ops and heap churn
Overhead IssuesCache pressure, false sharing, heap fragmentation, latency spikesProfiling tools: perf, Valgrind, VTune, Heaptrack
Monitoring Smart Pointer UsageAtomic op count, heap allocations, cache miss ratesAvoid in tight loops or critical paths
Use CasesLong-lived financial structures (e.g. Yield Curves, Risk Matrices)Enables clean sharing, esp. across threads

The code for the article is available here:

https://github.com/cppforquants/smartpointers/tree/main

June 29, 2025 0 comments
Greek Vega Calculation C++
Greeks

Option Greeks: Vega Calculation in C++ Explained

by Clement D. June 28, 2025

Vega plays a vital role for trades. It measures how much the price of an option changes with respect to changes in volatility. In this article, we’ll break down the concept of Vega, explore its importance in options trading, and walk through a clean and efficient implementation in C++, the language of choice for many high-performance financial systems. Whether you’re building a derivatives pricing engine or just deepening your understanding of options, this guide will provide both the theory and the code to get you started. Let’s work on this Vega calculation in C++!

1. What’s Vega?

Let’s derive Vega from the Black-Scholes formula for a European call option.

This is Black-Scholes for a European call option:

[math] \Large C = S \cdot N(d_1) – K e^{-rT} \cdot N(d_2) [/math]

Where:

  • [math] S [/math]: spot price
  • [math] K [/math]: strike price
  • [math] r [/math]: risk-free rate
  • [math] T [/math]: time to maturity (in years)
  • [math] \sigma [/math]: volatility
  • [math] N(\cdot) [/math]: cumulative distribution function of the standard normal distribution

With:

[math] \Large d_1 = \frac{\ln(S/K) + (r + \frac{1}{2}\sigma^2)T}{\sigma \sqrt{T}} [/math]

And:

[math] \Large d_2 = d_1 – \sigma \sqrt{T} [/math]

To compute Vega, we differentiate the Black-Scholes price with respect to [math] \sigma [/math], valid for both call and put optiions:

[math] \Large \text{Vega} = \frac{\partial C}{\partial \sigma} [/math]

After applying calculus and simplifying:

[math] \Large \text{Vega} = S \cdot \phi(d_1) \cdot \sqrt{T} [/math]

Where [math] \phi(d) [/math] is the standard normal probability density function:

[math] \Large \phi(d) = \frac{1}{\sqrt{2\pi}} e^{-d^2 / 2} [/math]

All the terms are positive (spot price, density function and square root of time to maturity).

✅ Summary:

  • Vega is positive for both call and put options as all the terms of the formula are positive.
  • It reaches its maximum at-the-money, where [math] S \approx K [/math].
  • Vega declines as expiration approaches.
  • It is essential for volatility trading strategies.

2. Implementation in C++: A Vanilla Snippet

Important assumptions of the following implementation of the vega calculation in C++:

  • The option is European (no early exercise).
  • We use the Black-Scholes model.

Let’s simply implement the formula defined in the previous section in a vega.cpp file:

#include <iostream>
#include <cmath>

// Compute standard normal PDF without hardcoding pi
double normal_pdf(double x) {
    static const double inv_sqrt_2pi = 1.0 / std::sqrt(2.0 * std::acos(-1.0));
    return inv_sqrt_2pi * std::exp(-0.5 * x * x);
}

// Compute d1 for Black-Scholes formula
double d1(double S, double K, double r, double T, double sigma) {
    return (std::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * std::sqrt(T));
}

// Compute Vega
double vega(double S, double K, double r, double T, double sigma) {
    double d1_val = d1(S, K, r, T, sigma);
    return S * normal_pdf(d1_val) * std::sqrt(T);
}

// Example usage
int main() {
    double S = 100.0;     // Spot price
    double K = 100.0;     // Strike price
    double r = 0.05;      // Risk-free rate
    double T = 1.0;       // Time to maturity (years)
    double sigma = 0.2;   // Volatility

    double v = vega(S, K, r, T, sigma);
    std::cout << "Vega: " << v << std::endl;

    return 0;
}

Let’s use a reasonable CMakeLists.txt to be able to compile the file above:

cmake_minimum_required(VERSION 3.10)
project(vega)
set(CMAKE_CXX_STANDARD 17)
add_executable(vega ../vega.cpp)

Let’s create a build directory and compile:

mkdir build
cd build
comake ..
make

Then run the executable:

➜  build make       
[ 50%] Building CXX object CMakeFiles/vega.dir/vega.cpp.o
[100%] Linking CXX executable vega
[100%] Built target vega

➜  build ./vega
Vega: 37.524

Interpretation:

Vega = 37.52 means that if the implied volatility increases by 1 percentage point (0.01), the option’s price increases by approximately 0.3752 units.

So for a 5-point move in volatility (e.g. from 20% to 25%), the option price would rise by ~1.88 units.

Why is it so “high”?

Because:

  • The option is at-the-money.
  • The time to expiry is 1 year.
  • Vega is proportional to [math]S \cdot \sqrt{T}[/math].
  • The PDF value [math]\phi(d_1)[/math] is near its peak at [math]d_1 \approx 0[/math].

If you changed any of the following:

  • Time to maturity ↓ → Vega ↓
  • Move far in- or out-of-the-money → Vega ↓
  • Lower spot price → Vega ↓

3. How would you calculate it for an American-Style option?

Since there’s no closed-form solution for American-style options, we would estimate Vega using a numerical approach:

General Steps

  1. Choose a pricing model that supports American options:
    • Most common: binomial tree
  2. Define base parameters:
    Spot price [math]S[/math], strike [math]K[/math], rate [math]r[/math], time [math]T[/math], and volatility [math]\sigma[/math]
  3. Pick a small increment for volatility (e.g., [math]\epsilon = 0.01[/math])
  4. Compute option prices at [math]\sigma + \epsilon[/math] and [math]\sigma – \epsilon[/math]
    using your American option pricing model (e.g., binomial tree):

[math] \Large V_+ = \text{Price}(\sigma + \epsilon) [/math]

[math] \Large V_- = \text{Price}(\sigma – \epsilon) [/math]

Estimate Vega using central difference:

[math] \Large \text{Vega} \approx \frac{V_+ – V_-}{2 \epsilon} [/math]

This is just to give you a taste of it, we will do a vega calculation in C++ for American-style options in another article.

Code of the article with Readme for compilation and execution:

https://github.com/cppforquants/vega/tree/main

June 28, 2025 0 comments
Citadel article
Jobs

Citadel: Top Performing Hedge Fund for C++ Quant Developers

by Clement D. June 27, 2025

Founded by Ken Griffin in 1990, Citadel has evolved into a global powerhouse, consistently delivering top-tier returns across its multi-strategy portfolios. But behind its alpha-generating engine lies an often underappreciated force: the C++ quantitative developers who architect the low-latency systems, model risk in real time, and turn market chaos into structured opportunity.

1. The Rise of Citadel

Founded in 1990 by Ken Griffin with just over $4 million in capital, Citadel has grown into one of the most successful hedge funds in history. Headquartered in Chicago, it now manages over $60 billion in assets and operates across the globe, including offices in New York, London, Hong Kong, and beyond.

Citadel is not a single-strategy fund—it’s a multi-strategy powerhouse, deploying capital across equities, fixed income, commodities, credit, and quantitative strategies. This diversification, paired with robust risk management, has made the firm highly resilient through market cycles.

What sets Citadel apart isn’t just its performance—though returns like 38% in 2022 certainly command attention—but its deep integration of technology and talent. The firm runs on infrastructure built for speed, scale, and precision, and this is where quantitative developers come in.

Its flagship Wellington fund has outperformed many rivals, even in turbulent years. Citadel’s ability to consistently extract alpha is often attributed to its collaborative model, where engineers, researchers, and portfolio managers work shoulder-to-shoulder.

The firm’s reputation as a developer-first hedge fund has drawn some of the world’s top C++ talent. At Citadel, engineering is not support—it’s strategy.

With a culture that values innovation, ruthless execution, and data-driven decision making, Citadel has become more than a hedge fund—it’s a financial technology juggernaut.

2. C++ at Citadel

Citadel operates at nanosecond precision. That level of performance demands fine-grained control over memory, concurrency, and CPU cache: all things C++ excels at. Unlike higher-level languages, C++ lets developers write code that talks directly to the hardware, eliminating unnecessary overhead.

The firm’s systems are responsible for processing millions of market messages per second, reacting to market events in real time, and executing trades across global venues. This wouldn’t be possible without the deterministic behavior and performance characteristics of C++.

But Citadel’s use of C++ isn’t limited to execution systems. Quant researchers rely on C++ libraries to backtest strategies, model derivatives, and run complex simulations at scale. Its computational efficiency makes it ideal for workloads that can’t afford latency or garbage collection pauses.

Citadel also invests heavily in modern C++, adopting features from C++17 and C++20 where they offer measurable gains in safety, readability, or performance. Engineers are encouraged to write clean, maintainable code—but never at the cost of latency.

Citadel’s tech culture rewards those who can profile, benchmark, and optimize. The best C++ quants here don’t just code—they engineer alpha.

3. Salaries at Citadel

  • Citadel Securities Quantitative Developer / Research Engineer roles have a base salary range of $250,000–$350,000, with discretionary incentive bonuses on top teamblind.com+12citadelsecurities.com+12teamblind.com+12.
  • Central Risk Services Quant Dev positions offer a lower base range of $150,000–$300,000, plus discretionary bonuses and full benefits investopedia.com+3citadel.com+3glassdoor.com+3teamblind.com+1fnlondon.com+1.
  • According to Levels.fyi, software engineering roles (which often include quant roles) at Citadel report total compensation between $369K (L1) up to $586K (L5), with a U.S. median of $435K citadelsecurities.com+11levels.fyi+11indeed.com+11teamblind.com+1indeed.com+1.
  • On Glassdoor/Indeed, reported base pay averages range widely:
    • Glassdoor reports ~$224K/year average for Quant Dev teamblind.comreddit.com+7glassdoor.com+7teamblind.com+7.
    • Indeed reports around $170K/year, with ranges from $85K–$257K teamblind.com+4indeed.com+4glassdoor.com+4teamblind.com+5teamblind.com+5levels.fyi+5.
  • Reddit/industry anecdotes suggest: “Quant Developers: $300k–1M”
    Contributions near alpha can push compensation toward the high end fnlondon.comreddit.com+5reddit.com+5levels.fyi+5.
  • QuantNet survey indicates for top-tier quant roles (2–5 years of experience):
    • First-year total comp spans $350K–$625K (base $150K–$300K + sign-on $50K–$200K + bonus $75K–$150K) fnlondon.com+11quantnet.com+11reddit.com+11.
    • By 5 years, total comp may reach $800K–$1.2M with solid performance.
  • TeamBlind insights highlight that senior quant devs at Citadel can expect $650K–$900K total compensation, especially for C++ specialists quantnet.com+1reddit.com+1reddit.com+11teamblind.com+11teamblind.com+11efinancialcareers.com+3businessinsider.com+3reddit.com+3.

Summary Snapshot

Role / SeniorityBase SalaryTotal Compensation
Entry Quant/Research Engineer$150K–$300K$250K–$400K+
Junior SWE / Quant at Citadel Securities$250K–$350K$350K–$600K (Levels.fyi data)
U.S. Median SWE @ Citadel (all levels)—$435K
Mid-level (2–5 yrs experience)$150K–$300K base$350K–$625K
Senior (5+ yrs, high-performers)N/A$800K–$1.2M+
Top-tier C++ Quant Dev (High perf, senior)—$650K–$900K (and above)

✅ Key Takeaways

  1. Base ranges: Typically $150K–$300K depending on team and experience.
  2. Total compensation: Often $350K–$600K early in career, with senior/high-impact quants exceeding $800K and potentially hitting $1M+.
  3. Bonuses & incentives: Discretionary, performance-based, and can include multi-year vesting or trading revenue share.
  4. C++ expertise is highly prized—especially for roles touching low-latency systems—often commanding top-end packages.

4. Conclusion on Citadel

Citadel is a technology-driven trading powerhouse where engineering excellence meets financial strategy. For C++ developers, it’s a rare environment where your code directly moves markets, influences multi-billion-dollar portfolios, and demands world-class technical execution.

Its use of cutting-edge C++, relentless focus on performance, and collaborative culture put it at the frontier of quantitative finance. From optimizing nanosecond latency to building scalable real-time infrastructure, developers at Citadel solve problems that few other places even attempt.

Compensation reflects this intensity: total packages rival top tech firms and reward measurable impact. But beyond the pay, Citadel offers a sense of ownership and precision that appeals to the most driven minds in systems programming and quantitative research.

For those who thrive under pressure, care deeply about system-level performance, and want to work alongside the best in finance and engineering: Citadel is the summit. And C++ is one of the best languages that gets you there.

June 27, 2025 0 comments
cost-of-carry futures
Futures

Pricing Futures Using Cost-of-Carry in C++

by Clement D. June 26, 2025

Futures contracts derive their value from the underlying asset, but their price is not always equal to the spot price. The cost-of-carry model explains this difference by accounting for interest rates, storage costs, and dividends. It’s a fundamental concept in pricing futures and understanding arbitrage relationships. In this article, we’ll break down the formula and implement a clean, flexible version in C++. Whether you’re pricing equity, commodity, or FX futures, this model offers a solid foundation. How to calculate the cost-of-carry in C++?

1. What’s a Future?

A future is a standardized financial contract that obligates the buyer to purchase, or the seller to sell, an underlying asset at a predetermined price on a specified future date.

Unlike forwards, futures are traded on exchanges and are marked to market daily, meaning gains and losses are settled each day until the contract expires. They are commonly used for hedging or speculation across a wide range of assets, including commodities, equities, interest rates, and currencies.

Futures contracts help investors manage risk by locking in prices, and they also play a key role in price discovery in global markets.

2. The Cost-of-Carry Formula

The cost-of-carry model provides a theoretical price for a futures contract based on the current spot price of the asset and the costs (or benefits) of holding the asset until the contract’s expiration. These costs include financing (via interest rates), storage (for physical goods), and dividends or income lost by holding the asset instead of investing it elsewhere.

The formula is:

[math] \Large F_t = S_t \cdot e^{(r + u – q)(T – t)} [/math]

Where:

  • [math] F_t [/math]: Theoretical futures price at time [math] t [/math]
  • [math] S_t [/math]: Spot price of the underlying asset
  • [math] r [/math]: Risk-free interest rate (annualized)
  • [math] u [/math]: Storage cost (as a percentage, annualized)
  • [math] q [/math]: Dividend yield or convenience yield (annualized)
  • [math] T – t [/math]: Time to maturity in years

This formula assumes continuous compounding. In practice:

  • For equity index futures, [math] u = 0 [/math], but [math] q > 0 [/math]
  • For commodities like oil or gold, [math] u > 0 [/math], and [math] q = 0 [/math]
  • For FX futures, [math] r [/math] and [math] q [/math] represent the interest rate differential between two currencies

The cost-of-carry explains why futures can trade at a premium or discount to the spot price, depending on these inputs.

An example of this cost, when the spot price stays the same:

Now how to calculate the cost-of-carry in C++?

3. A Flexible C++ Implementation

Below is a self-contained example that takes spot price, interest rate, storage cost, dividend yield, and time to maturity as inputs and outputs the futures price.

Here’s a complete, ready-to-run C++ program:

#include <iostream>
#include <cmath>

struct FuturesPricingInput {
    double spot_price;
    double risk_free_rate;
    double storage_cost;
    double dividend_yield;
    double time_to_maturity; // in years
};

double price_futures(const FuturesPricingInput& input) {
    double exponent = (input.risk_free_rate + input.storage_cost - input.dividend_yield) * input.time_to_maturity;
    return input.spot_price * std::exp(exponent);
}

int main() {
    FuturesPricingInput input {
        100.0,   // spot_price
        0.05,    // risk_free_rate (5%)
        0.01,    // storage_cost (1%)
        0.02,    // dividend_yield (2%)
        0.5      // time_to_maturity (6 months)
    };

    double futures_price = price_futures(input);
    std::cout << "Futures price: " << futures_price << std::endl;

    return 0;
}

Let’s write that code in a futures.cpp file, create a CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
project(futures)
set(CMAKE_CXX_STANDARD 17)
add_executable(futures ../futures.cpp)

And compile it:

mkdir build 
cd build
cmake ..
make

The result is the price of our future:

➜  build ./futures 
Futures price: 102.02
June 26, 2025 0 comments
Gamma Greek Calculation
Greeks

Options Greeks in C++: Derive and Calculate Gamma

by Clement D. June 26, 2025

The Greeks are a set of metrics that describe these sensitivities. Among them, Gamma measures how fast Delta (the sensitivity of the option price to the underlying price) changes as the underlying price itself changes. So, how to calculate gamma?

In this article, we’ll focus on deriving the Gamma of a European option under the Black-Scholes model and implementing its calculation in C++. Whether you’re building a pricing engine or refining a hedging strategy, Gamma plays a key role in capturing the curvature of your position’s risk profile.

1. What is Gamma?

Gamma is one of the key Greeks in options trading. It measures the rate of change of Delta with respect to changes in the underlying asset price.

In other words:

  • Delta tells you how much the option price changes when the underlying asset moves by a small amount.
  • Gamma tells you how much Delta itself changes when the underlying price moves.

Interpretation:

  • High Gamma means Delta is very sensitive to price changes — common for near-the-money options with short time to expiry.
  • Low Gamma indicates Delta is more stable — typical for deep in-the-money or far out-of-the-money options.

2. The Gamma Formula

In the context of options pricing, Gamma arises naturally from the Black-Scholes Partial Differential Equation, which describes how the value of an option V(S,t) evolves with respect to the underlying asset price S and time t:

[math]\Large \frac{\partial V}{\partial t} + \frac{1}{2} \sigma^2 S^2 \frac{\partial^2 V}{\partial S^2} + r S \frac{\partial V}{\partial S} – r V = 0[/math]

In this equation:

  • [math]\frac{\partial V}{\partial S}[/math] is Delta
  • [math]\frac{\partial^2 V}{\partial S^2}[/math] is Gamma
  • [math]r[/math] is the risk-free rate
  • [math]\sigma[/math] is the volatility

From the Black-Scholes PDE, we isolate Gamma as the second derivative of the option value with respect to the underlying price. Under the Black-Scholes model, the closed-form expression for Gamma is:

[math]\Large \Gamma = \frac{N'(d_1)}{S \cdot \sigma \cdot \sqrt{T}}[/math]

Where:

  • [math]N'(d_1)[/math] is the standard normal probability density at [math]d_1[/math]
  • [math]S[/math] is the spot price of the underlying asset
  • [math]\sigma[/math] is the volatility
  • [math]T[/math] is the time to expiration

To derive the closed-form for Gamma, we start from the Black-Scholes formula for the price of a European call option:

[math]\Large C = S N(d_1) – K e^{-rT} N(d_2)[/math]

Here, the dependence on the spot price [math]S[/math] is explicit in both [math]S N(d_1)[/math] and [math]d_1[/math] itself, which is a function of [math]S[/math]:

[math]\Large d_1 = \frac{\ln(S/K) + (r + \frac{1}{2} \sigma^2)T}{\sigma \sqrt{T}}[/math]

To compute Gamma, we take the second partial derivative of [math]C[/math] with respect to [math]S[/math]. This involves applying the chain rule twice due to [math]d_1[/math]’s dependence on [math]S[/math].

After simplification (and canceling terms involving [math]d_2[/math]), what remains is:

[math]\Large \Gamma = \frac{N'(d_1)}{S \cdot \sigma \cdot \sqrt{T}}[/math]

This elegant result shows that Gamma depends only on the standard normal density at [math]d_1[/math], scaled by spot price, volatility, and time.

3. Implementation in Vanilla C++

Now that we’ve derived the closed-form formula for Gamma, let’s implement it in C++. The following code calculates the Gamma of a European option using the Black-Scholes model. It includes:

  • A helper to compute the standard normal PDF
  • A function to compute d1d_1d1​
  • A gamma function implementing the formula
  • A main() function that runs an example with sample inputs

Here’s the complete implementation to calculate gamma:

#include <iostream>
#include <cmath>

// Standard normal probability density function
double norm_pdf(double x) {
    return (1.0 / std::sqrt(2 * M_PI)) * std::exp(-0.5 * x * x);
}

// Compute d1 used in the Black-Scholes formula
double compute_d1(double S, double K, double r, double sigma, double T) {
    return (std::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * std::sqrt(T));
}

// Compute Gamma using the closed-form Black-Scholes formula
double gamma(double S, double K, double r, double sigma, double T) {
    double d1 = compute_d1(S, K, r, sigma, T);
    return norm_pdf(d1) / (S * sigma * std::sqrt(T));
}

int main() {
    double S = 100.0;     // Spot price
    double K = 100.0;     // Strike price
    double r = 0.05;      // Risk-free interest rate
    double sigma = 0.2;   // Volatility
    double T = 1.0;       // Time to maturity in years

    double gamma_val = gamma(S, K, r, sigma, T);
    std::cout << "Gamma: " << gamma_val << std::endl;

    return 0;
}

After compilation, running the executable will calculate gamma:

➜  build ./gamma        
Gamma: 0.018762

Code of the article with Readme for compilation and execution:

https://github.com/cppforquants/gamma/tree/main

June 26, 2025 0 comments
automatic diff
Greeks

Using Automatic Differentiation for Greeks Computation in C++

by Clement D. June 25, 2025

Traders and risk managers rely on accurate values for Delta, Gamma, Vega, and other Greeks to hedge portfolios and manage exposure. Today, we’re going to see how to use automatic differentiation for greeks calculation.

Traditionally, these sensitivities are calculated using finite difference methods, which approximate derivatives numerically. While easy to implement, this approach suffers from trade-offs between precision, performance, and stability. Enter Automatic Differentiation (AD): a modern technique that computes derivatives with machine precision and efficient performance.

In this article, we explore how to compute Greeks using automatic differentiation in C++ using the lightweight AD library Adept.

1. Background: Greeks and Finite Differences

The Delta of a derivative is the rate of change of its price with respect to the underlying asset price:

[math] \Large {\Delta = \displaystyle \frac{\partial V}{\partial S}} [/math]

With finite differences, we might compute:

[math] \Large { \Delta \approx \frac{V(S + \epsilon) – V(S)}{\epsilon} } [/math]

This introduces:

  • Truncation error if ε is too large,
  • Rounding error if ε is too small,
  • And 2N evaluations for N Greeks.

2. Automatic Differentiation: Another Way

Automatic Differentiation (AD) is a technique for computing exact derivatives of functions expressed as computer programs. It is not the same as:

  • Numerical differentiation (e.g., finite differences), which approximates derivatives and is prone to rounding/truncation errors.
  • Symbolic differentiation (like in computer algebra systems), which manipulates expressions analytically but can become unwieldy and inefficient.

AD instead works by decomposing functions into elementary operations and systematically applying the chain rule to compute derivatives alongside function evaluation.

🔄 How It Works

  1. Operator Overloading (in C++)
    AD libraries in C++ (like Adept or CppAD) define a custom numeric type—say, adouble—which wraps a real number and tracks how it was computed. Arithmetic operations (+, *, sin, exp, etc.) are overloaded to record both the value and the derivative.
  2. Taping
    During function execution, the AD library records (“tapes”) every elementary operation (e.g., x * y, log(x), etc.) into a computational graph. Each node stores the local derivative of the output with respect to its inputs.
  3. Chain Rule Application
    Once the function is evaluated, AD applies the chain rule through the computational graph to compute the final derivatives.
    • Forward mode AD: computes derivatives with respect to one or more inputs.
    • Reverse mode AD: computes derivatives of one output with respect to many inputs (more efficient for scalar-valued functions with many inputs, like in ML or risk models).

🎯 Why It’s Powerful

  • Exact to machine precision (unlike finite differences)
  • Fast and efficient: typically only 5–10x the cost of the original function
  • Compositional: works on any function composed of differentiable primitives, no matter how complex

📦 In C++ Quant Contexts

AD shines in quant applications where:

  • Analytical derivatives are difficult to compute or unavailable (e.g., path-dependent or exotic derivatives),
  • Speed and accuracy matter (e.g., calibration loops or sensitivity surfaces),
  • You want to avoid hard-coding Greeks and re-deriving math manually.

3. C++ Implementation Using CppAD

Install CppAD and link it with your C++ project:

git clone https://github.com/coin-or/CppAD.git
cd CppAD
mkdir build
cd build
cmake ..
make
make install

    Then link the library to your CMakeLists.txt:

    cmake_minimum_required(VERSION 3.10)
    project(CppADGreeks)
    
    set(CMAKE_CXX_STANDARD 17)
    
    # Find and link CppAD
    find_path(CPPAD_INCLUDE_DIR cppad/cppad.hpp PATHS /usr/local/include)
    find_library(CPPAD_LIBRARY NAMES cppad_lib PATHS /usr/local/lib)
    
    if (NOT CPPAD_INCLUDE_DIR OR NOT CPPAD_LIBRARY)
        message(FATAL_ERROR "CppAD not found")
    endif()
    
    include_directories(${CPPAD_INCLUDE_DIR})
    link_libraries(${CPPAD_LIBRARY})
    
    # Example executable
    add_executable(automaticdiff ../automaticdiff.cpp)
    target_link_libraries(automaticdiff ${CPPAD_LIBRARY})

    And the following code is in my automaticdiff.cpp:

    #include <cppad/cppad.hpp>
    #include <iostream>
    #include <vector>
    #include <cmath>
    
    template <typename T>
    T norm_cdf(const T& x) {
        return 0.5 * CppAD::erfc(-x / std::sqrt(2.0));
    }
    
    int main() {
        using CppAD::AD;
    
        std::vector<AD<double>> X(1);
        X[0] = 105.0;  // Spot price
    
        CppAD::Independent(X);
    
        double K = 100.0, r = 0.05, sigma = 0.2, T = 1.0;
        AD<double> S = X[0];
    
        AD<double> d1 = (CppAD::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * std::sqrt(T));
        AD<double> d2 = d1 - sigma * std::sqrt(T);
    
        AD<double> price = S * norm_cdf(d1) - K * CppAD::exp(-r * T) * norm_cdf(d2);
    
        std::vector<AD<double>> Y(1);
        Y[0] = price;
    
        CppAD::ADFun<double> f(X, Y);
    
        std::vector<double> x = {105.0};
        std::vector<double> delta = f.Jacobian(x);
    
        std::cout << "Call Price: " << CppAD::Value(price) << std::endl;
        std::cout << "Delta (∂Price/∂S): " << delta[0] << std::endl;
    
        return 0;
    }
    

    After compiling and running this code, we get a delta:

    ➜  build ./automaticdiff
    Call Price: 13.8579
    Delta (∂Price/∂S): 0.723727

    4. Explanation of the Code

    Here’s the key part of the code again:

    cppCopierModifierstd::vector<AD<double>> X(1);
    X[0] = 105.0;  // Spot price S
    CppAD::Independent(X);       // Start taping operations
    
    // Constants
    double K = 100.0, r = 0.05, sigma = 0.2, T = 1.0;
    AD<double> S = X[0];
    
    // Black-Scholes price formula
    AD<double> d1 = (CppAD::log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * std::sqrt(T));
    AD<double> d2 = d1 - sigma * std::sqrt(T);
    AD<double> price = S * norm_cdf(d1) - K * CppAD::exp(-r * T) * norm_cdf(d2);
    
    std::vector<AD<double>> Y(1); Y[0] = price;
    CppAD::ADFun<double> f(X, Y);  // Create a differentiable function f: S ↦ price
    
    std::vector<double> x = {105.0};
    std::vector<double> delta = f.Jacobian(x);  // Get ∂price/∂S

    ✅ What This Code Does (Conceptually)

    Step 1 – You Define a Function:

    The code defines a function f(S) = Black-Scholes call price as a function of spot S. But unlike normal code, you define this using AD types (AD<double>), so CppAD can trace the computation.

    Step 2 – CppAD Records All Operations:

    When you run this line:

    CppAD::Independent(X);

    CppAD starts building a computational graph. Every operation you do on S (which is X[0]) is recorded:

    • log(S / K)
    • d1
    • d2
    • norm_cdf(d1)
    • …
    • final result price

    This is like taping a math program:
    CppAD now knows how the output (price) was built from the input (S).

    Step 3 – You “seal” the function:

    CppAD::ADFun<double> f(X, Y);
    
    
    
    
    
    

    This finalizes the tape into a function:

    [math] \Large{f: S \mapsto \text{price}} [/math]

    🤖 How AD Gets the Derivative

    Using:

    std::vector<double> delta = f.Jacobian(x);

    CppAD:

    • Evaluates the forward pass to get intermediate values (just like normal code),
    • Then walks backward through the graph, applying the chain rule at each node to compute:

    [math] \Large{ \Delta = \frac{\partial \text{price}}{\partial S} } [/math]

    This is the exact derivative, not an approximation.

    ⚙️ Summary: How the Code Avoids Finite Difference Pitfalls

    FeatureYour CppAD CodeFinite Differences
    Number of evaluations1 forward pass + internal backprop2+ full evaluations
    Step size tuning?❌ None needed✅ Must choose ϵ\epsilonϵ carefully
    Derivative accuracy✅ Machine-accurate❌ Approximate
    Performance on multiple Greeks✅ Fast with reverse mode❌ Expensive (1 per Greek)
    Maintenance cost✅ Code reflects math structure❌ Rewriting required for each sensitivity

    June 25, 2025 0 comments
    Best Quant Cities
    Jobs

    Top Cities for High-Paying C++ Quantitative Roles

    by Clement D. June 25, 2025

    If you’re a skilled C++ developer with an interest in high-performance finance, the world of quantitative trading offers some of the most lucrative roles available today. These positions, often found at hedge funds, proprietary trading firms, and investment banks, demand a blend of low-latency programming expertise, mathematical insight, and real-time systems knowledge. While demand exists globally, a handful of cities stand out for consistently offering the highest compensation packages.

    From Wall Street to Canary Wharf, certain global financial hubs continue to dominate the quant talent market. These cities not only house the world’s top funds and trading desks but also offer competitive salaries, generous bonuses, and exposure to cutting-edge infrastructure. In this article, we break down the top five cities for high-paying C++ quantitative roles, supported by up-to-date salary data and market trends.

    1. New York City, USA

    Here’s a detailed look at New York City, the top destination globally for high-paying C++ quantitative developer roles:

    💰 Salary Ranges & Market Averages

    • Built In reports the average base salary for a Quant Developer in NYC is $326,667, with an additional cash compensation of $50,000, bringing average total comp to $376,667 — with a typical range of $180k – $500k indeed.com+6oxfordknight.co.uk+6ziprecruiter.com+6linkedin.com+5builtin.com+5reddit.com+5.
    • Glassdoor estimates total compensation at $242,376 annually, with average base salary around $138,351 glassdoor.com—likely reflecting more mid-career roles.
    • ZipRecruiter lists the average at ~$185,700/year (~$89/hour as of June 2025) indeed.com+15ziprecruiter.com+15payscale.com+15.

    🏢 Top Firms & Roles

    • HFT firms (e.g., Jane Street, Citadel, Renaissance) often offer $250k–$300k base for C++ quant devs, with bonuses regularly doubling take-home pay investopedia.com+5l.ny.nyc.associationcareernetwork.com+5oxfordknight.co.uk+5.
    • Citadel-level roles report total comp from $200k to $700k, with a median of $550k for Quant Devs in NYC payscale.com+15levels.fyi+15builtin.com+15.
    • Selby Jennings lists openings with salary bands of $300k–$400k l.ny.nyc.associationcareernetwork.com+5glassdoor.com+5linkedin.com+5.
    • Other firms like Xantium, Barclays, and Bloomberg offer ranges from $155k to $300k+$, often with discretionary bonuses builtin.com+2oxfordknight.co.uk+2xantium.com+2.

    🔍 3. Role Levels & Progression

    • Entry & mid-level Quant Dev roles typically range from $150k to $225k base, often with significant bonuses in year one and thereafter builtin.com+1xantium.com+1.
    • Senior/front-office developers in top firms frequently see base pay ≥ $250k, and total comp that can push $500k+ payscale.com+14linkedin.com+14indeed.com+14.

    🧠 4. Compensation Structure

    • Compensation is a blend of base + bonus + potential stock/equity. Bonuses can equal or exceed base salaries, especially in top-tier firms .
    • Reddit users note: “Quant Developers: $300k–1M … top graduates … guarantees in excess of $500k for the 1st year” at elite shops reddit.com+1efinancialcareers.com+1.

    📍 5. Role Types & Relevance of C++

    • Most high-paying roles are C++-centric, especially in low-latency trading, execution systems, and infrastructure for quant analytics.
    • Job listings for C++ Low Latency Trading Systems Dev show base pay typically $150k–$300k, with bonus upsides indeed.com+3linkedin.com+3bloomberg.avature.net+3.

    🧩 👉 Summary Snapshot

    Role LevelBase PayTotal Compensation
    Entry–Mid (Hedge/Fund)$150k–$225k$225k–$350k (with bonus)
    Senior/Elite$250k–$350k+$400k–$700k+ (with bonus/equity)
    Average (mid-career)$326k base$376k total comp

    Bottom line:
    New York City remains the gold standard for C++ quantitative developers. At top-tier firms, you’ll see base salaries of $250k+ and total compensation reaching $500k–$700k, especially at hedge funds and prop trading shops. Even outside the elite circles, mid-tier roles offer $150k–$200k base with solid bonus structures. C++ expertise in low-latency systems is an exceptionally valued skill in this market.

    2. San Francisco Bay Area / Silicon Valley, USA

    Here’s a detailed exploration of San Francisco Bay Area / Silicon Valley, demonstrating why it ranks as one of the top-paying regions for C++ quantitative developers:

    💼 Salary Overview for Quantitative Developers

    • Indeed reports the average salary for a Quantitative Developer in San Francisco itself at $196,116/year wellfound.comglassdoor.com+8indeed.com+8indeed.com+8.
    • ZipRecruiter lists the rate at $199,978/year, or about $96.14/hour, as of April 2025 ziprecruiter.com+2ziprecruiter.com+2ziprecruiter.com+2.
    • Glassdoor shows a median total compensation around $306,000/year, with typical base pay between $131k–$173k and additional compensation of $116k–$217k glassdoor.com+1levels.fyi+1.

    📊 Comparison with California & National Averages

    • These San Francisco figures exceed the California Quant Dev average of $167,506/year (~ $80.53/hr) glassdoor.com+2ziprecruiter.com+2glassdoor.com+2.
    • Built In’s national data shows an average base of around $196k and total comp around $248k, placing Bay Area roles significantly above national mean builtin.com+1levels.fyi+1.

    🛠️ C++ Developer Base Salaries

    • Indeed reports average base pay for general C++ Developers in San Francisco at $177,272/year, with a range from $123k to $254k salary.com+5indeed.com+5indeed.com+5.
    • Salary.com notes senior C++ Developer base ranges from $177,904 to $212,377, averaging $193,584 salary.com.

    🚀 Silicon Valley Premium

    • C++ developer salaries in the broader Bay Area average $162k, with wide dispersion—from $95k to $460k—reflecting startup equity upside wellfound.com.
    • San Jose (next to SF) offers higher comps for Quant Dev roles: total compensation averages $429,654/year, with base around $225,027/year wellfound.com+3glassdoor.com+3ziprecruiter.com+3.

    💵 Total Compensation Breakdown

    Role TypeBase Pay RangeTotal Comp Range
    Quant Dev (SF)$131k–$173k$248k–$390k+
    General C++ Dev (SF)$123k–$212k—
    Quant Dev (San Jose)~$225k (base)~$430k total avg

    ⚙️ Role Types & C++ Relevance

    High-paying roles typically emphasize low-latency C++ engineering within algorithmic trading engines, pricing libraries, and high-performance analytics platforms.

    • Compensation structure includes base + cash bonus + sometimes stock/equity, especially at startups and tech-influenced trading shops.

    🎯 Bottom Line

    • Base pay for Bay Area quant C++ roles ranges $130k–$225k+, depending on seniority and location.
    • Total compensation regularly reaches $250k–$400k in San Francisco, with $430k+ in San Jose, especially at elite or startup-oriented firms.

    3. London, UK

    💷 Salary Range – Base & Total Compensation

    • Payscale indicates the average base salary for Quant Developers with C++ experience in London is £65k–£100k, with total pay (including bonus) ranging from £70k–£125k uk.indeed.com+14payscale.com+14efinancialcareers-canada.com+14.
    • Morgan McKinley reports base ranges for Quant Developers are £105k–£150k overall, breaking down as:
      • £70k–100k for 0–3 years
      • £105k–150k for 3–5 years
      • £150k–195k for 5+ years morganmckinley.com+1morganmckinley.com+1.
    • Glassdoor cites average base at £90,339 with total compensation around £127,470 uk.indeed.com+10glassdoor.ca+10efinancialcareers-canada.com+10.

    📈 Premium Compensation for C++ & HFT Roles

    • Listings from eFinancialCareers for top quant firms show up to £200k base plus bonus reddit.com+6efinancialcareers-canada.com+6efinancialcareers.com+6.
    • Specialized roles offering £130k–£140k base + £50k–£70k bonus appear regularly clientserver.com+2efinancialcareers-canada.com+2reddit.com+2.
    • Oxford Knight advertises C++ quant developer roles in systematic equities with £150k–£350k total compensation efinancialcareers.co.uk+14oxfordknight.co.uk+14efinancialcareers-canada.com+14.

    🎯 Senior & Front-Office Engineer Earnings

    • Roles in hedge funds and high-frequency trading often target senior candidates with £150k–£350k total compensation, emphasizing front-office C++ expertise .
    • Client Server listings feature C++/Python Quant Dev roles with £110k–£175k base, plus potential bonuses worth multiple base salaries clientserver.com.

    🧩 Skill Demand & Market Pressure

    • ITJobsWatch data shows the UK median for Quant Developer roles is £140k, with London’s median at £150k morganmckinley.com+7itjobswatch.co.uk+7oxfordknight.co.uk+7.
    • C++ and low-latency expertise feature prominently in job ads (~60% mention C++), especially within hedge funds and algorithmic trading shops .

    ⚖️ Junior to Senior Progression Path

    Experience LevelBase PayTotal Compensation
    Entry (0–3 yrs)£65k–£100k£70k–£125k
    Mid (3–5 yrs)£105k–£150k£150k–£200k
    Senior (5+ yrs)£150k–£200k+£200k–£350k+
    • Bonuses often range from £20k to £70k+, and in top-tier roles, they can double base compensation efinancialcareers.com+4morganmckinley.com+4morganmckinley.com+4morganmckinley.com+3payscale.com+3morganmckinley.com+3morganmckinley.com+1morganmckinley.com+1reddit.com.

    🧭 Why London Holds Its Ground

    • As a major global financial hub, London hosts numerous hedge funds, trading desks, and investment banks that depend on low-latency C++ infrastructure .
    • Market demand remains strong, with job vacancies growing and salaries up ~9% year-on-year, according to ITJobsWatch .

    ✅ Summary Insight

    London offers compelling opportunities for C++ quantitative developers:

    • Base salaries typically: £65k–£150k, rising with seniority.
    • Total compensation often ranges from £100k to £350k+ at elite firms.
    • Top-tier roles at HFT/hedge funds pay aggressively, reflecting C++’s strategic value in low-latency systems.
    June 25, 2025 0 comments
    C++ quant jobs
    Jobs

    C++ Quantitative Developers: A Skyrocketing Job Market

    by Clement D. June 23, 2025

    The job market for C++ quantitative developers is experiencing a major surge. Driven by the relentless demand for low-latency execution, many hedge funds and trading firms are ramping up hiring. C++ remains the gold standard for performance-critical systems in finance — and its dominance is growing.
    Top firms like Citadel, Jane Street, and Jump Trading are offering eye-watering compensation packages to attract talent.

    In London, New York, and Singapore, six-figure base salaries are now entry-level — with total comp often exceeding $500k.
    Real-time risk, high-frequency trading, and exotic derivatives desks all need C++ expertise.
    The rise of data-driven modeling has only reinforced the need for tight integration between quant models and execution engines.
    Firms want devs who can code, optimize, and understand the math — and C++ sits at that intersection.
    With competition heating up, even junior roles now demand systems-level thinking and modern C++ fluency.
    For quants and devs alike, now is a golden moment to ride the C++ finance wave.

    1. Some Data about Salaries

    The demand for C++ Quant Developers in the UK has exploded — and salaries reflect it. The median salary has jumped to £170,000, up 17.24% year-on-year, continuing a multi-year upward trend. Even more striking, the 10th percentile salary has more than doubled since 2024, rising from £56,250 to £135,000, suggesting that entry-level roles are commanding mid-career paychecks.

    The number of permanent roles has also nearly doubled in a year, with 33 positions advertised versus just 18 the year prior. C++ quant roles now represent 0.058% of all UK job ads, a 3.6x increase in relative share — highlighting how niche, high-impact, and in-demand this skillset has become.

    Whether you’re a seasoned systems programmer or a mathematically-minded developer eyeing the finance sector, the numbers don’t lie: it’s a C++ quant boom.

    2. The Trend is Volatile but Clearly Goes Up

    This 20-year salary trend graph shows a clear and accelerating rise in compensation for C++ Quantitative Developers in the UK. After a decade of relative stability from 2005 to 2015, salaries began a marked upward shift around 2018, aligning with the growing demand for low-latency systems and tighter model-to-execution integration. Since 2020, volatility has increased — but so has the upside. The median salary line (orange) now sits firmly above £150,000, with the top 10% breaching the £200,000+ mark. Even the 25th percentile has climbed significantly, pointing to strong tailwinds across all seniority levels. As of mid-2025, the trend is steeply upward — a reflection of how C++ has reasserted itself as a core technology in quant finance.

    3. The Tools, Skills, Exposure and Libraries Required

    The C++ Quant Developer role is no longer just about knowing C++. According to data from the six months leading up to June 2025, Low Latency (93.94%), Equities, and Hedge Funds appear alongside C++ in nearly all job ads, signaling a strong demand for developers who understand real-time execution environments in capital markets.

    Interestingly, Python (90.91%) appears just as frequently — reinforcing the industry’s shift toward hybrid devs who can optimize in C++ and prototype in Python.
    Skills like Linux, Multithreading, Algorithms, and Data Structures remain core, while mentions of Boost, Test Automation, and Order Management show that system reliability and front-office tooling are just as valued as raw performance.

    Whether it’s Quantitative Trading, Market Making, or Greenfield Projects, this skill map clearly shows that modern quant devs must span systems engineering, financial markets, and rapid prototyping — a rare and highly-paid blend.

    4. Conclusion

    The data is unambiguous: C++ Quantitative Developers are among the most sought-after professionals in finance today. Salaries are surging, with median comp hitting £170,000, and top roles exceeding £200,000+. Even the lowest percentiles are rising fast — entry-level is no longer “junior” in pay. Demand has more than doubled year-on-year, with job postings climbing steadily. C++ sits at the core of high-frequency trading, real-time risk, and complex derivatives pricing.

    But employers aren’t just hiring C++ coders — they want versatile technologists. Fluency in Python, Linux, Multithreading, and Low Latency architecture is essential. Firms want devs who can design systems, optimize them, and understand market dynamics. This is no longer a back-office role — the Front Office is calling, and it pays.

    Whether you’re a quant with systems chops or a dev learning finance, now is the time to strike. The most competitive candidates understand both algorithms and alpha. They build fast, test fast, and deploy into production with confidence. The C++ quant role is evolving — it’s becoming broader, better-paid, and more central to business.

    This is not a bubble. It’s a structural shift. As market infrastructure becomes more automated and data-hungry, firms will invest heavily in top-tier engineering. That means a long runway for anyone investing in the right skills now.
    If you’ve been on the fence about switching into finance — consider this your signal. And if you’re already here: it’s time to double down on your edge.

    June 23, 2025 0 comments
    moving average interview
    Interview

    Calculate Moving Average in C++ in O(1) – An Interview-Style Problem

    by Clement D. April 26, 2025

    A classic interview question in quantitative finance or software engineering roles is:
    “Design a data structure that calculates the moving average of a stock price stream in O(1) time per update.”.
    How to calculate the moving average in C++ in an optimal way?

    Let’s tackle this with a focus on Microsoft (MSFT) stock, although the solution is applicable to any time-series financial instrument. We’ll use C++ to build an efficient, clean implementation suitable for production-grade quant systems.

    1. Problem Statement

    Design a class that efficiently calculates the moving average of the last N stock prices.

    Your class should support:

    • void addPrice(double price): Adds the latest price.
    • double getAverage(): Returns the average of the last N prices.

    Constraints:

    • The moving average must be updated in O(1) time per price.
    • Handle the case where fewer than N prices have been added.

    Implement this in C++.

    You will need to complete the following code:

    #include <vector>
    
    
    class MovingAverage {
    public:
        explicit MovingAverage(int size);
        void addPrice(double price);
        double getAverage() const;
    private:
        std::vector<double> buffer;
        int maxSize;
        double sum = 0.0;
    };
    

    Imagine the following historical prices for Microsoft stocks:

    DayPrice (USD)
    1400.0
    2402.5
    3405.0
    4410.0
    5412.0
    6415.5

    And assume we want to calculate the moving average of prices on 3 days, everyday, as a test.

    2. A Naive Implementation in O(N)

    Let’s start with a first implementation using `std::accumulate`.

    It’s defined as follow in the C++ documentation:
    “std::accumulate Computes the sum of the given value init and the elements in the range [first, last)“. The last iterator is not included in the operation. This is a half-open interval, written as.

    std::accumulate is O(N) in time complexity.

    Let’s use it to calculate the MSTF stock price moving average in C++:

    #include <iostream>
    #include <vector>
    #include <numeric>
    
    class MovingAverage {
    public:
        explicit MovingAverage(int size) : maxSize(size) {}
    
        void addPrice(double price) {
            buffer.push_back(price);
            if (buffer.size() > maxSize) {
                buffer.erase(buffer.begin()); // O(N)
            }
        }
    
        double getAverage() const {
            if (buffer.empty()) return 0.0;
            double sum = std::accumulate(buffer.begin(), buffer.end(), 0.0); // O(N)
            return sum / buffer.size();
        }
    
    private:
        std::vector<double> buffer;
        int maxSize;
    };
    
    int main() {
        MovingAverage ma(3); // 3-day moving average
        std::vector<double> msftPrices = {400.0, 402.5, 405.0, 410.0, 412.0, 415.5};
    
        for (size_t i = 0; i < msftPrices.size(); ++i) {
            ma.addPrice(msftPrices[i]);
            std::cout << "Day " << i + 1 << " - Price: " << msftPrices[i]
                      << ", 3-day MA: " << ma.getAverage() << std::endl;
        }
    
        return 0;
    }
    

    Every new day the moving average is re-calculated from scratch, not ideal! But it gives the right results:

    ➜  build ./movingaverage 
    Day 1 - Price: 400, 3-day MA: 400
    Day 2 - Price: 402.5, 3-day MA: 401.25
    Day 3 - Price: 405, 3-day MA: 402.5
    Day 4 - Price: 410, 3-day MA: 405.833
    Day 5 - Price: 412, 3-day MA: 409
    Day 6 - Price: 415.5, 3-day MA: 412.5

    3. An optimal Implementation in O(1)

    Let’s now do it in an iterative way and just add the value to the former sum to calculate a performant moving average in C++:

    #include <iostream>
    #include <vector>
    
    class MovingAverage {
    public:
        explicit MovingAverage(int size)
            : buffer(size, 0.0), maxSize(size) {}
    
        void addPrice(double price) {
            sum -= buffer[index];        // Subtract the value being overwritten
            sum += price;                // Add the new value
            buffer[index] = price;       // Overwrite the old value
            index = (index + 1) % maxSize;
    
            if (count < maxSize) count++;
        }
    
        double getAverage() const {
            return count == 0 ? 0.0 : sum / count;
        }
    
    private:
        std::vector<double> buffer;
        int maxSize;
        int index = 0;
        int count = 0;
        double sum = 0.0;
    };
    
    int main() {
        // Example: 3-day moving average for Microsoft stock prices
        MovingAverage ma(3);
        std::vector<double> msftPrices = {400.0, 402.5, 405.0, 410.0, 412.0, 415.5};
    
        for (size_t i = 0; i < msftPrices.size(); ++i) {
            ma.addPrice(msftPrices[i]);
            std::cout << "Day " << i + 1
                      << " - Price: " << msftPrices[i]
                      << ", 3-day MA: " << ma.getAverage()
                      << std::endl;
        }
    
        return 0;
    }
    

    This time, no re-calculation, each time a new price gets in, we add it and average it on the fly. Although it looks better, the vector data structure in that case is not ideal.

    Another approach is to use a double-entry queue: std::deque.

    It perfectly suits the sliding window use case as we can pop and add elements both sides of the data structure in a very easy way (push_back/pop_front):

    #include <iostream>
    #include <deque>
    
    /**
     * MovingAverage maintains a fixed-size sliding window of the most recent prices
     * and efficiently computes their average.
     */
    class MovingAverage {
    public:
        explicit MovingAverage(int windowSize) : size_(windowSize), sum_(0.0) {}
    
        // Adds a new price to the window and updates the sum accordingly
        void addPrice(double price) {
            prices_.push_back(price);
            sum_ += price;
    
            // Remove the oldest price if the window is too big
            if (prices_.size() > size_) {
                sum_ -= prices_.front();
                prices_.pop_front();
            }
        }
    
        // Returns the current moving average
        double getMovingAverage() const {
            if (prices_.empty()) return 0.0;
            return sum_ / prices_.size();
        }
    
    private:
        std::deque<double> prices_;
        double sum_;
        int size_;
    };
    
    int main() {
        MovingAverage ma(3); // 3-day moving average
    
        // Historical Microsoft stock prices
        std::vector<double> msftPrices = {400.0, 402.5, 405.0, 410.0, 412.0, 415.5};
    
        std::cout << "Microsoft 3-day Moving Average Calculation:\n" << std::endl;
    
        for (size_t i = 0; i < msftPrices.size(); ++i) {
            ma.addPrice(msftPrices[i]);
            std::cout << "Day " << i + 1 << " - Price: " << msftPrices[i]
                      << ", 3-day MA: " << ma.getMovingAverage() << std::endl;
        }
    
        return 0;
    }
    


    It only adds/pops elements, then updates the sum, then the average.

    In other terms: the time complexity O(1).

    Let’s run it:

    ➜  build ./movingaverage
    Day 1 - Price: 400, 3-day MA: 400
    Day 2 - Price: 402.5, 3-day MA: 401.25
    Day 3 - Price: 405, 3-day MA: 402.5
    Day 4 - Price: 410, 3-day MA: 405.833
    Day 5 - Price: 412, 3-day MA: 409
    Day 6 - Price: 415.5, 3-day MA: 412.5

    The same results but way faster!

    And you pass your interview to calculate the moving average in C++ in the most performant way.

    4. Common STL Container Member Functions & Tips (with Vector vs Deque)

    ✅ Applies to Both std::vector<T> and std::deque<T>

    SyntaxDescriptionNotes / Gotchas
    container.begin()Iterator to first elementUsed in range-based loops and STL algorithms ([begin, end))
    container.end()Iterator past the last elementNon-inclusive range end
    container.size()Number of elementsType is size_t — beware of unsigned vs signed int bugs
    container.empty()true if container is emptySafer than size() == 0
    container.front()First element⚠️ Undefined if container is empty
    container.back()Last element⚠️ Undefined if container is empty
    container.push_back(x)Add x to the endO(1) amortized for vector, always O(1) for deque
    container.pop_back()Remove last element⚠️ Undefined if empty
    std::accumulate(begin, end, init)Sum or fold values over rangeFrom <numeric> — works on both vector and deque

    🟦 std::deque<T> Specific

    SyntaxDescriptionNotes / Gotchas
    container.push_front(x)Add x to frontO(1); ideal for queues and sliding windows
    container.pop_front()Remove first elementO(1); ⚠️ Undefined if empty
    container.erase(it)Remove element at iteratorO(1) when removing from front/back
    ✅ Random access ([i])SupportedSlightly slower than vector (more overhead under the hood)
    April 26, 2025 0 comments
    yield curve
    Bonds

    Building a Yield Curve in C++: Theory and Implementation

    by Clement D. March 25, 2025

    In fixed income markets, the yield curve is a fundamental tool that maps interest rates to different maturities. It underpins the pricing of bonds, swaps, and other financial instruments. How to build a yield curve in C++?

    Traders, quants, and risk managers rely on it daily to discount future cash flows. This article explores how to build a basic zero-coupon yield curve using C++, starting from market data and ending with interpolated discount factors. We’ll also compare our results with QuantLib, the industry-standard quantitative finance library.

    1. What’s a Yield Curve?

    At its core, the yield curve captures how interest rates change with the length of time you lend money. Here’s an example of hypothetical yields across maturities:

    MaturityYield (%)
    Overnight (ON)1.00
    1 Month1.20
    3 Months1.35
    6 Months1.50
    1 Year1.80
    2 Years2.10
    5 Years2.60
    10 Years3.00
    30 Years3.20

    Plotted on a graph, these points form the yield curve — typically upward sloping, as longer maturities usually command higher yields due to risk and time value of money.

    Regular Yield Curve

    However, in stressed environments, the curve can flatten or even invert (e.g., 2-year yield > 10-year yield), often signaling economic uncertainty:

    Inverted Yield Curve

    Here is the plot of a hypothetical inverted yield curve, where shorter-term yields are higher than longer-term ones. This often reflects market expectations of economic slowdown or interest rate cuts in the future.

    When investors expect future economic weakness, they begin to anticipate interest rate cuts by the central bank. As a result:

    • Demand increases for long-term bonds (seen as safe havens), which pushes their prices up and their yields down.
    • Meanwhile, short-term rates may remain high due to current central bank policy (e.g. inflation control).

    This flips the curve: short-term yields become higher than long-term yields.

    2. Key Concepts Behind Yield Curve Construction

    Before jumping into code, it’s important to understand how yield curves are built from market data. In practice, we don’t observe a complete yield curve directly, we build (or “bootstrap”) it from liquid instruments such as:

    • Deposits (e.g., overnight to 6 months)
    • Forward Rate Agreements (FRAs) and Futures
    • Swaps (e.g., 1-year to 30-year)

    These instruments give us information about specific points on the curve. QuantLib uses these to construct a continuous curve using interpolation (e.g., linear, log-linear) between observed data points.

    Some essential building blocks in QuantLib include:

    • RateHelpers: Abstractions that turn market quotes (like deposit or swap rates) into bootstrapping constraints.
    • DayCount conventions and Calendars: Needed for accurate date and interest calculations.
    • YieldTermStructure: The central object representing the term structure of interest rates, which you can query for zero rates, discount factors, or forward rates.

    QuantLib lets you define all of this in a modular way, so you can plug in market data and generate an accurate, arbitrage-free curve for pricing and risk analysis.

    3. Implement in C++ with Quantlib

    To construct a yield curve in QuantLib, you’ll typically bootstrap it from a mix of deposit rates and swaps. QuantLib provides a flexible interface to handle real-world instruments and interpolate the curve. Below is a minimal C++ example that constructs a USD zero curve from deposit and swap rates:

    #include <ql/quantlib.hpp>
    #include <iostream>
    
    using namespace QuantLib;
    
    int main() {
        Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
        Date today(25, June, 2025);
        Settings::instance().evaluationDate() = today;
    
        // Market quotes
        Rate depositRate = 0.015; // 1.5% for 3M deposit
        Rate swapRate5Y = 0.025;  // 2.5% for 5Y swap
    
        // Quote handles
        Handle<Quote> dRate(boost::make_shared<SimpleQuote>(depositRate));
        Handle<Quote> sRate(boost::make_shared<SimpleQuote>(swapRate5Y));
    
        // Day counters and conventions
        DayCounter depositDayCount = Actual360();
        DayCounter curveDayCount = Actual365Fixed();
        Thirty360 swapDayCount(Thirty360::BondBasis);
    
        // Instrument helpers
        std::vector<boost::shared_ptr<RateHelper>> instruments;
    
        instruments.push_back(boost::make_shared<DepositRateHelper>(
            dRate, 3 * Months, 2, calendar, ModifiedFollowing, false, depositDayCount));
    
        instruments.push_back(boost::make_shared<SwapRateHelper>(
            sRate, 5 * Years, calendar, Annual, Unadjusted, swapDayCount,
            boost::make_shared<Euribor6M>()));
    
        // Construct the yield curve
        boost::shared_ptr<YieldTermStructure> yieldCurve =
            boost::make_shared<PiecewiseYieldCurve<ZeroYield, Linear>>(
                today, instruments, curveDayCount);
    
        // Example output: discount factor at 2Y
        Date maturity = calendar.advance(today, 2, Years);
        std::cout << "==== Yield Curve ====" << std::endl;
        for (int y = 1; y <= 5; ++y) {
        Date maturity = calendar.advance(today, y, Years);
        double discount = yieldCurve->discount(maturity);
        double zeroRate = yieldCurve->zeroRate(maturity, curveDayCount, Compounded, Annual).rate();
    
        std::cout << "Maturity: " << y << "Y\t"
                  << "Yield: " << std::fixed << std::setprecision(2) << 100 * zeroRate << "%\t"
                  << "Discount: " << std::setprecision(5) << discount
                  << std::endl;
        }
    
        return 0;
    }

    This snippet shows how to:

    • Set up today’s date and calendar,
    • Define deposit and swap instruments as bootstrapping inputs,
    • Build a PiecewiseYieldCurve using QuantLib’s helper classes,
    • Query the curve for a discount factor.

    You can easily extend this with more instruments (FRA, futures, longer swaps) for a realistic curve.

    4. Compile, Execute and Plot

    After installing quantlib, I compile my code with the following CMakeLists.txt:

    cmake_minimum_required(VERSION 3.10)
    project(QuantLibImpliedVolExample)
    
    set(CMAKE_CXX_STANDARD 17)
    
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(QUANTLIB REQUIRED QuantLib)
    
    include_directories(${QUANTLIB_INCLUDE_DIRS})
    link_directories(${QUANTLIB_LIBRARY_DIRS})
    
    add_executable(yieldcurve ../yieldcurve.cpp)
    target_link_libraries(yieldcurve ${QUANTLIB_LIBRARIES})

    I run:

    mkdir build
    cd build
    cmake ..
    make

    And run:

    ➜ build ./yieldcurve
    ==== Yield Curve ====
    Maturity: 1Y Yield: 1.68% Discount: 0.98345
    Maturity: 2Y Yield: 1.89% Discount: 0.96324
    Maturity: 3Y Yield: 2.10% Discount: 0.93946
    Maturity: 4Y Yield: 2.31% Discount: 0.91272
    Maturity: 5Y Yield: 2.52% Discount: 0.88306

    And we can plot it too:

    So this is the plot of the zero yield curve from 1 to 5 years, based on your QuantLib output. It shows a smooth, gently upward-sloping curve: typical for a healthy interest rate environment.

    But why is it so… Linear?

    This is due to both the limited number of inputs and the interpolation method applied.

    In the example above, the curve is built using QuantLib’s PiecewiseYieldCurve<ZeroYield, Linear>, which performs linear interpolation between the zero rates of the provided instruments. With only a short-term and a long-term point, this leads to a straight-line interpolation between the two, hence the linear shape.

    In reality, yield curves typically exhibit curvature:

    • They rise steeply in the short term,
    • Then gradually flatten as maturity increases.

    This reflects the market’s expectations about interest rates, inflation, and economic growth over time.

    To better approximate real-world behavior, the curve can be constructed using:

    • A richer set of instruments (e.g., deposits, futures, swaps across many maturities),
    • More appropriate interpolation techniques such as LogLinear or Cubic.

    5. A More Realistic Curve

    This version uses LogLinear interpolation, which interpolates on the log of the discount factors. It results in a more natural term structure than simple linear interpolation, even with only two instruments.

    For a truly realistic curve, additional instruments should be incorporated, especially swaps with intermediate maturities:

    #include <ql/quantlib.hpp>
    #include <iostream>
    #include <iomanip>
    
    using namespace QuantLib;
    
    int main() {
        Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
        Date today(25, June, 2025);
        Settings::instance().evaluationDate() = today;
    
        // Market quotes
        Rate depositRate = 0.015;    // 1.5% for 3M deposit
        Rate swapRate2Y = 0.020;     // 2.0% for 2Y swap
        Rate swapRate5Y = 0.025;     // 2.5% for 5Y swap
        Rate swapRate10Y = 0.030;    // 3.0% for 10Y swap
    
        // Quote handles
        Handle<Quote> dRate(boost::make_shared<SimpleQuote>(depositRate));
        Handle<Quote> sRate2Y(boost::make_shared<SimpleQuote>(swapRate2Y));
        Handle<Quote> sRate5Y(boost::make_shared<SimpleQuote>(swapRate5Y));
        Handle<Quote> sRate10Y(boost::make_shared<SimpleQuote>(swapRate10Y));
    
        // Day counters and conventions
        DayCounter depositDayCount = Actual360();
        DayCounter curveDayCount = Actual365Fixed();
        Thirty360 swapDayCount(Thirty360::BondBasis);
    
        // Instrument helpers
        std::vector<boost::shared_ptr<RateHelper>> instruments;
    
        instruments.push_back(boost::make_shared<DepositRateHelper>(
            dRate, 3 * Months, 2, calendar, ModifiedFollowing, false, depositDayCount));
    
        instruments.push_back(boost::make_shared<SwapRateHelper>(
            sRate2Y, 2 * Years, calendar, Annual, Unadjusted, swapDayCount,
            boost::make_shared<Euribor6M>()));
    
        instruments.push_back(boost::make_shared<SwapRateHelper>(
            sRate5Y, 5 * Years, calendar, Annual, Unadjusted, swapDayCount,
            boost::make_shared<Euribor6M>()));
    
        instruments.push_back(boost::make_shared<SwapRateHelper>(
            sRate10Y, 10 * Years, calendar, Annual, Unadjusted, swapDayCount,
            boost::make_shared<Euribor6M>()));
    
        // Construct the yield curve with Cubic interpolation
        boost::shared_ptr<YieldTermStructure> yieldCurve =
            boost::make_shared<PiecewiseYieldCurve<ZeroYield, Cubic>>(
                today, instruments, curveDayCount);
    
        // Output: yield curve at each year from 1Y to 10Y
        std::cout << "==== Yield Curve ====" << std::endl;
        for (int y = 1; y <= 10; ++y) {
            Date maturity = calendar.advance(today, y, Years);
            double discount = yieldCurve->discount(maturity);
            double zeroRate = yieldCurve->zeroRate(maturity, curveDayCount, Compounded, Annual).rate();
    
            std::cout << "Maturity: " << y << "Y\t"
                      << "Yield: " << std::fixed << std::setprecision(2) << 100 * zeroRate << "%\t"
                      << "Discount: " << std::setprecision(5) << discount
                      << std::endl;
        }
    
        return 0;
    }

    And let’s run it again:

    ➜  build ./yieldcurve
    ==== Yield Curve ====
    Maturity: 1Y    Yield: 1.67%    Discount: 0.98355
    Maturity: 2Y    Yield: 2.00%    Discount: 0.96122
    Maturity: 3Y    Yield: 2.20%    Discount: 0.93680
    Maturity: 4Y    Yield: 2.37%    Discount: 0.91058
    Maturity: 5Y    Yield: 2.51%    Discount: 0.88320
    Maturity: 6Y    Yield: 2.64%    Discount: 0.85524
    Maturity: 7Y    Yield: 2.75%    Discount: 0.82674
    Maturity: 8Y    Yield: 2.86%    Discount: 0.79790
    Maturity: 9Y    Yield: 2.96%    Discount: 0.76912
    Maturity: 10Y   Yield: 3.05%    Discount: 0.74018

    Which gives a better looking yield curve:

    March 25, 2025 0 comments
    • 1
    • …
    • 4
    • 5
    • 6
    • 7

    @2025 - All Right Reserved.


    Back To Top
    • Home
    • News
    • Contact
    • About