Market Data

TL;DR

Bind a venue data source, subscribe to its typed topic, and react to every event in your handler. Topic keys are compile-time literals validated against known schemas — a typo in the key is a build error, not a silent runtime failure.

Why canonical topic keys?

Aurum uses compile-time topic keys like bar://okx/BTCUSDT@1m instead of runtime-registered string keys. This is a deliberate design choice with three consequences:

  1. Typos are build errors. bar://okx/BTCUSDT@1n (invalid interval) does not compile. You find out in your editor, not after six hours of silent data loss in production.

  2. Schema binding is automatic. The topic literal "bar://okx/BTCUSDT@1m" deduces Tick<schema::BarRow> at compile time. Your handler cannot accept the wrong payload type — the compiler rejects it.

  3. The key format is self-describing. The scheme identifies the resource family (bar://), the authority identifies the source (okx), and the path encodes the instrument and parameters (BTCUSDT@1m). Clients can parse keys without an external schema registry.

Alternative considered: runtime string keys (like Kafka topics). Runtime keys are more flexible — you can add topics without recompiling. But they push data contract errors to runtime: a consumer expecting a different schema than the producer receives garbled data with no compile-time warning. For a trading system where data correctness is the primary requirement, Aurum chose compile-time safety over runtime flexibility.

Bind a feed

#include <aurum/venue/okx.hpp>

bind<Datafeed>(venue::okx::Bar("BTCUSDT", "1m"));
bind<Datafeed>(venue::okx::Orderbook("BTCUSDT"));

The bind<Datafeed> call registers the feed with the engine. The venue factory normalizes the exchange-specific connection details — you pass the symbol and interval; the factory handles authentication, reconnection, and protocol decoding.

Subscribe to its topics

on(TOPIC<"bar://okx/BTCUSDT@1m">,
   [](const Tick<schema::BarRow>& tick) {
  if (tick.payload.close_timestamp == 0) return;
  log::info("close={}", tick.payload.close.to_string());
});

on(TOPIC<"book://okx/BTCUSDT@full">,
   [](const Tick<schema::BookRow>&) {
  // Read the current projected book through the runtime view.
});

The topic literal and payload type form one contract. A mismatched schema is a compile-time error where the SDK has a canonical binding.

Note

An unconfirmed bar has close_timestamp == 0. The bar event still fires — it carries open, high, low, and volume but the close is not yet final. A handler that intentionally ignores unconfirmed bars can appear idle until the current interval closes. This is correct behavior, not a data feed failure.

Canonical topic families

Family

Example

Payload

Bars

bar://okx/BTCUSDT@1m

schema::BarRow

Books

book://okx/BTCUSDT@full

schema::BookRow

Factors

factor://spread/btcusdt.okx@bar:1m

Scalar factor value

Order events

order://paper/filled

Order event envelope

Resource discovery

A running application publishes a resource catalog. Forge and Terminal use that catalog to discover exact topic keys, schemas, delivery models, and history availability.

Warning

Clients must not guess a topic from its display label. Terminal shows a human-readable label for each resource; the underlying key may differ. If you construct a key from a label, your subscription will silently receive no data because the key does not match any published resource. Always copy the key from the catalog.

Common mistakes

Ignoring unconfirmed bars.

A bar event with close_timestamp == 0 is an in-progress bar. If your handler processes it as a complete bar, your calculations will use an unconfirmed close price that may change when the interval ends. Check close_timestamp before using the close value.

Constructing keys from labels.

Terminal labels are for display; topic keys are for subscriptions. They are not the same string. Always use the exact key from the resource catalog or the venue factory documentation.

Binding a feed without a subscriber.

bind<Datafeed> registers the connection, but data only flows when at least one handler subscribes through on(). If you bind a feed and see no data, check that you have an on() call for that topic.

Assuming all order books are the same depth.

book://okx/BTCUSDT@full delivers the full book. @5 and @bbo-tbt deliver different depths. The depth is encoded in the key; there is no default. Specify the depth you need in the factory call.

Next steps