Skip to content
View ctj01's full-sized avatar

Block or report ctj01

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Content in all repositories owned by your account will be closed.
Maximum 250 characters. Please don’t include any personal information such as legal names or email addresses. Markdown is supported. This note will only be visible to you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse
ctj01/README.md

Cristian Mendoza

Low-latency systems in C++ — market data, lock-free concurrency, and the measurements behind both

LinkedIn • Email • TradingView


About

Software engineer, five years in, mostly in the two industries where being wrong is expensive: finance and healthcare. I have built clinical systems handling patient records — where a bug is not a rollback, it is someone's medical history — and trading infrastructure where the cost of being wrong is measured in basis points. Both taught the same lesson from opposite ends. Confidence is not evidence.

That is roughly how I ended up here. I spent years writing C# and .NET, and at some point started wondering why processing a market data event takes microseconds in one language and nanoseconds in another. The answer had very little to do with the languages and everything to do with cache lines, memory ordering, and how often you ask the operating system for permission to continue. Rather than take anyone's word for it, I started rebuilding the pieces myself and measuring each one.

That is what most of these repositories are. Not tutorials, not libraries anyone asked for — the things I wanted to understand well enough to build from scratch, with benchmarks run on my own hardware instead of numbers quoted from a book.

I have a habit of not believing things, including my own results. A green test suite means nothing until you have broken the code on purpose and watched it turn red — so I do that first, and only then trust the clean run. The same scepticism goes for consensus in markets: my most-read writing has been the contrarian calls, and the ones worth publishing are the ones where I could show the work rather than assert a view. Several times that habit has forced me to publish a result I did not want. Cache-line padding bought 1.16x, not the 2-3x everyone repeats. An optimisation I was sure about changed the throughput by nothing at all. Those went in the READMEs too.

Lately I have been working where software engineering, quantitative methods and machine learning overlap — mean reversion, volatility forecasting, regime detection — and building the low-latency systems underneath them. That intersection is where I want to be.


What I have been building

cpp-hft — a trading engine, rewritten in C++

I had a working market data and market making engine in C#. It handled about 3,500 events per second, end to end in roughly 200 microseconds, which was fine. Then I wondered how much of that was the problem and how much was the runtime.

So I started porting it, beginning with the parts where C++ actually earns its keep: the ring buffer, the order book, and the path between them. Measured over 500,000 simulated Binance messages flowing through two real threads, the whole chain — parse the JSON, cross the ring buffer, update the book, run the strategy, produce a quote — now takes 417 nanoseconds at the median, and sustains 11.4 million events per second.

The honest caveat, which the repo states too: the C# number includes the network hop and this one does not, so it is not a clean comparison. The tail latency (p99 and beyond) is mostly the scheduler preempting the process, since I have no CPU isolation or thread pinning on this machine.

The repo also collects the lessons that got me there, each with a reproducible benchmark. My favourite is the one that taught me the most: a formally incorrect memory_order that passed twenty out of twenty test runs on x86-64 and would have broken silently on ARM. Nothing caught it except ThreadSanitizer. At this level, the bugs that matter often produce no signal at all — correct output, clean build, passing tests, and wrong on hardware you do not own.

C++20, CMake, atomics, ThreadSanitizer, Valgrind


OpenHFT-Lab — the C# engine it came from

The original. A live Binance WebSocket feed into lock-free ring buffers, an L2 order book, market making and triangular arbitrage strategies, and a real-time dashboard over SignalR.

It is still the most complete system I have built, and the one people have found most useful — it is by some distance the most starred thing on this profile. Writing it is what made me curious about the layer below it.

C#/.NET 8, unsafe pointers, volatile and memory barriers, SignalR


spsc-ring — one lock-free queue, done properly

A single-producer single-consumer ring buffer, which is the smallest piece of the puzzle above and the one where every detail is visible.

It moves 462 million items per second between two threads — about 25 times what the same handoff costs through a mutex. But the number I care about more is this: I deliberately broke the memory ordering, confirmed ThreadSanitizer reported the race, and only then trusted the clean run on the correct version. A passing sanitizer proves nothing unless you know it would have failed.

I also ran it against the live Binance order book for BTC. Three hundred and sixteen updates applied to a local book with zero sequence gaps, which is the only real proof that the thing works — a dropped or reordered message corrupts the book silently, and the exchange's own sequence numbers are what tell you.

Two findings I wrote down because they contradicted what I expected. Cache-line padding bought 1.16x here, not the 2-3x that gets quoted around. And the latency I measured turned out to be almost entirely my consumer's waiting policy, not the queue — changing it cut the 99th percentile by 184x without touching the ring at all.

C++20, atomics and memory ordering, Catch2, Google Benchmark


thread-pool — concurrency primitives from scratch

