High-performance market-data SDKs for ThetaData, in Python, TypeScript, C++, and Rust — one Rust engine under all four. Pull US stock, option, index, and rate data three ways: point-in-time history, real-time streaming, and whole-universe flat files, all from a single authenticated client. Connects straight to ThetaData — no Java terminal, no JVM.
Important
A valid ThetaData subscription is required. The SDK authenticates against ThetaData's Nexus API using your account credentials.
- Complete coverage — stocks, options, indices, and rates across 61 typed endpoints.
- Three access modes, one client — point-in-time history, real-time streaming, and bulk flat-file downloads.
- DataFrames built in — every result chains straight to Polars, pandas, or Arrow over a zero-copy boundary.
- Greeks without a round-trip — 23 Black-Scholes Greeks and an implied-volatility solver, computed locally.
- The same surface in every language — identical methods and identical typed errors, Python through Rust.
- No terminal to run — a direct connection to ThetaData; nothing to install and babysit locally.
pip install thetadatadx # Python
npm install thetadatadx # TypeScript / Node.js
cargo add thetadatadx # RustC++ ships as a single header plus a prebuilt library — see the C++ guide.
Tip
Credentials can come from a creds.txt file (email on line 1, password on
line 2), an inline Credentials, or the THETADATA_EMAIL /
THETADATA_PASSWORD environment variables.
from thetadatadx import ThetaDataDxClient, Credentials, Config
tdx = ThetaDataDxClient(Credentials.from_file("creds.txt"), Config.production())
# First-order Greeks for every strike on SPY's 2026-06-19 expiry, as of 2024-03-15
greeks = tdx.option_history_greeks_first_order("SPY", "20260619", "20240315")
df = greeks.to_polars()
print(df.select(["strike", "right", "delta", "gamma", "theta", "vega"]).head())Stream live quotes and trades through the same client. The callback matches on typed event classes:
import time
from thetadatadx import Contract, Quote, Trade
def format_contract(contract):
parts = [contract.symbol]
if contract.expiration is not None:
parts.append(str(contract.expiration))
if contract.strike is not None:
parts.append(f"{contract.strike:g}")
if contract.right is not None:
parts.append(contract.right)
return " ".join(parts)
def on_event(event):
match event:
case Trade(price=px, size=sz, exchange=ex, ms_of_day=ms, sequence=seq, condition=cond, contract=c):
print(
f"{format_contract(c)} trade price={px:.2f} size={sz} "
f"exchange={ex} ms_of_day={ms} sequence={seq} condition={cond}"
)
case Quote(bid=b, ask=a, bid_size=bs, ask_size=asz, bid_exchange=bx, ask_exchange=ax, ms_of_day=ms, contract=c):
print(
f"{format_contract(c)} quote bid={b:.2f} ask={a:.2f} "
f"bid_size={bs} ask_size={asz} bid_exchange={bx} "
f"ask_exchange={ax} ms_of_day={ms}"
)
spy_call = Contract.option("SPY", expiration="20260619", strike="550", right="C")
with tdx.streaming(on_event) as session:
session.subscribe_many([spy_call.quote(), spy_call.trade(), spy_call.market_value()])
time.sleep(60) # park the main thread while events flow into on_eventimport { Contract, ThetaDataDxClient } from 'thetadatadx';
const client = ThetaDataDxClient.connectFromFile('creds.txt');
const formatContract = (contract: {
symbol: string;
expiration?: number;
strike?: number;
right?: string;
}) => [contract.symbol, contract.expiration, contract.strike, contract.right]
.filter((value) => value != null)
.join(' ');
client.startStreaming((event) => {
if (event.kind === 'trade' && event.trade) {
const { contract, price, size, exchange, msOfDay, sequence, condition } = event.trade;
console.log(
`${formatContract(contract)} trade price=${price} size=${size} ` +
`exchange=${exchange} ms_of_day=${msOfDay} sequence=${sequence} condition=${condition}`,
);
} else if (event.kind === 'quote' && event.quote) {
const { contract, bid, ask, bidSize, askSize, bidExchange, askExchange, msOfDay } = event.quote;
console.log(
`${formatContract(contract)} quote bid=${bid} ask=${ask} ` +
`bid_size=${bidSize} ask_size=${askSize} bid_exchange=${bidExchange} ` +
`ask_exchange=${askExchange} ms_of_day=${msOfDay}`,
);
}
});
const leg = { expiration: '20260619', strike: '550', right: 'C' };
client.subscribeMany([
Contract.option('SPY', leg).quote(),
Contract.option('SPY', leg).trade(),
]);#include <thetadx.hpp>
#include <cstdio>
int main() {
auto client = tdx::Client::connect(
tdx::Credentials::from_file("creds.txt"),
tdx::Config::production());
auto greeks = client.option_history_greeks_first_order("SPY", "20260619", "20240315");
for (const auto& t : greeks) {
std::printf("K=%.2f %c delta=%+.4f gamma=%+.4f\n",
t.strike, t.right, t.delta, t.gamma);
}
}[dependencies]
thetadatadx = "12"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }use thetadatadx::{ThetaDataDxClient, Credentials, DirectConfig};
#[tokio::main]
async fn main() -> Result<(), thetadatadx::Error> {
let creds = Credentials::from_file("creds.txt")?;
let tdx = ThetaDataDxClient::connect(&creds, DirectConfig::production()).await?;
let greeks = tdx
.option_history_greeks_eod("SPY", "20260619", "20240101", "20240331")
.await?;
for t in greeks.iter().take(5) {
println!("{} K={:.2} {} delta={:+.4}", t.date, t.strike, t.right, t.delta);
}
Ok(())
}Every historical result is a typed list that converts directly to a dataframe — no row-by-row iteration:
greeks.to_polars() # polars.DataFrame
greeks.to_pandas() # pandas.DataFrame (pip install thetadatadx[pandas])
greeks.to_arrow() # pyarrow.Table (zero-copy)The same .to_polars() / .to_pandas() / .to_arrow() terminals are available
on flat-file results. For multi-day backfills, stream the response in chunks
instead of buffering it — see Bulk backfill.
One connection, one authentication. Historical queries work immediately; the
streaming transport connects on the first subscription. Subscribe specific
contracts with the fluent Contract API, or take a whole-market feed — every
option trade across the universe, no per-contract setup:
with tdx.streaming(on_event) as session:
session.subscribe(SecType.OPTION.full_trades())
time.sleep(60) # the callback runs on the streaming thread — keep it fastTip
On an involuntary disconnect the client recovers on its own — exponential
backoff with jitter, automatic host failover, then a paced re-subscribe of
every active contract. Read liveness directly off the stream with
connection_status() and the last-event timestamp; no separate health poll
needed.
61 typed endpoints across stocks, options, indices, the market calendar, and interest rates, plus real-time streaming and a local Greeks calculator.
| Category | Endpoints | Examples |
|---|---|---|
| Stock | 14 | EOD, OHLC, trades, quotes, snapshots, at-time |
| Option | 34 | Every stock surface plus five Greeks tiers, open interest, contract lists |
| Index | 9 | EOD, OHLC, price, snapshots |
| Calendar | 3 | Market open/close, holidays, early closes |
| Interest rate | 1 | EOD rate history |
The full per-language method list lives in the API Reference.
Every binding raises the same typed hierarchy, so the same cases are catchable
in any language — AuthenticationError, RateLimitError, NotFoundError,
DeadlineExceededError, InvalidParameterError, and the rest, all under a
common ThetaDataError base.
| Path | Package | Purpose |
|---|---|---|
crates/thetadatadx |
thetadatadx (crates.io) |
The Rust SDK — tick types, Greeks, price math, and the network client in one crate |
sdks/python |
thetadatadx (PyPI) |
Python package with DataFrame adapters |
sdks/typescript |
thetadatadx (npm) |
TypeScript / Node.js package, prebuilt binaries |
sdks/cpp |
header + prebuilt library | C++ wrapper over the C ABI |
ffi/ |
release artifacts | C ABI for embedders |
tools/cli |
tdx |
Command-line client |
tools/server |
thetadatadx-server |
Local HTTP / WebSocket server |
tools/mcp |
thetadatadx-mcp |
MCP server exposing every historical endpoint to AI clients |
docs-site |
— | Documentation site (GitHub Pages) |
- Documentation site — getting started, API reference, streaming, server, and MCP
- Changelog
See CONTRIBUTING.md for development setup and the pull-request process. Community discussion happens on the ThetaData Discord.
Licensed under the Apache License, Version 2.0. See LICENSE.