Skip to content

Commit 03d8152

Browse files
committed
Add Pro C++ TMP, Lock-Free Queue, and Market Making Model
1 parent 183112a commit 03d8152

File tree

4 files changed

+220
-42
lines changed

4 files changed

+220
-42
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
class AvellanedaStoikovSimulator:
5+
"""
6+
Simulates the Avellaneda-Stoikov Market Making model.
7+
8+
Reference: "High-frequency trading in a limit order book" (2008)
9+
10+
Key Concepts:
11+
- Inventory Risk: As q (inventory) deviates from 0, we skew quotes to revert.
12+
- Reservation Price: r(s, t, q) = s - q * gamma * sigma^2 * (T - t)
13+
- Optimal Spread: Depends on kappa (market order arrival intensity).
14+
"""
15+
16+
def __init__(self, S0=100, T=1.0, sigma=2, dt=0.005, gamma=0.1, k=1.5, A=140):
17+
self.S0 = S0 # Initial Price
18+
self.T = T # Total Time
19+
self.sigma = sigma # Volatility
20+
self.dt = dt # Time step
21+
self.gamma = gamma # Risk aversion
22+
self.k = k # Order book liquidity param
23+
self.A = A # Order arrival rate param
24+
25+
self.M = int(T/dt)
26+
self.time = np.linspace(0, T, self.M+1)
27+
28+
def run(self):
29+
S = np.zeros(self.M+1)
30+
q = np.zeros(self.M+1) # Inventory
31+
cash = np.zeros(self.M+1)
32+
wealth = np.zeros(self.M+1)
33+
34+
S[0] = self.S0
35+
36+
for i in range(1, self.M+1):
37+
# 1. Evolve Mid-Price (Geometric Brownian Motion)
38+
dW = np.random.normal(0, np.sqrt(self.dt))
39+
S[i] = S[i-1] + self.sigma * dW
40+
41+
# 2. Calculate Reservation Price and Quotes
42+
# r = S - q * gamma * sigma^2 * (T - t)
43+
remaining_time = self.T - self.time[i-1]
44+
reservation_price = S[i-1] - q[i-1] * self.gamma * (self.sigma**2) * remaining_time
45+
46+
# spread = gamma * sigma^2 * (T - t) + (2/gamma) * ln(1 + gamma/k)
47+
spread = self.gamma * (self.sigma**2) * remaining_time + (2/self.gamma) * np.log(1 + self.gamma/self.k)
48+
49+
bid = reservation_price - spread/2
50+
ask = reservation_price + spread/2
51+
52+
# 3. Simulate Market Order Arrivals (Poisson Process)
53+
# Prob of fill decays exponentially with distance from mid-price (delta)
54+
delta_b = S[i-1] - bid
55+
delta_a = ask - S[i-1]
56+
57+
lambda_b = self.A * np.exp(-self.k * delta_b)
58+
lambda_a = self.A * np.exp(-self.k * delta_a)
59+
60+
prob_b = lambda_b * self.dt
61+
prob_a = lambda_a * self.dt
62+
63+
# Execution
64+
q[i] = q[i-1]
65+
cash[i] = cash[i-1]
66+
67+
# Did someone hit our bid? (We buy)
68+
if np.random.rand() < prob_b:
69+
q[i] += 1
70+
cash[i] -= bid
71+
72+
# Did someone lift our ask? (We sell)
73+
if np.random.rand() < prob_a:
74+
q[i] -= 1
75+
cash[i] += ask
76+
77+
wealth[i] = cash[i] + q[i] * S[i]
78+
79+
return self.time, S, q, wealth
80+
81+
if __name__ == "__main__":
82+
sim = AvellanedaStoikovSimulator()
83+
t, S, q, W = sim.run()
84+
85+
fig, ax = plt.subplots(3, 1, figsize=(10, 12), sharex=True)
86+
87+
ax[0].plot(t, S, label='Mid Price')
88+
ax[0].set_title('Stock Price')
89+
ax[0].grid(True)
90+
91+
ax[1].plot(t, q, label='Inventory (q)', color='orange')
92+
ax[1].set_title('Inventory Position')
93+
ax[1].grid(True)
94+
95+
ax[2].plot(t, W, label='Total Wealth', color='green')
96+
ax[2].set_title('P&L (Wealth)')
97+
ax[2].grid(True)
98+
99+
plt.show()
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <iostream>
2+
#include <array>
3+
#include <cmath>
4+
5+
/**
6+
* Compile-Time Math using C++20 'consteval' / 'constexpr'.
7+
*
8+
* In HFT, we often replace runtime math functions (pow, exp, sqrt) with:
9+
* 1. Lookup Tables (LUT).
10+
* 2. Polynomial Approximations.
11+
*
12+
* This example generates a Standard Normal CDF Lookup Table at compile time.
13+
*/
14+
15+
// Approximate CDF of Normal Distribution (Error < 0.0003)
16+
constexpr double normal_cdf(double x) {
17+
// Constants for approximation
18+
constexpr double p = 0.2316419;
19+
constexpr double b1 = 0.319381530;
20+
constexpr double b2 = -0.356563782;
21+
constexpr double b3 = 1.781477937;
22+
constexpr double b4 = -1.821255978;
23+
constexpr double b5 = 1.330274429;
24+
25+
if (x < 0.0) return 1.0 - normal_cdf(-x);
26+
27+
double t = 1.0 / (1.0 + p * x);
28+
double pdf = (1.0 / 2.50662827463) * std::exp(-0.5 * x * x); // 1/sqrt(2pi)
29+
30+
return 1.0 - pdf * (b1*t + b2*t*t + b3*t*t*t + b4*t*t*t*t + b5*t*t*t*t*t);
31+
}
32+
33+
// Generate LUT at compile time
34+
template<size_t N>
35+
struct NormalCDFTable {
36+
std::array<double, N> values;
37+
double min_x;
38+
double max_x;
39+
double step;
40+
41+
constexpr NormalCDFTable(double min_val, double max_val)
42+
: values{}, min_x(min_val), max_x(max_val), step((max_val - min_val) / (N - 1)) {
43+
44+
for (size_t i = 0; i < N; ++i) {
45+
double x = min_x + i * step;
46+
values[i] = normal_cdf(x);
47+
}
48+
}
49+
50+
// Runtime lookup is O(1) array access
51+
constexpr double lookup(double x) const {
52+
if (x <= min_x) return 0.0;
53+
if (x >= max_x) return 1.0;
54+
55+
size_t index = static_cast<size_t>((x - min_x) / step);
56+
return values[index];
57+
}
58+
};
59+
60+
// Create the table in the data segment (zero runtime initialization cost)
61+
constexpr auto cdf_table = NormalCDFTable<1000>(-4.0, 4.0);
62+
63+
int main() {
64+
// This value is fetched from memory, no calculation performed at runtime.
65+
std::cout << "N(0.0) = " << cdf_table.lookup(0.0) << std::endl;
66+
std::cout << "N(1.96) = " << cdf_table.lookup(1.96) << std::endl;
67+
68+
static_assert(cdf_table.lookup(0.0) > 0.49 && cdf_table.lookup(0.0) < 0.51, "Compile time check failed");
69+
70+
return 0;
71+
}

