Quick Start
One page covering all four SDKs (Rust, Python, TypeScript / Node.js, C++). Each step shows the same workflow tabbed across languages. Go consumers can build a thin cgo wrapper against the unchanged C ABI in ffi/.
Install
# Cargo.toml
# [dependencies]
# thetadatadx = "9"
# tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
cargo add thetadatadx tokio --features tokio/rt-multi-thread,tokio/macrospip install thetadatadx
# With DataFrame support:
pip install thetadatadx[pandas] # pandas
pip install thetadatadx[polars] # polars
pip install thetadatadx[arrow] # pyarrow only
pip install thetadatadx[all] # all threenpm install thetadatadx# Prerequisites: C++17 compiler, CMake 3.16+, Rust toolchain
git clone https://github.com/userFRM/ThetaDataDx.git
cd ThetaDataDx
cargo build --release -p thetadatadx-ffi
cd sdks/cpp
mkdir build && cd build
cmake ..
makePython wheels are pre-built (abi3, Python 3.9+); no Rust toolchain needed on supported platforms. C++ links against the Rust FFI library built once with cargo build.
Authenticate
use thetadatadx::Credentials;
// From file (line 1 = email, line 2 = password)
let creds = Credentials::from_file("creds.txt")?;
// Or from env vars
let creds = Credentials::new(
std::env::var("THETA_EMAIL")?,
std::env::var("THETA_PASS")?,
);from thetadatadx import Credentials
# From file
creds = Credentials.from_file("creds.txt")
# Or from env vars
import os
creds = Credentials(os.environ["THETA_EMAIL"], os.environ["THETA_PASS"])import { ThetaDataDxClient } from 'thetadatadx';
// Credentials are passed directly to the connect helpers below.#include "thetadx.hpp"
auto creds = tdx::Credentials::from_file("creds.txt");
// Or from env vars
auto envCreds = tdx::Credentials(
std::getenv("THETA_EMAIL"),
std::getenv("THETA_PASS")
);First historical call
use thetadatadx::{ThetaDataDxClient, DirectConfig};
#[tokio::main]
async fn main() -> Result<(), thetadatadx::Error> {
let creds = thetadatadx::Credentials::from_file("creds.txt")?;
let tdx = ThetaDataDxClient::connect(&creds, DirectConfig::production()).await?;
let eod = tdx.stock_history_eod("AAPL", "20240101", "20240301").await?;
for tick in &eod {
println!("{}: O={:.2} H={:.2} L={:.2} C={:.2} V={}",
tick.date, tick.open, tick.high, tick.low, tick.close, tick.volume);
}
Ok(())
}from thetadatadx import Credentials, Config, ThetaDataDxClient
creds = Credentials.from_file("creds.txt")
tdx = ThetaDataDxClient(creds, Config.production())
eod = tdx.stock_history_eod("AAPL", "20240101", "20240301")
for tick in eod:
print(f"{tick.date}: O={tick.open:.2f} H={tick.high:.2f} "
f"L={tick.low:.2f} C={tick.close:.2f} V={tick.volume}")
# Chain directly to a DataFrame:
df = tdx.stock_history_eod("AAPL", "20240101", "20240301").to_polars()
pdf = tdx.stock_history_eod("AAPL", "20240101", "20240301").to_pandas()
tbl = tdx.stock_history_eod("AAPL", "20240101", "20240301").to_arrow()import { ThetaDataDxClient } from 'thetadatadx';
const tdx = await ThetaDataDxClient.connectFromFile('creds.txt');
const eod = tdx.stockHistoryEOD('AAPL', '20240101', '20240301');
for (const tick of eod) {
console.log(`${tick.date}: O=${tick.open} H=${tick.high} L=${tick.low} C=${tick.close} V=${tick.volume}`);
}#include "thetadx.hpp"
#include <iomanip>
#include <iostream>
int main() {
auto creds = tdx::Credentials::from_file("creds.txt");
auto client = tdx::UnifiedClient::connect(creds, tdx::Config::production());
auto eod = client.stock_history_eod("AAPL", "20240101", "20240301");
for (const auto& tick : eod) {
std::cout << tick.date
<< std::fixed << std::setprecision(2)
<< ": O=" << tick.open << " H=" << tick.high
<< " L=" << tick.low << " C=" << tick.close
<< " V=" << tick.volume << std::endl;
}
}Every historical endpoint returns typed tick records (EodTick, OhlcTick, TradeTick, ...). On Python the returned list wrapper chains directly to to_polars(), to_pandas(), to_arrow(), or to_list(). See DataFrames for the zero-copy Arrow scope.
First streaming call
use thetadatadx::{ThetaDataDxClient, DirectConfig};
use thetadatadx::fpss::{FpssData, FpssEvent};
use thetadatadx::fpss::protocol::Contract;
#[tokio::main]
async fn main() -> Result<(), thetadatadx::Error> {
let creds = thetadatadx::Credentials::from_file("creds.txt")?;
let tdx = ThetaDataDxClient::connect(&creds, DirectConfig::production()).await?;
tdx.start_streaming(|event: &FpssEvent| match event {
FpssEvent::Data(FpssData::Quote { contract, bid, ask, .. }) => {
println!("Quote: {} {bid:.2}/{ask:.2}", contract.symbol);
}
FpssEvent::Data(FpssData::Trade { contract, price, size, .. }) => {
println!("Trade: {} {price:.2} x {size}", contract.symbol);
}
_ => {}
})?;
// Fluent contract-first subscribe — `Contract::stock("AAPL").quote()`
// produces a `Subscription` the unified `subscribe()` accepts.
tdx.subscribe(Contract::stock("AAPL").quote())?;
tdx.subscribe(Contract::stock("MSFT").trade())?;
tokio::signal::ctrl_c().await.ok();
tdx.stop_streaming();
Ok(())
}from thetadatadx import Credentials, Config, ThetaDataDxClient, Contract
creds = Credentials.from_file("creds.txt")
client = ThetaDataDxClient(creds, Config.production())
client.subscribe(Contract.stock("AAPL").quote())
client.subscribe(Contract.stock("MSFT").trade())
# Pull-iter mode: context-managed typed iterator over the SPSC
# queue. The iterator raises StopIteration once `stop_streaming()`
# fires AND the queue is fully drained; the `with` block pairs
# `stop_streaming()` + `await_drain()` automatically on exit.
with client.streaming_iter() as it:
for event in it:
if event.kind == "quote":
print(f"Quote: {event.contract.symbol} "
f"{event.bid:.2f}/{event.ask:.2f}")
elif event.kind == "trade":
print(f"Trade: {event.contract.symbol} "
f"{event.price:.2f} x {event.size}")
elif event.kind == "disconnected":
breakimport { ThetaDataDxClient, Contract } from 'thetadatadx';
const client = await ThetaDataDxClient.connectFromFile('creds.txt');
client.subscribe(Contract.stock('AAPL').quote());
client.subscribe(Contract.stock('MSFT').trade());
// Pull-iter mode: async iterable over the SPSC queue. The
// iterator resolves `done: true` once `client.stopStreaming()`
// fires AND the queue is fully drained.
const iter = client.startStreamingIter();
try {
for await (const event of iter) {
if (event.kind === 'quote') {
console.log(`Quote: ${event.contract.symbol} ${event.bid.toFixed(2)}/${event.ask.toFixed(2)}`);
} else if (event.kind === 'trade') {
console.log(`Trade: ${event.contract.symbol} ${event.price.toFixed(2)} x ${event.size}`);
} else if (event.kind === 'simple' && event.eventType === 'disconnected') {
break;
}
}
} finally {
client.stopStreaming();
}#include "thetadx.hpp"
#include <iostream>
int main() {
auto creds = tdx::Credentials::from_file("creds.txt");
auto config = tdx::Config::production();
auto client = tdx::UnifiedClient::connect(creds, config);
// Fluent contract-first subscribe — same shape as the Rust /
// Python / TypeScript bindings.
client.subscribe(tdx::Contract::stock("AAPL").quote());
client.subscribe(tdx::Contract::stock("MSFT").trade());
auto iter = client.start_streaming_iter();
while (!iter.ended()) {
auto event = iter.next(std::chrono::milliseconds(1000));
if (!event) continue;
switch (event->kind) {
case TDX_FPSS_QUOTE: {
const auto& q = event->quote;
std::cout << "Quote: " << q.contract.symbol
<< " " << q.bid << "/" << q.ask << std::endl;
break;
}
case TDX_FPSS_TRADE: {
const auto& t = event->trade;
std::cout << "Trade: " << t.contract.symbol
<< " " << t.price << " x " << t.size << std::endl;
break;
}
default: break;
}
}
fpss.shutdown();
}Streaming is real-time FPSS — no polling the historical REST endpoints. See Streaming (FPSS) for the callback / polling model, reconnect policy, and latency tracking.
Where to next
- Authentication — credentials file, environment variables, token lifecycle
- First query — deeper dive on a single historical call
- DataFrames — Arrow / Polars / Pandas output with the zero-copy scope
- Streaming (FPSS) — SPKI pinning, callback / polling models, lock-free ring, reconnect policy
- Error handling —
ThetaDataErrorhierarchy, retry policy, session refresh - Historical endpoints — complete generated historical surface, with per-language examples on each