Option Pricing and Greeks: From Black-Scholes to Monte Carlo
Implementing option pricing models and risk calculations for derivatives trading systems—practical approaches for production environments
Options are the building blocks of modern finance. Pricing them correctly and understanding their risk characteristics (Greeks) is fundamental to derivatives trading. A 1% error in volatility estimation can mean millions in P&L impact on a large portfolio.
We've built option pricing systems handling thousands of instruments with millisecond latency requirements. This post covers the theory, implementation, and production considerations for option pricing and Greeks calculation.
The classic closed-form solution for European options:
1// black_scholes.hpp - Black-Scholes pricing with Greeks
2#pragma once
3
4#include <cmath>
5#include <numbers>
6
7class BlackScholes {
8public:
9 struct Inputs {
10 double spot; // Current stock price
11 double strike; // Strike price
12 double time_to_expiry; // Time to expiry (years)
13 double risk_free_rate; // Risk-free rate
14 double volatility; // Implied volatility
15 double dividend_yield; // Continuous dividend yield
16 bool is_call; // Call or put
17 };
18
19 struct Greeks {
20 double delta; // ∂V/∂S
21 double gamma; // ∂²V/∂S²
22 double vega; // ∂V/∂σ
23 double theta; // ∂V/∂t
24 double rho; // ∂V/∂r
25 double vanna; // ∂²V/∂S∂σ
26 double volga; // ∂²V/∂σ²
27 double charm; // ∂²V/∂S∂t
28 };
29
30 struct Result {
31 double price;
32 Greeks greeks;
33 };
34
35 static Result calculate(const Inputs& input) {
36 // Adjust for dividends
37 const double S = input.spot * std::exp(-input.dividend_yield * input.time_to_expiry);
38 const double K = input.strike;
39 const double T = input.time_to_expiry;
40 const double r = input.risk_free_rate;
41 const double sigma = input.volatility;
42 const double q = input.dividend_yield;
43
44 // Calculate d1 and d2
45 const double sqrt_T = std::sqrt(T);
46 const double d1 = (std::log(S / K) + (r - q + 0.5 * sigma * sigma) * T) / (sigma * sqrt_T);
47 const double d2 = d1 - sigma * sqrt_T;
48
49 // Standard normal CDF and PDF
50 const double N_d1 = normal_cdf(d1);
51 const double N_d2 = normal_cdf(d2);
52 const double n_d1 = normal_pdf(d1);
53
54 // Price calculation
55 double price;
56 if (input.is_call) {
57 price = S * std::exp(-q * T) * N_d1 - K * std::exp(-r * T) * N_d2;
58 } else {
59 price = K * std::exp(-r * T) * (1 - N_d2) - S * std::exp(-q * T) * (1 - N_d1);
60 }
61
62 // Greeks calculation
63 Greeks greeks;
64
65 // Delta: ∂V/∂S
66 if (input.is_call) {
67 greeks.delta = std::exp(-q * T) * N_d1;
68 } else {
69 greeks.delta = std::exp(-q * T) * (N_d1 - 1);
70 }
71
72 // Gamma: ∂²V/∂S² (same for call and put)
73 greeks.gamma = std::exp(-q * T) * n_d1 / (S * sigma * sqrt_T);
74
75 // Vega: ∂V/∂σ (per 1% change in volatility)
76 greeks.vega = S * std::exp(-q * T) * n_d1 * sqrt_T / 100.0;
77
78 // Theta: ∂V/∂t (per day)
79 const double theta_term1 = -(S * std::exp(-q * T) * n_d1 * sigma) / (2 * sqrt_T);
80 if (input.is_call) {
81 const double theta_term2 = -r * K * std::exp(-r * T) * N_d2;
82 const double theta_term3 = q * S * std::exp(-q * T) * N_d1;
83 greeks.theta = (theta_term1 + theta_term2 + theta_term3) / 365.0;
84 } else {
85 const double theta_term2 = r * K * std::exp(-r * T) * (1 - N_d2);
86 const double theta_term3 = -q * S * std::exp(-q * T) * (1 - N_d1);
87 greeks.theta = (theta_term1 + theta_term2 + theta_term3) / 365.0;
88 }
89
90 // Rho: ∂V/∂r (per 1% change in interest rate)
91 if (input.is_call) {
92 greeks.rho = K * T * std::exp(-r * T) * N_d2 / 100.0;
93 } else {
94 greeks.rho = -K * T * std::exp(-r * T) * (1 - N_d2) / 100.0;
95 }
96
97 // Second-order Greeks
98
99 // Vanna: ∂²V/∂S∂σ
100 greeks.vanna = -std::exp(-q * T) * n_d1 * d2 / sigma;
101
102 // Volga (Vomma): ∂²V/∂σ²
103 greeks.volga = S * std::exp(-q * T) * n_d1 * sqrt_T * d1 * d2 / sigma;
104
105 // Charm: ∂²V/∂S∂t (delta decay)
106 if (input.is_call) {
107 greeks.charm = -q * std::exp(-q * T) * N_d1
108 - std::exp(-q * T) * n_d1 * (2 * (r - q) * T - d2 * sigma * sqrt_T)
109 / (2 * T * sigma * sqrt_T);
110 } else {
111 greeks.charm = -q * std::exp(-q * T) * (N_d1 - 1)
112 - std::exp(-q * T) * n_d1 * (2 * (r - q) * T - d2 * sigma * sqrt_T)
113 / (2 * T * sigma * sqrt_T);
114 }
115
116 return {price, greeks};
117 }
118
119private:
120 // Standard normal cumulative distribution function
121 static double normal_cdf(double x) {
122 return 0.5 * std::erfc(-x * std::numbers::inv_sqrt2);
123 }
124
125 // Standard normal probability density function
126 static double normal_pdf(double x) {
127 constexpr double inv_sqrt_2pi = 0.3989422804014327;
128 return inv_sqrt_2pi * std::exp(-0.5 * x * x);
129 }
130};
131For models without closed-form solutions, use finite differences:
1// numerical_greeks.hpp - Finite difference Greeks
2#pragma once
3
4#include <functional>
5
6class NumericalGreeks {
7public:
8 using PricingFunction = std::function<double(double spot, double vol, double time)>;
9
10 struct Config {
11 double spot_bump = 0.01; // 1% bump for delta/gamma
12 double vol_bump = 0.0001; // 1bp bump for vega
13 double time_bump = 1.0/365.0; // 1 day for theta
14 };
15
16 struct Greeks {
17 double delta;
18 double gamma;
19 double vega;
20 double theta;
21 };
22
23 static Greeks calculate(
24 const PricingFunction& pricer,
25 double spot,
26 double vol,
27 double time,
28 const Config& config = {}
29 ) {
30 // Base price
31 const double V = pricer(spot, vol, time);
32
33 // Delta: first-order central difference
34 const double V_up = pricer(spot * (1 + config.spot_bump), vol, time);
35 const double V_down = pricer(spot * (1 - config.spot_bump), vol, time);
36 const double delta = (V_up - V_down) / (2 * spot * config.spot_bump);
37
38 // Gamma: second-order central difference
39 const double gamma = (V_up - 2 * V + V_down) /
40 std::pow(spot * config.spot_bump, 2);
41
42 // Vega: first-order central difference
43 const double V_vol_up = pricer(spot, vol + config.vol_bump, time);
44 const double V_vol_down = pricer(spot, vol - config.vol_bump, time);
45 const double vega = (V_vol_up - V_vol_down) / (2 * config.vol_bump * 100);
46
47 // Theta: backward difference (can't go forward in time easily)
48 const double V_time_minus = pricer(spot, vol, time - config.time_bump);
49 const double theta = -(V - V_time_minus) / config.time_bump * (1.0/365.0);
50
51 return {delta, gamma, vega, theta};
52 }
53};
54American options require early exercise consideration:
1// binomial_tree.hpp - Binomial tree for American options
2#pragma once
3
4#include <vector>
5#include <algorithm>
6#include <cmath>
7
8class BinomialTree {
9public:
10 struct Inputs {
11 double spot;
12 double strike;
13 double time_to_expiry;
14 double risk_free_rate;
15 double volatility;
16 double dividend_yield;
17 bool is_call;
18 bool is_american;
19 int steps; // Number of time steps
20 };
21
22 static double price(const Inputs& input) {
23 const double dt = input.time_to_expiry / input.steps;
24 const double u = std::exp(input.volatility * std::sqrt(dt));
25 const double d = 1.0 / u;
26 const double a = std::exp((input.risk_free_rate - input.dividend_yield) * dt);
27 const double p = (a - d) / (u - d); // Risk-neutral probability
28 const double discount = std::exp(-input.risk_free_rate * dt);
29
30 // Build price tree
31 std::vector<double> prices(input.steps + 1);
32
33 // Terminal nodes
34 for (int i = 0; i <= input.steps; ++i) {
35 const double S_T = input.spot * std::pow(u, input.steps - i) * std::pow(d, i);
36
37 if (input.is_call) {
38 prices[i] = std::max(0.0, S_T - input.strike);
39 } else {
40 prices[i] = std::max(0.0, input.strike - S_T);
41 }
42 }
43
44 // Backward induction
45 for (int step = input.steps - 1; step >= 0; --step) {
46 for (int i = 0; i <= step; ++i) {
47 // Continuation value
48 const double continuation = discount * (p * prices[i] + (1 - p) * prices[i + 1]);
49
50 if (input.is_american) {
51 // Early exercise value
52 const double S = input.spot * std::pow(u, step - i) * std::pow(d, i);
53 const double exercise = input.is_call ?
54 std::max(0.0, S - input.strike) :
55 std::max(0.0, input.strike - S);
56
57 prices[i] = std::max(continuation, exercise);
58 } else {
59 prices[i] = continuation;
60 }
61 }
62 }
63
64 return prices[0];
65 }
66};
67For exotic options (Asian, barriers, etc.):
1// monte_carlo.hpp - Monte Carlo option pricing
2#pragma once
3
4#include <random>
5#include <vector>
6#include <cmath>
7#include <algorithm>
8#include <thread>
9#include <numeric>
10
11class MonteCarlo {
12public:
13 struct Inputs {
14 double spot;
15 double strike;
16 double time_to_expiry;
17 double risk_free_rate;
18 double volatility;
19 double dividend_yield;
20 bool is_call;
21 size_t num_paths;
22 size_t num_steps; // For path-dependent options
23 unsigned int seed;
24 };
25
26 // Standard European option
27 static double european_option(const Inputs& input) {
28 std::mt19937_64 rng(input.seed);
29 std::normal_distribution<double> normal(0.0, 1.0);
30
31 const double drift = (input.risk_free_rate - input.dividend_yield -
32 0.5 * input.volatility * input.volatility) * input.time_to_expiry;
33 const double vol_sqrt_t = input.volatility * std::sqrt(input.time_to_expiry);
34 const double discount = std::exp(-input.risk_free_rate * input.time_to_expiry);
35
36 double payoff_sum = 0.0;
37
38 for (size_t i = 0; i < input.num_paths; ++i) {
39 const double Z = normal(rng);
40 const double S_T = input.spot * std::exp(drift + vol_sqrt_t * Z);
41
42 double payoff;
43 if (input.is_call) {
44 payoff = std::max(0.0, S_T - input.strike);
45 } else {
46 payoff = std::max(0.0, input.strike - S_T);
47 }
48
49 payoff_sum += payoff;
50 }
51
52 return discount * (payoff_sum / input.num_paths);
53 }
54
55 // Asian option (average price)
56 static double asian_option(const Inputs& input) {
57 std::mt19937_64 rng(input.seed);
58 std::normal_distribution<double> normal(0.0, 1.0);
59
60 const double dt = input.time_to_expiry / input.num_steps;
61 const double drift = (input.risk_free_rate - input.dividend_yield -
62 0.5 * input.volatility * input.volatility) * dt;
63 const double vol_sqrt_dt = input.volatility * std::sqrt(dt);
64 const double discount = std::exp(-input.risk_free_rate * input.time_to_expiry);
65
66 double payoff_sum = 0.0;
67
68 for (size_t i = 0; i < input.num_paths; ++i) {
69 double S = input.spot;
70 double price_sum = 0.0;
71
72 // Generate path
73 for (size_t step = 0; step < input.num_steps; ++step) {
74 const double Z = normal(rng);
75 S *= std::exp(drift + vol_sqrt_dt * Z);
76 price_sum += S;
77 }
78
79 // Average price
80 const double avg_price = price_sum / input.num_steps;
81
82 // Payoff based on average
83 double payoff;
84 if (input.is_call) {
85 payoff = std::max(0.0, avg_price - input.strike);
86 } else {
87 payoff = std::max(0.0, input.strike - avg_price);
88 }
89
90 payoff_sum += payoff;
91 }
92
93 return discount * (payoff_sum / input.num_paths);
94 }
95
96 // Barrier option (knock-out)
97 static double barrier_option(
98 const Inputs& input,
99 double barrier,
100 bool is_up_and_out
101 ) {
102 std::mt19937_64 rng(input.seed);
103 std::normal_distribution<double> normal(0.0, 1.0);
104
105 const double dt = input.time_to_expiry / input.num_steps;
106 const double drift = (input.risk_free_rate - input.dividend_yield -
107 0.5 * input.volatility * input.volatility) * dt;
108 const double vol_sqrt_dt = input.volatility * std::sqrt(dt);
109 const double discount = std::exp(-input.risk_free_rate * input.time_to_expiry);
110
111 double payoff_sum = 0.0;
112
113 for (size_t i = 0; i < input.num_paths; ++i) {
114 double S = input.spot;
115 bool knocked_out = false;
116
117 // Generate path and check barrier
118 for (size_t step = 0; step < input.num_steps; ++step) {
119 const double Z = normal(rng);
120 S *= std::exp(drift + vol_sqrt_dt * Z);
121
122 // Check barrier condition
123 if (is_up_and_out && S >= barrier) {
124 knocked_out = true;
125 break;
126 } else if (!is_up_and_out && S <= barrier) {
127 knocked_out = true;
128 break;
129 }
130 }
131
132 // Payoff only if not knocked out
133 if (!knocked_out) {
134 double payoff;
135 if (input.is_call) {
136 payoff = std::max(0.0, S - input.strike);
137 } else {
138 payoff = std::max(0.0, input.strike - S);
139 }
140 payoff_sum += payoff;
141 }
142 }
143
144 return discount * (payoff_sum / input.num_paths);
145 }
146
147 // Parallel Monte Carlo with variance reduction
148 static double european_option_parallel(const Inputs& input) {
149 const size_t num_threads = std::thread::hardware_concurrency();
150 const size_t paths_per_thread = input.num_paths / num_threads;
151
152 std::vector<std::thread> threads;
153 std::vector<double> results(num_threads);
154
155 for (size_t t = 0; t < num_threads; ++t) {
156 threads.emplace_back([&, t]() {
157 Inputs thread_input = input;
158 thread_input.num_paths = paths_per_thread;
159 thread_input.seed = input.seed + t;
160
161 // Antithetic variates for variance reduction
162 results[t] = european_option_antithetic(thread_input);
163 });
164 }
165
166 for (auto& thread : threads) {
167 thread.join();
168 }
169
170 return std::accumulate(results.begin(), results.end(), 0.0) / num_threads;
171 }
172
173private:
174 // Antithetic variates variance reduction
175 static double european_option_antithetic(const Inputs& input) {
176 std::mt19937_64 rng(input.seed);
177 std::normal_distribution<double> normal(0.0, 1.0);
178
179 const double drift = (input.risk_free_rate - input.dividend_yield -
180 0.5 * input.volatility * input.volatility) * input.time_to_expiry;
181 const double vol_sqrt_t = input.volatility * std::sqrt(input.time_to_expiry);
182 const double discount = std::exp(-input.risk_free_rate * input.time_to_expiry);
183
184 double payoff_sum = 0.0;
185
186 for (size_t i = 0; i < input.num_paths / 2; ++i) {
187 const double Z = normal(rng);
188
189 // Path with +Z
190 const double S_T_plus = input.spot * std::exp(drift + vol_sqrt_t * Z);
191 double payoff_plus = input.is_call ?
192 std::max(0.0, S_T_plus - input.strike) :
193 std::max(0.0, input.strike - S_T_plus);
194
195 // Antithetic path with -Z
196 const double S_T_minus = input.spot * std::exp(drift - vol_sqrt_t * Z);
197 double payoff_minus = input.is_call ?
198 std::max(0.0, S_T_minus - input.strike) :
199 std::max(0.0, input.strike - S_T_minus);
200
201 payoff_sum += (payoff_plus + payoff_minus) / 2.0;
202 }
203
204 return discount * (payoff_sum / (input.num_paths / 2));
205 }
206};
207Market prices embed volatility expectations. Extract IV using Newton-Raphson:
1// implied_volatility.hpp - IV calculation
2#pragma once
3
4#include "black_scholes.hpp"
5#include <cmath>
6#include <optional>
7
8class ImpliedVolatility {
9public:
10 struct Result {
11 double implied_vol;
12 int iterations;
13 double error;
14 };
15
16 static std::optional<Result> calculate(
17 double market_price,
18 double spot,
19 double strike,
20 double time_to_expiry,
21 double risk_free_rate,
22 double dividend_yield,
23 bool is_call,
24 double initial_guess = 0.3,
25 double tolerance = 1e-6,
26 int max_iterations = 100
27 ) {
28 double vol = initial_guess;
29
30 for (int iter = 0; iter < max_iterations; ++iter) {
31 // Calculate price and vega
32 BlackScholes::Inputs input{
33 spot, strike, time_to_expiry,
34 risk_free_rate, vol, dividend_yield, is_call
35 };
36
37 auto result = BlackScholes::calculate(input);
38
39 const double price_error = result.price - market_price;
40
41 // Check convergence
42 if (std::abs(price_error) < tolerance) {
43 return Result{vol, iter, price_error};
44 }
45
46 // Newton-Raphson update
47 const double vega_decimal = result.greeks.vega * 100; // Convert from % to decimal
48
49 if (std::abs(vega_decimal) < 1e-10) {
50 // Vega too small, can't converge
51 return std::nullopt;
52 }
53
54 vol -= price_error / vega_decimal;
55
56 // Keep vol positive and reasonable
57 vol = std::clamp(vol, 0.001, 5.0);
58 }
59
60 // Failed to converge
61 return std::nullopt;
62 }
63
64 // Vectorized IV calculation for smile/surface
65 static std::vector<double> calculate_smile(
66 const std::vector<double>& market_prices,
67 const std::vector<double>& strikes,
68 double spot,
69 double time_to_expiry,
70 double risk_free_rate,
71 double dividend_yield,
72 bool is_call
73 ) {
74 std::vector<double> implied_vols;
75 implied_vols.reserve(market_prices.size());
76
77 for (size_t i = 0; i < market_prices.size(); ++i) {
78 auto result = calculate(
79 market_prices[i],
80 spot,
81 strikes[i],
82 time_to_expiry,
83 risk_free_rate,
84 dividend_yield,
85 is_call
86 );
87
88 if (result) {
89 implied_vols.push_back(result->implied_vol);
90 } else {
91 implied_vols.push_back(std::nan(""));
92 }
93 }
94
95 return implied_vols;
96 }
97};
98For a portfolio of options, aggregate Greeks efficiently:
1// portfolio_greeks.hpp - Portfolio-level Greeks
2#pragma once
3
4#include <vector>
5#include <unordered_map>
6#include <string>
7#include "black_scholes.hpp"
8
9class PortfolioGreeks {
10public:
11 struct Position {
12 std::string symbol;
13 double quantity; // Number of contracts (can be negative for short)
14 BlackScholes::Inputs option_params;
15 };
16
17 struct PortfolioRisk {
18 // Total Greeks
19 double delta;
20 double gamma;
21 double vega;
22 double theta;
23 double rho;
24
25 // Greeks by underlying
26 std::unordered_map<std::string, double> delta_by_symbol;
27 std::unordered_map<std::string, double> gamma_by_symbol;
28 std::unordered_map<std::string, double> vega_by_symbol;
29
30 // Risk metrics
31 double total_notional;
32 double total_market_value;
33 double var_1d_95; // 1-day VaR at 95% confidence
34 };
35
36 static PortfolioRisk calculate(
37 const std::vector<Position>& positions,
38 double spot_shock = 0.01, // 1% spot shock for VaR
39 double vol_shock = 0.0001 // 1bp vol shock for VaR
40 ) {
41 PortfolioRisk risk{};
42
43 for (const auto& pos : positions) {
44 // Calculate Greeks for this position
45 auto result = BlackScholes::calculate(pos.option_params);
46
47 const double contract_multiplier = 100.0; // Standard option contract
48 const double position_size = pos.quantity * contract_multiplier;
49
50 // Aggregate total Greeks
51 risk.delta += result.greeks.delta * position_size;
52 risk.gamma += result.greeks.gamma * position_size;
53 risk.vega += result.greeks.vega * position_size;
54 risk.theta += result.greeks.theta * position_size;
55 risk.rho += result.greeks.rho * position_size;
56
57 // Aggregate by symbol
58 risk.delta_by_symbol[pos.symbol] += result.greeks.delta * position_size;
59 risk.gamma_by_symbol[pos.symbol] += result.greeks.gamma * position_size;
60 risk.vega_by_symbol[pos.symbol] += result.greeks.vega * position_size;
61
62 // Market value
63 risk.total_market_value += result.price * position_size;
64 risk.total_notional += pos.option_params.spot * std::abs(position_size);
65 }
66
67 // Calculate VaR (delta-gamma approximation)
68 risk.var_1d_95 = calculate_var_delta_gamma(positions, spot_shock);
69
70 return risk;
71 }
72
73private:
74 static double calculate_var_delta_gamma(
75 const std::vector<Position>& positions,
76 double spot_shock
77 ) {
78 // Group positions by underlying
79 std::unordered_map<std::string, std::vector<const Position*>> by_underlying;
80
81 for (const auto& pos : positions) {
82 by_underlying[pos.symbol].push_back(&pos);
83 }
84
85 double total_var = 0.0;
86
87 // Calculate VaR for each underlying
88 for (const auto& [symbol, symbol_positions] : by_underlying) {
89 double delta = 0.0;
90 double gamma = 0.0;
91 double spot = 0.0;
92
93 for (const auto* pos : symbol_positions) {
94 auto result = BlackScholes::calculate(pos->option_params);
95 const double size = pos->quantity * 100.0;
96
97 delta += result.greeks.delta * size;
98 gamma += result.greeks.gamma * size;
99 spot = pos->option_params.spot; // Assume same spot for same symbol
100 }
101
102 // Delta-gamma approximation for P&L change
103 const double dS = spot * spot_shock;
104 const double pnl_change = delta * dS + 0.5 * gamma * dS * dS;
105
106 total_var += pnl_change * pnl_change; // Assuming independence (simplification)
107 }
108
109 return std::sqrt(total_var) * 1.65; // 95% confidence (1.65 std devs)
110 }
111};
1121// option_pricer_cache.hpp - Cached pricing for performance
2#pragma once
3
4#include <unordered_map>
5#include <mutex>
6#include <chrono>
7
8class CachedOptionPricer {
9public:
10 struct CacheKey {
11 double spot;
12 double strike;
13 double vol;
14 double time;
15 bool is_call;
16
17 bool operator==(const CacheKey& other) const {
18 return spot == other.spot &&
19 strike == other.strike &&
20 vol == other.vol &&
21 time == other.time &&
22 is_call == other.is_call;
23 }
24 };
25
26 struct CacheKeyHash {
27 size_t operator()(const CacheKey& k) const {
28 size_t h = std::hash<double>{}(k.spot);
29 h ^= std::hash<double>{}(k.strike) << 1;
30 h ^= std::hash<double>{}(k.vol) << 2;
31 h ^= std::hash<double>{}(k.time) << 3;
32 h ^= std::hash<bool>{}(k.is_call) << 4;
33 return h;
34 }
35 };
36
37 CachedOptionPricer(std::chrono::seconds ttl = std::chrono::seconds(60))
38 : cache_ttl_(ttl) {}
39
40 BlackScholes::Result price(const BlackScholes::Inputs& input) {
41 // Create cache key (rounded to reduce cache misses)
42 CacheKey key{
43 round_to_precision(input.spot, 0.01),
44 round_to_precision(input.strike, 0.01),
45 round_to_precision(input.volatility, 0.0001),
46 round_to_precision(input.time_to_expiry, 1.0/365.0),
47 input.is_call
48 };
49
50 // Check cache
51 {
52 std::shared_lock lock(mutex_);
53 auto it = cache_.find(key);
54 if (it != cache_.end()) {
55 auto age = std::chrono::steady_clock::now() - it->second.timestamp;
56 if (age < cache_ttl_) {
57 ++cache_hits_;
58 return it->second.result;
59 }
60 }
61 }
62
63 // Cache miss - calculate
64 ++cache_misses_;
65 auto result = BlackScholes::calculate(input);
66
67 // Update cache
68 {
69 std::unique_lock lock(mutex_);
70 cache_[key] = {result, std::chrono::steady_clock::now()};
71 }
72
73 return result;
74 }
75
76 void clear_cache() {
77 std::unique_lock lock(mutex_);
78 cache_.clear();
79 }
80
81 double get_hit_rate() const {
82 uint64_t total = cache_hits_ + cache_misses_;
83 return total > 0 ? static_cast<double>(cache_hits_) / total : 0.0;
84 }
85
86private:
87 struct CacheEntry {
88 BlackScholes::Result result;
89 std::chrono::steady_clock::time_point timestamp;
90 };
91
92 static double round_to_precision(double value, double precision) {
93 return std::round(value / precision) * precision;
94 }
95
96 std::unordered_map<CacheKey, CacheEntry, CacheKeyHash> cache_;
97 mutable std::shared_mutex mutex_;
98 std::chrono::seconds cache_ttl_;
99 std::atomic<uint64_t> cache_hits_{0};
100 std::atomic<uint64_t> cache_misses_{0};
101};
102Option pricing and Greeks calculation are fundamental to derivatives trading. The key is choosing the right model for each situation: Black-Scholes for vanilla Europeans, trees for Americans, Monte Carlo for exotics.
In production, performance matters. Cache results, use SIMD when possible, and pre-calculate what you can. But never sacrifice accuracy for speed—a mispriced option can cost millions.
Building derivatives pricing systems? Contact us to discuss your quantitative finance infrastructure.
Technical Writer
NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.
Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.