AlgoDaily

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.

Why Data First

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.

Feed Mental Model

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:

  1. receive raw data
  2. parse into a strongly typed structure
  3. reject invalid records early
  4. 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 0 means 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.

Time And Quality

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.

Acquisition Checklist

Open interactive view →