| name | citadel-low-latency-systems |
| description | Build trading systems in the style of Citadel Securities, the world's largest market maker. Emphasizes ultra-low latency, deterministic execution, kernel bypass networking, and high-frequency trading infrastructure. Use when building latency-critical systems, market making engines, or high-performance trading platforms. |
| tags | trading, low-latency, market-making, hft, fpga, networking, systems, performance, finance, real-time |
Citadel Securities Style Guide
Overview
Citadel Securities is the world's largest market maker, handling ~25% of all U.S. equity volume and ~40% of retail order flow. They execute millions of trades daily with sub-microsecond latency requirements. Their infrastructure represents the pinnacle of low-latency systems engineering.
Core Philosophy
"Every microsecond is a competitive advantage."
"Determinism is more important than average performance."
"The fastest system is the one that doesn't do unnecessary work."
Citadel believes that in market making, consistent low latency beats occasionally fast. Jitter is the enemy. Every component must be predictable and measurable.
Design Principles
-
Latency is King: Measure in microseconds, optimize in nanoseconds.
-
Determinism Over Speed: Predictable performance beats variable performance.
-
Kernel Bypass: The OS is too slow; go around it.
-
Lock-Free Everything: Locks are latency landmines.
-
Mechanical Sympathy: Know your hardware intimately.
When Building Low-Latency Systems
Always
- Measure latency at every component boundary
- Use kernel bypass networking (DPDK, Solarflare OpenOnload)
- Pin threads to cores, isolate from OS scheduler
- Pre-allocate all memory, no runtime allocation
- Use lock-free data structures
- Disable all non-essential OS features (hyperthreading, C-states, etc.)
Never
- Allocate memory on the critical path
- Use locks in the hot path
- Let the OS schedule your critical threads
- Use exceptions for control flow
- Trust the compiler—verify generated assembly
- Log synchronously on the critical path
Prefer
- Busy-waiting over blocking
- Batch processing over item-by-item
- Inline functions over virtual dispatch
- Fixed-size structures over dynamic allocation
- Struct-of-arrays over array-of-structs (for cache efficiency)
- Direct hardware access over OS abstractions
Code Patterns
Kernel Bypass Networking with DPDK
class DPDKMarketDataReceiver {
private:
struct rte_mempool* mbuf_pool_;
uint16_t port_id_;
alignas(64) Stats stats_;
public:
void init(uint16_t port_id) {
port_id_ = port_id;
mbuf_pool_ = rte_pktmbuf_pool_create(
"MBUF_POOL",
8192,
256,
0,
RTE_MBUF2_BUF_SIZE,
rte_socket_id()
);
struct rte_eth_conf port_conf = {};
port_conf.rxmode.mq_mode = ETH_MQ_RX_NONE;
port_conf.txmode.mq_mode = ETH_MQ_TX_NONE;
port_conf.rxmode.offloads = 0;
port_conf.txmode.offloads = 0;
rte_eth_dev_configure(port_id_, 1, 0, &port_conf);
}
__attribute__((always_inline, hot))
void poll_packets(PacketHandler& handler) {
struct rte_mbuf* bufs[];
nb_rx = (port_id_, , bufs, );
((nb_rx > )) {
((bufs[], *));
}
( i = ; i < nb_rx; i++) {
(i + < nb_rx) {
((bufs[i + ], *));
}
* data = (bufs[i], *);
len = (bufs[i]);
handler.(data, len);
(bufs[i]);
}
stats_.packets_received += nb_rx;
}
};
Lock-Free Order Book
template<size_t MAX_LEVELS = 256>
class alignas(64) LockFreeOrderBook {
private:
struct PriceLevel {
std::atomic<int64_t> price;
std::atomic<int64_t> quantity;
};
alignas(64) std::array<PriceLevel, MAX_LEVELS> bids_;
alignas(64) std::array<PriceLevel, MAX_LEVELS> asks_;
alignas(64) std::atomic<uint64_t> sequence_;
public:
__attribute__((always_inline))
void update_bid(size_t level, int64_t price, int64_t qty) {
bids_[level].price.store(price, std::memory_order_relaxed);
bids_[level].quantity.store(qty, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
sequence_.fetch_add(1, std::memory_order_relaxed);
}
__attribute__((always_inline))
bool read_bbo(int64_t& bid, & ask, & bid_qty, & ask_qty) {
seq1, seq2;
{
seq1 = sequence_.(std::memory_order_acquire);
bid = bids_[].price.(std::memory_order_relaxed);
bid_qty = bids_[].quantity.(std::memory_order_relaxed);
ask = asks_[].price.(std::memory_order_relaxed);
ask_qty = asks_[].quantity.(std::memory_order_relaxed);
std::(std::memory_order_acquire);
seq2 = sequence_.(std::memory_order_relaxed);
} (seq1 != seq2 || (seq1 & ));
;
}
};
CPU Pinning and Isolation
class LatencyCriticalThread {
public:
void configure_for_low_latency(int cpu_core) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_core, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
struct sched_param param;
param.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
mlockall(MCL_CURRENT | MCL_FUTURE);
prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0);
}
};
cpuidle/state*/disable; do
echo 1 > $cpu
done
# Set CPU frequency to maximum
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > $cpu
done
*/
Memory Pool with Zero Allocation
template<typename T, size_t POOL_SIZE = 65536>
class alignas(64) ObjectPool {
private:
struct alignas(64) Slot {
std::aligned_storage_t<sizeof(T), alignof(T)> storage;
std::atomic<Slot*> next;
};
std::array<Slot, POOL_SIZE> slots_;
alignas(64) std::atomic<Slot*> free_list_;
public:
ObjectPool() {
for (size_t i = 0; i < POOL_SIZE - 1; i++) {
slots_[i].next.store(&slots_[i + 1], std::memory_order_relaxed);
}
slots_[POOL_SIZE - 1].next.store(nullptr, std::memory_order_relaxed);
free_list_.store(&slots_[0], std::memory_order_release);
volatile char* ptr = reinterpret_cast<volatile char*>(slots_.data());
for (size_t i = 0; i < sizeof(slots_); i += 4096) {
ptr[i] = 0;
}
}
__attribute__((always_inline))
{
Slot* slot;
{
slot = free_list_.(std::memory_order_acquire);
(!slot) ;
} (!free_list_.(
slot, slot->next.(std::memory_order_relaxed),
std::memory_order_release, std::memory_order_relaxed));
<T*>(&slot->storage);
}
__attribute__((always_inline))
{
Slot* slot = <Slot*>(ptr);
Slot* head;
{
head = free_list_.(std::memory_order_relaxed);
slot->next.(head, std::memory_order_relaxed);
} (!free_list_.(
head, slot,
std::memory_order_release, std::memory_order_relaxed));
}
};
Latency Measurement
class LatencyHistogram {
private:
static constexpr size_t BUCKETS = 1000;
alignas(64) std::array<std::atomic<uint64_t>, BUCKETS> histogram_;
std::atomic<uint64_t> overflow_;
public:
__attribute__((always_inline))
void record(uint64_t latency_ns) {
uint64_t bucket = latency_ns / 1000;
if (bucket < BUCKETS) {
histogram_[bucket].fetch_add(1, std::memory_order_relaxed);
} else {
overflow_.fetch_add(1, std::memory_order_relaxed);
}
}
LatencyStats get_stats() const {
uint64_t total = 0;
uint64_t count = 0;
uint64_t p50_bucket = 0, p99_bucket = 0, p999_bucket = 0;
for (size_t i = 0; i < BUCKETS; i++) {
uint64_t bucket_count = histogram_[i].load(std::memory_order_relaxed);
count += bucket_count;
total += bucket_count * i;
}
running = ;
( i = ; i < BUCKETS; i++) {
running += histogram_[i].(std::memory_order_relaxed);
(p50_bucket == && running >= count * ) p50_bucket = i;
(p99_bucket == && running >= count * ) p99_bucket = i;
(p999_bucket == && running >= count * ) p999_bucket = i;
}
{
.mean_us = <>(total) / count,
.p50_us = p50_bucket,
.p99_us = p99_bucket,
.p999_us = p999_bucket,
.count = count
};
}
};
__attribute__((always_inline))
{
lo, hi;
;
(()hi << ) | lo;
}
Mental Model
Citadel approaches low-latency systems by asking:
- What's the latency budget? Allocate nanoseconds to each component
- Where are the syscalls? Eliminate them from the hot path
- Where are the locks? Replace with lock-free alternatives
- Where are the allocations? Pre-allocate everything
- What's the worst case? Optimize for tail latency, not average
Signature Citadel Moves
- Kernel bypass with DPDK/OpenOnload
- Lock-free data structures everywhere
- CPU pinning and isolation
- Pre-allocated memory pools
- Busy-polling over blocking
- RDTSC for timing
- Cache-line alignment
- Disabled OS features (HT, C-states, THP)
- Assembly-level verification
- Nanosecond-precision measurement