Market Data Acquisition
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Why Data First
Market data is your application's heartbeat
Now that we've covered Introduction to Algorithmic Trading, built a usable C++ setup in Setting Up Your C++ Development Environment, and practiced latency-aware containers in Data Structures and Algorithms for Trading, we're ready to connect the application to the outside world.
In this lesson, we'll focus on market data acquisition: getting prices, trades, and order book updates into your C++ program in a clean, fast, reliable format.
The goal
By the end, you should understand how to design a small acquisition layer that:
- receives raw market data messages
- parses them into typed C++ structures
- normalizes different message formats
- detects obvious feed problems
- prepares clean input for the trading logic you'll build next
We are not deciding when to buy or sell yet. First, we need trustworthy data.

Feed Mental Model
Think in messages, not just prices
A market data feed is usually a stream of small messages. Each message says something changed.
Common message types:
- Trade: an actual transaction happened
- Quote: best bid/ask changed
- Order book update: a price level was added, changed, or removed
- Snapshot: current state of the book at a moment in time
- Heartbeat: feed is alive, even if no market event occurred
Simple acquisition pipeline
Raw Feed -> Parse -> Validate -> Normalize -> Buffer -> Consumers
The key design idea: keep the acquisition layer boring and predictable. It should not contain strategy rules. Its job is to transform external messages into internal facts.

Pick The Boundary
Your C++ application receives market data from an exchange API. Which responsibility belongs most clearly inside the market data acquisition layer?
Parse Trade Messages
Start with a tiny typed event
Real feeds may use binary protocols, WebSockets, FIX, CSV-like payloads, or vendor SDK objects. But the C++ habit is the same:
- receive raw data
- parse into a strongly typed structure
- reject invalid records early
- pass clean events downstream
Here is a small standalone example using a simplified CSV trade message:
symbol,price,quantity,exchange_timestamp_ns
This is not a production parser, but it demonstrates the acquisition-layer shape.
```cpp
#include <iostream>
#include <sstream>
#include <string>
#include <optional>
struct TradeEvent {
std::string symbol;
double price{};
int quantity{};
long long exchangeTimestampNs{};
};
std::optional<TradeEvent> parseTrade(const std::string& raw) {
std::stringstream ss(raw);
std::string symbol, priceText, qtyText, tsText;
if (!std::getline(ss, symbol, ',')) return std::nullopt;
if (!std::getline(ss, priceText, ',')) return std::nullopt;
if (!std::getline(ss, qtyText, ',')) return std::nullopt;
if (!std::getline(ss, tsText, ',')) return std::nullopt;
try {
TradeEvent event;
event.symbol = symbol;
event.price = std::stod(priceText);
event.quantity = std::stoi(qtyText);
event.exchangeTimestampNs = std::stoll(tsText);
if (event.symbol.empty() || event.price <= 0.0 || event.quantity <= 0) {
return std::nullopt;
}
return event;
} catch (...) {
return std::nullopt;
}
}
int main() {
std::string raw = "AAPL,192.45,100,1712345678901234567";
auto parsed = parseTrade(raw);
if (parsed) {
std::cout << "Trade parsed\n";
std::cout << "Symbol: " << parsed->symbol << "\n";
std::cout << "Price: " << parsed->price << "\n";
std::cout << "Quantity: " << parsed->quantity << "\n";
std::cout << "Exchange timestamp ns: " << parsed->exchangeTimestampNs << "\n";
} else {
std::cout << "Invalid trade message\n";
}
return 0;
}
```
Raw Equals Ready
True or false: once a market data message is received from a broker or exchange API, it is safe to send directly into the trading algorithm without validation or normalization.
Maintain Book State
Order book acquisition is stateful
A trade event can often stand alone: symbol, price, quantity, time.
Order book data is different. Many feeds send incremental updates, meaning each message changes one price level. Your application must maintain the current state.
A common internal representation is:
- bids sorted from highest price to lower price
- asks sorted from lowest price to higher price
- quantity
0means remove that level
Below is a compact example using std::map. It is not the fastest possible book, but it is clear and correct for learning. In low-latency production systems, you may choose flatter arrays, custom allocators, or price-indexed structures.
```cpp
#include <iostream>
#include <map>
#include <string>
struct BookUpdate {
std::string side; // "B" for bid, "A" for ask
double price{};
int quantity{}; // 0 means delete level
};
class OrderBook {
public:
void apply(const BookUpdate& update) {
auto& bookSide = (update.side == "B") ? bids : asks;
if (update.quantity == 0) {
bookSide.erase(update.price);
} else {
bookSide[update.price] = update.quantity;
}
}
void printTop() const {
std::cout << "Top of book\n";
if (!bids.empty()) {
auto bestBid = bids.rbegin();
std::cout << "Best bid: " << bestBid->first << " x " << bestBid->second << "\n";
} else {
std::cout << "Best bid: none\n";
}
if (!asks.empty()) {
auto bestAsk = asks.begin();
std::cout << "Best ask: " << bestAsk->first << " x " << bestAsk->second << "\n";
} else {
std::cout << "Best ask: none\n";
}
}
private:
std::map<double, int> bids;
std::map<double, int> asks;
};
int main() {
OrderBook book;
book.apply({"B", 100.10, 500});
book.apply({"B", 100.15, 300});
book.apply({"A", 100.25, 400});
book.apply({"A", 100.30, 250});
book.printTop();
std::cout << "\nAfter removing best bid:\n";
book.apply({"B", 100.15, 0});
book.printTop();
return 0;
}
```
Sequence Number Check
Many market data feeds include a sequence number so your application can detect missing or out-of-order messages.
If the last processed sequence number is 105, the next expected sequence number is ____.
Time And Quality
Fast data is not enough
In trading systems, bad data can be more dangerous than no data.
Your acquisition layer should track quality signals such as:
- exchange timestamp: when the market event happened
- receive timestamp: when your process saw it
- sequence number: whether messages are missing or out of order
- symbol validity: whether the instrument is known
- staleness: whether the feed has gone quiet unexpectedly
- duplicate detection: whether the same event arrived twice
Two useful timestamps
Exchange timestamp -> market event time
Receive timestamp -> your machine's observation time
The difference helps you estimate feed delay, but treat this carefully: machine clocks, network paths, and vendor infrastructure all matter.

Normalize Carefully
You connect two data vendors. One sends trade quantity as shares, another sends quantity in lots. What should the acquisition layer do before forwarding events downstream?
Acquisition Checklist
What you have now
You now have the mental model for the first real input layer of your C++ trading application.
A practical market data acquisition component should answer:
- What feed source am I reading from?
- What raw message formats can arrive?
- What internal event structures do I produce?
- How do I validate malformed or impossible data?
- How do I track sequence numbers and timestamps?
- How do I maintain stateful structures like an order book?
- What do I log when the feed is stale, broken, or out of sync?
Keep the boundary clean
Market Data Acquisition = facts about the market
Trading Algorithm = decisions based on those facts
Next, we'll use this cleaned-up stream of market facts to start Developing Trading Algorithms.