06_quantitative_development/cpp_low_latency/examples/lock_free_spsc_queue.cpp

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,86 @@
22
#include <vector>
33
#include <iostream>
44
#include <thread>
5+
#include <chrono>
56

67
/**
7-
* @brief Lock-Free Single Producer Single Consumer (SPSC) Ring Buffer.
8-
*
9-
* Key Concepts for Interviews:
10-
* 1. std::atomic for thread-safe indices.
11-
* 2. std::memory_order_acquire/release to ensure visibility without full fences.
12-
* 3. Cache line padding (false sharing prevention) using alignas.
8+
* Lock-Free Single-Producer Single-Consumer (SPSC) Queue.
9+
*
10+
* Concept:
11+
* - Uses a Ring Buffer (circular array).
12+
* - 'head' is modified by Producer.
13+
* - 'tail' is modified by Consumer.
14+
* - 'std::atomic' with 'memory_order_acquire' / 'memory_order_release' ensures
15+
* we don't see stale data without needing a full mutex lock.
16+
*
17+
* Usage in HFT:
18+
* - Thread A (Network): Pushes market data packets.
19+
* - Thread B (Strategy): Pops packets and processes.
1320
*/
1421

15-
template<typename T, size_t Size>
16-
class RingBuffer {
22+
template<typename T, size_t Capacity>
23+
class LockFreeSPSCQueue {
24+
private:
25+
std::vector<T> buffer;
26+
alignas(64) std::atomic<size_t> head; // Producer index
27+
alignas(64) std::atomic<size_t> tail; // Consumer index
28+
// alignas(64) prevents False Sharing (cache line thrashing)
29+
1730
public:
18-
RingBuffer() : head_(0), tail_(0) {}
31+
LockFreeSPSCQueue() : buffer(Capacity + 1), head(0), tail(0) {}
1932

20-
bool push(const T& val) {
21-
size_t head = head_.load(std::memory_order_relaxed);
22-
size_t next_head = (head + 1) % Size;
33+
bool push(const T& item) {
34+
size_t current_head = head.load(std::memory_order_relaxed);
35+
size_t next_head = (current_head + 1) % buffer.size();
2336

24-
if (next_head == tail_.load(std::memory_order_acquire)) {
25-
return false; // Buffer full
37+
if (next_head == tail.load(std::memory_order_acquire)) {
38+
return false; // Full
2639
}
2740

28-
buffer_[head] = val;
29-
head_.store(next_head, std::memory_order_release);
41+
buffer[current_head] = item;
42+
head.store(next_head, std::memory_order_release);
3043
return true;
3144
}
3245

33-
bool pop(T& val) {
34-
size_t tail = tail_.load(std::memory_order_relaxed);
46+
bool pop(T& item) {
47+
size_t current_tail = tail.load(std::memory_order_relaxed);
3548

36-
if (tail == head_.load(std::memory_order_acquire)) {
37-
return false; // Buffer empty
49+
if (current_tail == head.load(std::memory_order_acquire)) {
50+
return false; // Empty
3851
}
3952

40-
val = buffer_[tail];
41-
tail_.store((tail + 1) % Size, std::memory_order_release);
53+
item = buffer[current_tail];
54+
tail.store((current_tail + 1) % buffer.size(), std::memory_order_release);
4255
return true;
4356
}
44-
45-
private:
46-
std::vector<T> buffer_{Size};
47-
48-
// Align to cache line size (usually 64 bytes) to prevent false sharing
49-
alignas(64) std::atomic<size_t> head_;
50-
alignas(64) std::atomic<size_t> tail_;
5157
};
5258

5359
int main() {
54-
RingBuffer<int, 1024> rb;
60+
LockFreeSPSCQueue<int, 1024> queue;
5561

5662
std::thread producer([&]() {
57-
for (int i = 0; i < 1000; ++i) {
58-
while (!rb.push(i)) {
59-
// Spin wait strategy often used in low latency
63+
for (int i = 0; i < 100; ++i) {
64+
while (!queue.push(i)) {
65+
// Busy wait or yield
6066
std::this_thread::yield();
6167
}
6268
}
6369
});
6470

6571
std::thread consumer([&]() {
6672
int val;
67-
for (int i = 0; i < 1000; ++i) {
68-
while (!rb.pop(val)) {
69-
std::this_thread::yield();
73+
int count = 0;
74+
while (count < 100) {
75+
if (queue.pop(val)) {
76+
// Process val
77+
count++;
7078
}
71-
// std::cout << "Popped: " << val << std::endl; // IO is slow, avoid in HFT loop
7279
}
80+
std::cout << "Consumer finished processing " << count << " items." << std::endl;
7381
});
7482

7583
producer.join();
7684
consumer.join();
77-
std::cout << "Finished SPSC test." << std::endl;
85+
7886
return 0;
79-
}
87+
}

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ Whether you are aiming for a **Quantitative Researcher** role (heavy math/stats/
3232
We have curated specialized resources that target the specific requirements of HFT and Prop Trading interviews.
3333

3434
### ⚡ Low Latency & Systems
35-
* **C++ Mastery:** [Order Matching Engine](./06_quantitative_development/cpp_low_latency/examples/order_matching_engine.cpp) & [Memory Pool Allocator](./06_quantitative_development/cpp_low_latency/examples/memory_pool.cpp).
35+
* **C++ Mastery:** [Order Matching Engine](./06_quantitative_development/cpp_low_latency/examples/order_matching_engine.cpp), [Lock-Free Queue](./06_quantitative_development/cpp_low_latency/examples/lock_free_spsc_queue.cpp), & [Memory Pool](./06_quantitative_development/cpp_low_latency/examples/memory_pool.cpp).
36+
* **Template Metaprogramming:** [Compile-Time Greeks](./06_quantitative_development/cpp_low_latency/examples/compile_time_greeks.cpp) (C++20 `consteval` LUTs).
3637
* **Market Data:** [ITCH Feed Handler](./06_quantitative_development/data_engineering/market_data/itch_parser_mock.py).
37-
* **Java Performance:** [GC-Free Coding](./06_quantitative_development/java_low_latency/README.md).
3838
* **Architecture:** [HFT Infrastructure](./06_quantitative_development/system_design/architecture_notes/hft_architecture.md).
3939

4040
### 🧠 Interview Mastery
4141
* **The Roadmap:** [8-Week Study Plan](./07_interview_preparation/study_roadmap.md).
42-
* **Quant Strategies:** [Pairs Trading (Stat Arb)](./05_algorithmic_trading/strategies/systematic_strategies/pairs_trading_stat_arb.ipynb) & [Event-Driven Backtester](./05_algorithmic_trading/backtesting_frameworks/simple_event_driven.py).
42+
* **Quant Strategies:** [Avellaneda-Stoikov Market Making](./05_algorithmic_trading/strategies/market_microstructure/avellaneda_stoikov_mm.py) & [Pairs Trading](./05_algorithmic_trading/strategies/systematic_strategies/pairs_trading_stat_arb.ipynb).
4343
* **Jane Street Guide:** [Probability & Betting](./07_interview_preparation/company_insights/jane_street_guide.md).
4444
* **Quant Math:** [Green Book Companion](./01_foundations/mathematics/probability/quant_probability_guide.md).
4545

0 commit comments

Comments
 (0)