You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
C++ SDK for ThetaData market data. Header-only RAII wrappers over the thetadatadx Rust crate via the shared C FFI layer.
Every call crosses the C ABI boundary into compiled Rust: gRPC communication, protobuf parsing, zstd decompression, and TCP streaming run inside the thetadatadx crate.
Surface coverage: the C++ binding exposes all three ThetaData surfaces — MDDS (historical), FPSS (streaming), and FLATFILES (whole-universe daily blobs). Flat files land via unified.flat_files().*() with .to_arrow_ipc() terminals plus a flat_files().to_path(...) raw-bytes helper — see the Flat Files section for the full method list.
Prerequisites
C++17 compiler
CMake 3.16+
Rust toolchain (for building the FFI library)
Platform Support
Linux: CI-validated
macOS: CI-validated
Windows: CI-validated
Building
First, build the Rust FFI library:
# From the repository root
cargo build --release -p thetadatadx-ffi
All price fields (open, high, low, close, bid, ask, price, strike) are double (f64) -- decoded during parsing. No price_type in the public API.
Contract identification fields (bold above): expiration, strike, right are populated by the server on wildcard queries (pass "0" for expiration/strike). On single-contract queries these fields are 0. The right input parameter accepts "call", "put", "C", "P" (case-insensitive), plus "both"/"*" on endpoints that support a wildcard -- not "0". Output values stay "C"/"P".
FPSS Streaming
Real-time market data via ThetaData's FPSS servers. Streaming uses a separate FpssClient class, not methods on Client. Events are returned as typed #[repr(C)] structs -- no JSON parsing on the hot path.
#include"thetadx.hpp"
#include<iostream>intmain() {
auto creds = tdx::Credentials::from_file("creds.txt");
auto config = tdx::Config::production();
// Create a streaming client (separate from the historical Client)
tdx::FpssClient fpss(creds, config);
// Register a queued callback. The LMAX Disruptor consumer thread// invokes `fn` for every event under `catch_unwind`; the FPSS// reader thread never blocks on user code.
fpss.set_callback([](const tdx::FpssEvent& event) {
if (event.kind == TDX_FPSS_QUOTE) {
std::cout << "quote bid=" << event.quote.bid
<< " ask=" << event.quote.ask << std::endl;
}
});
// Fluent contract-first subscriptions (primary surface).auto stock = tdx::Contract::stock("AAPL");
auto option = tdx::Contract::option("SPY", "20260620", "550", "C");
unified.subscribe(stock.quote());
unified.subscribe(option.trade());
unified.subscribe(tdx::SecType::option().full_trades());
// Bulk install:
unified.subscribe_many({stock.quote(), option.quote()});
// ... let the callback run ...
fpss.shutdown();
}
All prices in streaming events are double (f64) -- decoded during parsing. Access them directly: event.quote.bid, event.trade.price, etc. No price_type decoding needed.
Push-callback (fpss.set_callback(fn) / unified.set_callback(fn)
above) is the recommended default for low-latency single-event
reaction. Pull-iter is the sibling delivery mode for high-throughput
batch processing: the user thread drains a per-client bounded queue
populated by the Disruptor consumer.
#include"thetadx.hpp"
#include<chrono>
#include<iostream>intmain() {
auto unified = tdx::UnifiedClient::connect(
tdx::Credentials::from_file("creds.txt"),
tdx::Config::production());
auto iter = unified.start_streaming_iter();
unified.subscribe(tdx::SecType::option().full_trades());
// Range-for adapter — 1-second per-pop timeout by default.for (constauto& event : iter) {
if (event.kind == TDX_FPSS_TRADE) {
std::cout << "trade " << event.trade.price
<< " x " << event.trade.size << std::endl;
}
}
// Or explicit poll with caller-chosen deadline:while (auto event = iter.next(std::chrono::milliseconds(500))) {
// ... process *event ...
}
if (iter.ended()) {
// terminal end-of-stream — the streaming session shut down// and the queue is drained.
}
}
tdx::EventIterator is move-only; the destructor frees the
underlying C handle. Mutually exclusive with the push-callback
methods on the same client; switch by stopping streaming and
starting again.
Truncated / unrecognised wire frames are filtered before the user
callback fires and accounted on the thetadatadx.fpss.decode_failures
metric counter on the Rust side; they never surface through the C ABI
event stream.
FpssClient is non-copyable but movable. The destructor calls shutdown() automatically.
Flat Files
Whole-universe daily snapshots over the legacy MDDS port. Decoded
schema is determined at runtime by (SecType, ReqType), so the C++
wrapper exposes Arrow IPC stream bytes — pair with arrow-cpp on the
consumer side to materialise an arrow::Table.
#include"thetadx.hpp"auto creds = tdx::Credentials::from_file("creds.txt");
auto config = tdx::Config::production();
auto unified = tdx::UnifiedClient::connect(creds, config);
auto rows = unified.flat_files().option_quote("20260428");
auto ipc = rows.to_arrow_ipc(); // std::vector<uint8_t>// Generic dispatcherauto oi = unified.flat_files().request("OPTION", "OPEN_INTEREST", "20260428");
// Raw vendor CSV / JSONL straight to disk
unified.flat_files().to_path("OPTION", "QUOTE", "20260428",
"/tmp/option-quote", "csv");
Available flat_files().* methods: option_quote, option_trade,
option_trade_quote, option_ohlc, option_open_interest,
option_eod, stock_quote, stock_trade, stock_trade_quote,
stock_eod, plus request(sec_type, req_type, date) and
to_path(...). The tdx::UnifiedClient wraps TdxUnified; the
existing tdx::Client (wrapping TdxClient) remains the
historical-only entry point.
Architecture
C++ code
| (RAII wrappers)
v
thetadatadx.h (C FFI)
|
v
libthetadatadx_ffi.so / .a
| (Rust FFI crate)
v
thetadatadx Rust crate
| (tonic gRPC / tokio TCP)
v
ThetaData servers