A header-only thread pool built on std::jthread, with cooperative cancellation modelled on .NET's CancellationToken — the one thing I genuinely missed coming from C#.

The interesting part was measuring where the time actually goes. Dispatching a task costs about 30 microseconds, and 95% of that is the thread wakeup, not the queue and not the allocation. That single measurement killed two optimisations I had planned and pointed at the one that worked: batching. A parallel for_each that submits one task per chunk instead of one per element runs roughly 1,255 times faster on fine-grained work.

I have kept the negative results in the README as well, since they were the useful part. Replacing std::function with a custom task type halved the allocations and changed the throughput by nothing at all.

C++20, jthread and stop_token, ASan, TSan


regime-discovery-cpp — finding market regimes without labels

Every trading day becomes a feature vector over a trailing window — realised volatility, momentum, drawdown, skewness. Index those with HNSW, cluster them with HDBSCAN, and market regimes fall out as dense regions of the space. No labels, no supervision, nothing told it what a "crisis" looks like.

Validated across 33 years of SPY. Both the nearest-neighbour index and the clustering are written from scratch, with an exact brute-force implementation kept alongside as the oracle that keeps the approximate one honest.

C++20, HNSW, HDBSCAN, 33 years of daily data


rag-system-cpp — vector search, hand-written

A vector similarity search engine built from nothing, with a local RAG pipeline on top as a demonstration. Everything runs on the machine; no external APIs.

The point was never the RAG part. It was the numeric core: contiguous cache-friendly storage, a hand-written AVX2 SIMD kernel that dispatches on what the CPU actually supports at startup, and an HNSW index. As in the regime work, an exact index sits next to the approximate one so that recall can be measured rather than assumed.

C++20, AVX2 intrinsics, runtime CPU dispatch, HNSW


automatic-differentiation-engine — how backpropagation actually works

Reverse-mode automatic differentiation in C++17, with no dependencies beyond a test framework. It builds the computation graph implicitly as you write expressions, then differentiates exactly in one backward sweep — and it is complete enough to train a small neural network end to end.

I built this because I wanted to stop treating loss.backward() as a magic incantation. It is neither symbolic differentiation nor finite differences, and understanding precisely why turned out to be worth the weekend.

C++17, reverse-mode AD, GoogleTest


Background

Day job: Senior .NET Developer — distributed systems, event-driven architecture, and the SQL performance work that tends to come with both.

On the quantitative side I finished Columbia's Financial Engineering and Risk Management coursework, covering derivatives pricing, Itô calculus, Markowitz and CAPM, VaR and CVaR, and Monte Carlo methods. I also run a statistical arbitrage strategy on QQQ/IWM against an Interactive Brokers paper account — Kalman filter for the dynamic hedge ratio, HMM regime filter on top, 1.77 out-of-sample Sharpe. The binding constraint there is transaction cost modelling rather than signal quality, which is the part most write-ups skip.

Self-taught throughout. The order that works for me is intuition first, then the mathematics, then the code. Doing it in the other direction produces something that runs and teaches me nothing.


What I work with

Day to day: C++20 for anything where latency matters, C#/.NET in production, and Python for research and anything statistical.

The specifics that come up most: lock-free data structures and memory ordering, cache-aware data layout, SIMD, and measuring any of it honestly — which usually means ThreadSanitizer, Valgrind, and Google Benchmark, plus enough scepticism to break my own code on purpose before trusting a green run.


Writing

I publish on macro and market microstructure on LinkedIn, usually where the two meet. Recent pieces: DXY accumulation, systemic risk in private credit, and a bear case on MicroStrategy.

Pinned Loading

  1. OpenHFT-Lab-High-Frequency-Trading-Laboratory OpenHFT-Lab-High-Frequency-Trading-Laboratory Public

    OpenHFT-Lab is a comprehensive, high-performance trading system designed for educational and research purposes. It demonstrates advanced concepts in high-frequency trading, including sub-millisecon…

    C# 23 7

  2. regime-discovery-cpp regime-discovery-cpp Public

    Unsupervised market regime discovery in C++ — custom HNSW + HDBSCAN, validated on 33 years of SPY”

    HTML 1

  3. cpp-hft cpp-hft Public

    Low-latency trading engine in C++20 — 417 ns p50 tick-to-decision, 11.4M events/sec. A port of the C# OpenHFT-Lab, with the benchmarks behind every decision.

    C++

  4. spsc-ring spsc-ring Public

    Lock-free SPSC ring buffer in C++20 — 462M items/sec, 25x a mutex. Validated against the live Binance BTC order book with zero sequence gaps.

    C++

  5. thread-pool thread-pool Public

    Header-only C++20 thread pool — jthread workers, cooperative cancellation, and parallel for_each that is ~1,255x faster than one task per element.

    C++