About this update. This article was first published in 2017 β the original Part I (2017) is still online for reference. The architectural principles still hold in 2026. What has shifted is the protocol landscape, the C++ memory toolkit, and a couple of LOB-layout refinements. The original structure and voice are intact; inline notes flag what is new.
In this article, I would like to talk about how to implement a low latency system, modules needed and its system architecture and be focus on the software development side.
There are other parts that need to be covered in order to have a complete system, and they might be things like network communications, switches/routers, specialized servers, FPGAs, system operating tweaking, like kernel-bypassing, etc. But, as I said, in this article I will be more focused on software design and its architecture.
This is a basic approach, to what I consider best practices for this kind of trading system. Depending on different factors, markets, compliances, this may vary, but some basics will remain the same for any trading system.
Of course, that the key factor here is being able to handle a big amount of incoming information, response time to external events, internal response time, and capabilities to provide the highest throughput and lowest latency.
When I design low latency systems, I always found the following challenges:
- The system must be able to handle multiple strategies, making sure that the system will not underperform as we add more strategies.
- Order book reconstruction from different venues. If we take as an example like the forex market, there are tons of different venues and ECNs, all of them using different API and connectivity standards. The system must be able to handle those different sources and aggregate them into an internal and well-defined data structure.
Thank you for reading this post, don't forget to subscribe!
2026 note. The two-challenge framing still holds. Crypto has compounded the second one β 300+ active venues with no consolidated tape and heavier API heterogeneity (WebSocket, REST, FIX, native binary, SBE on some majors).
1. Data Feed Handler
The feed handler is one of the most important components of any algorithmic trading system. Without accurate market data, any high-frequency or algorithmic strategy won’t be able to make correct decisions. So, the idea of this module is to capture market data from different venues, to allow our strategies, generate correct decisions, and keep a representation of venues’ limit order book.
A large number of market updates can be received per second for each market venue, and your internal representation of each limit order book should change accordingly.
This process should go like this:
The link between venues and your system is usually made using FIX protocol, so I have to put a FIX engine, in order to communicate with them (receive and send messages) and provide the core infrastructure that interfaces and manages the connectivity into the venues using FIX protocol.
Receiving message will be receiving market updates, and sending, will be sending orders generated by my strategies.
There are two options to implement a FIX Engine: custom made, or a commercial option. To implement a custom FIX engine, you will need to put too many man-hours and you can make sure that you will optimize the communication for a low latency environment. Firms like large banks and institutions will prefer their own FIX engine, so they can have ownership of the entire code.
I prefer to go with the commercial option, since there are too many good options out there, and you just need to plug their library, and you will be ready to go.
Note that besides network and communication latency, there also will be a decode/parse latency, so I’ve to take care of it as well. Parsing is a string manipulation, hence very expensive in terms of CPU cycles and memory management.
There are venues that also started to provide connectivity through newer and better protocols since FIX is becoming too slow for nowadays algorithms. FAST, ITCH/OUTCH are binary protocols and I always try to use them as long they are available. They will use a different engine to communicate, but the concept remains the same.
2026 update β the protocol stack has shifted. FAST is now legacy; SBE (Simple Binary Encoding, FIX Trading Community Final Standard since November 2020) has become the standard binary protocol on the ultra-low-latency hot path. CME’s iLink 2 was decommissioned on April 6, 2025 β any team still on it after that date was not trading on Globex. ICE rolled out its SBE-based Binary Order API to the Basildon silo (Oct 31, 2025) and Chicago silo (Feb 20, 2026). Euronext Optiq, MEMX, B3, Bybit, and Binance all run SBE. ITCH 5.0 is still Nasdaq’s standard (transport-only change: Nasdaq Nordic is migrating ITCH from TCP to UDP, completing October 2027). FIX is not displaced β it’s re-scoped: hot path uses the venue’s binary API; compliance, drop-copy, prime broker, and non-binary venues stay on FIX with a commercial engine (my 2017 commercial-vs-custom recommendation is unchanged for this layer).
2. Limit Order Book
Once the system has its connectivity between the venues, I will need to update all the events reported by them: order updates/adds or deletions (trades if needed as well).
For each event received I will go into my internal order book and make the changes it needs to be made.
Usually, all venues send these updates using a unique identifier (EntryID) and the update type (update is an insert, update or delete) so you can accurately replicate their limit order book.
One important thing to keep in mind here is what type of data structure we want to use to hold the order book data. Remember that we can get several million updates per second, so our data structure should be fast enough to find an entry or delete it.
From previous recommendations, I stated that the best data structure is to use plain arrays: one for the bids, one for the asks. They provide the best performance.
Moreover, pre-allocate as much memory as you can.
Dynamic allocation is expensive, should not be used in critical paths like updating a limit order book data structure and you will end up having a large amount of overhead per allocation.
The naive (and wrong) approach looks like this β every add and cancel may invoke the allocator on the hot path:
// Hot-path order book using std::vector β DO NOT do this in production
#include <algorithm>
#include <cstdint> // int64_t, int32_t
#include <functional> // std::greater
#include <ranges>
#include <vector>
struct PriceLevel {
double price;
int64_t quantity;
int32_t orders;
};
std::vector<PriceLevel> bids; // resizable β backed by the heap
void on_add(const PriceLevel& lvl) {
bids.push_back(lvl); // may call malloc
std::ranges::sort(bids, std::greater{}, &PriceLevel::price);
}
void on_cancel(double price) {
std::erase_if(bids, [=](const auto& l) { // may call free
return l.price == price;
});
}
But since we are building a low latency system, we need to pre-allocate our data structures. And I’m going to show you the difference between pre-allocating and dynamic allocation, and its performance impact.
2026 refinement on the LOB. Plain arrays is still right. Two layout details I underemphasized in 2017: use Structure of Arrays (separate contiguous arrays for prices, quantities, counts) rather than Array of Structures β for signal code that scans one or two fields per level, the cache locality is substantially better. And align the arrays to 64-byte cache lines (C++17’s
std::hardware_destructive_interference_sizemakes this portable) to avoid false sharing under concurrent bid/ask updates. The advice is “plain arrays, laid out as SoA, cache-line-aligned.”
// 2026 layout: SoA + cache-line alignment
#include <array>
#include <cstdint> // int64_t, int32_t
#include <new> // std::hardware_destructive_interference_size
inline constexpr std::size_t kDepth = 10;
inline constexpr std::size_t kCacheLine =
std::hardware_destructive_interference_size;
struct alignas(kCacheLine) BookSide {
std::array<double, kDepth> prices; // SoA: one field per array
std::array<int64_t, kDepth> quantities;
std::array<int32_t, kDepth> orders;
};
inline BookSide bidSide; // bid-update thread owns this
inline BookSide askSide; // ask-update thread owns this
// No false sharing: each BookSide lives on its own cache line(s).
2026 update on pre-allocation. Modern allocators (jemalloc, tcmalloc) have improved a lot and are worth using off-path. On the hot path, allocation is still not acceptable β pre-allocation, arena allocators, and placement-new into pre-allocated buffers remain the only patterns that work. C++17’s
std::pmrnow provides arena semantics in the standard library, so you no longer have to roll your own pool.
// 2026: standard-library arena allocation (no custom pool needed)
#include <array>
#include <cstddef>
#include <memory_resource>
#include <vector>
// kCacheLine defined in the SoA block above
alignas(kCacheLine) std::array<std::byte, 4 << 20> session_buffer;
std::pmr::monotonic_buffer_resource arena{
session_buffer.data(), session_buffer.size()
};
// Hot-path container backed by the arena β zero malloc, zero free
std::pmr::vector<OrderMessage> in_flight{&arena};
void on_session_boundary() {
in_flight.clear();
arena.release(); // single-instruction reclaim
}
On the system initialization phase, pre-allocate these arrays: bidsArray and askArrays, and let’s say we are going to store a 10-level depth of the book, hence, we will need to pre-allocate 10-element bid/ask array. And we are going to move/replace elements, NOT removing and creating since the allocation process will consume too much time.
You define the array with 10 elements on it and you will reuse all those elements. You may notice that instead of deleting the element, I’m clearing its values, and once ordered, sent them at the beginning of the array.
// Pre-allocated, fixed-size β the hot-path-safe pattern
#include <algorithm>
#include <array>
std::array<PriceLevel, kDepth> bidsArray{}; // pre-allocated at init
std::array<PriceLevel, kDepth> asksArray{};
// Cancel: clear the slot in place, shift the cleared cell to the back
void on_cancel(std::array<PriceLevel, kDepth>& side, std::size_t idx) {
side[idx] = PriceLevel{}; // clear values
std::rotate(side.begin() + idx,
side.begin() + idx + 1,
side.end()); // cleared slot β back
}
// Add at top of book: shift entries down by one, drop the last
void on_add(std::array<PriceLevel, kDepth>& side, const PriceLevel& lvl) {
std::rotate(side.rbegin(), side.rbegin() + 1, side.rend());
side[0] = lvl; // new best at top
}
Once I run a performance test between them, counting CPU cycles taken on each case on a sample of 100,000 iterations, I get the following results showing a 60% difference. Not a surprise for me, allocation/deallocation is expensive. And this was a very basic/simple example!!!
You can check this code example on my gist repository https://gist.github.com/silahian.
2026 note on the benchmark. I re-ran this comparison against modern allocators in 2026. Direction is identical β pre-allocation still wins by a large margin on the hot path. The exact percentage shifts depending on which allocator backs dynamic allocation (jemalloc and tcmalloc narrow the gap a bit versus the 2017 system allocator), but the architectural conclusion is unchanged: pre-allocate, reuse, do not allocate on the critical path.
To be continued⦠Part II covers the order management system, strategies (latency arbitrage, triangular arbitrage), and the design patterns for combining components under microsecond constraints. Part III covers the busy/wait pattern, position and risk management, and monitoring. Both will be published in the next few days, updated for 2026 in the same UPDATE-not-rewrite style as this one.
This is the 2026 update to the original 2017 article β same structure, same voice, same 60% benchmark example. Inline notes mark what has changed.
Never Miss an Update
Get notified when we publish new analysis on HFT, market microstructure, and electronic trading infrastructure. No spam.
Subscribe by EmailAriel Silahian is a senior technology executive in institutional electronic trading, with 30+ years across the buy and sell side (New York, Miami, London, Hong Kong). He is the author of "C++ High Performance for Financial Systems" (Packt) and the creator of VisualHFT, the open-source microstructure analytics stack. He writes on exchange architecture, market microstructure, and execution quality, and advises a select number of trading firms on infrastructure decisions that move P&L. Book a strategy call at hftAdvisory.com



