Important
The SDK now ships under per-language package names, starting fresh at 0.1.0: thetadatadx-rs (crates.io), thetadatadx-py (PyPI), thetadatadx-ts (npm), and the in-repo thetadatadx-cpp. The API and import surface are unchanged — only the package names and the version differ. Install the packages below.
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, with nothing to install and run locally.
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 65 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 on demand: first- through third-order Greeks and implied volatility, served straight from the option endpoints.
- 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.
Three client shapes over the same engine and the same auth. Pick per workload — they are not layers, they are separate entry points:
| Client | Connects to | Use it for |
|---|---|---|
MarketDataClient |
market-data (HTTP) only | history, snapshots, flat files. Never opens the real-time feed; a streaming call raises an error by design. |
StreamingClient |
the real-time feed only | live trades, quotes, OHLCVC. Never touches market-data. |
Client (unified) |
market-data on construct; the feed is lazy | both from one object. The feed is opened only when you call start_streaming, so a market-data-only workflow never touches it. |
from thetadatadx import MarketDataClient, StreamingClient, Client, Credentials, Config
md = MarketDataClient(Credentials.from_file("creds.txt"), Config.production()) # market-data only
stream = StreamingClient(Credentials.from_file("creds.txt"), Config.production()) # real-time feed only
client = Client(api_key="td1_...") # both; feed stays closed until you streamTip
MarketDataClient and StreamingClient use independent channels and independent sessions, so you can run them in separate processes or containers with no shared state and nothing stealing the other's session. A market-data worker next to a streaming worker (or a Config.stage() market-data instance next to a Config.dev() streaming instance) is a first-class pattern, not a workaround.
Important
The real-time feed is a single live session per account. Run one StreamingClient (or one unified Client that streams) per account and fan out to your consumers in-process. Opening a second streaming session on the same account takes over the connection and drops the first. Market-data is unaffected: it is per-request, so it runs alongside streaming and across as many MarketDataClient instances as you like.
Note
Market-data concurrency is account-wide, set by your highest subscription tier; multiple MarketDataClient instances share that one budget and extra requests queue and run in order. The real-time feed requires a paid subscription — FREE accounts get delayed market-data but no streaming.
Important
Install the per-language packages at 0.1.0 below — thetadatadx-rs (crates.io), thetadatadx-py (PyPI), thetadatadx-ts (npm).
pip install thetadatadx-py # Python
npm install thetadatadx-ts # TypeScript / Node.js
cargo add thetadatadx-rs # RustPoint an AI client (Claude Desktop, Cursor, and others) at the MCP server, no install and no Rust toolchain:
{ "command": "npx", "args": ["-y", "thetadatadx-mcp-server"], "env": { "THETADATA_API_KEY": "your_key" } }C++ ships as a header plus a small implementation file over a prebuilt library (a CMake target wires it up). See the C++ guide.
Tip
Pass your API key directly to the client and you are one line from a live connection. Generate a key from your ThetaData user portal, then hand it to the client: Client(api_key="td1_...") in Python, Client.connectWith({ apiKey: "td1_..." }) in TypeScript, Client::builder().api_key("td1_...").connect() in Rust and C++. The key can come from THETADATA_API_KEY (the env source) or a .env file instead of an inline literal. Email and password is also supported: pass email and password inline, load a creds.txt file (email on line 1, password on line 2), or read the THETADATA_EMAIL / THETADATA_PASSWORD environment variables. For full control over hosts and timeouts, build a typed Credentials + Config (see each SDK's "full control" example).
from thetadatadx import Client
# Pass your API key directly. Use market_data_type="STAGE" to target staging.
client = Client(api_key="td1_...")
# First-order Greeks for every strike on SPY's 2026-06-19 expiry, as of 2024-03-15
greeks = client.market_data.option_history_greeks_first_order("SPY", "20260619", date="20240315")
df = greeks.to_polars()
print(df.select(["strike", "right", "delta", "gamma", "theta", "vega"]).head())Other ways to construct the client:
from thetadatadx import Client, Credentials, Config
# API key from the THETADATA_API_KEY environment variable, or from a .env file
client = Client.from_env()
client = Client.from_dotenv(".env")
# Email and password, inline
client = Client(email="you@example.com", password="your_password")
# Full control: build a typed Credentials + Config (custom hosts, timeouts)
client = Client(Credentials.from_file("creds.txt"), Config.production())Stream live quotes and trades through the same client. The callback matches on typed event classes:
import time
from thetadatadx import Contract, MarketValue, Quote, Trade
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"{c.symbol} {c.expiration} {c.strike:g} {c.right} 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"{c.symbol} {c.expiration} {c.strike:g} {c.right} 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}"
)
case MarketValue(market_bid=mb, market_ask=ma, market_price=mp, ms_of_day=ms, contract=c):
print(
f"{c.symbol} {c.expiration} {c.strike:g} {c.right} market_value "
f"bid={mb:.2f} ask={ma:.2f} price={mp:.2f} ms_of_day={ms}"
)
spy_call = Contract.option("SPY", expiration="20260619", strike="550", right="C")
with client.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, Client } from 'thetadatadx-ts';
async function main() {
// Pass your API key directly. Add marketDataType: "STAGE" to target staging.
const client = await Client.connectWith({ apiKey: 'td1_...' });
await client.stream.startStreaming((event) => {
if (event.kind === 'trade' && event.trade) {
const { contract, price, size, exchange, msOfDay, sequence, condition } = event.trade;
console.log(
`${contract.symbol} ${contract.expiration} ${contract.strike} ${contract.right} 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(
`${contract.symbol} ${contract.expiration} ${contract.strike} ${contract.right} 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.stream.subscribeMany([
Contract.option('SPY', leg).quote(),
Contract.option('SPY', leg).trade(),
]);
}
await main();Other ways to construct the client:
import { Client } from 'thetadatadx-ts';
// API key from the THETADATA_API_KEY environment variable, or from a .env file
const fromEnv = await Client.connectWith({ apiKeyFromEnv: true });
const fromDotenv = await Client.connectWith({ apiKeyFromDotenv: '.env' });
// Email and password, inline
const withLogin = await Client.connectWith({ email: 'you@example.com', password: 'your_password' });
// Full control: load a typed credentials file (custom hosts, timeouts via Config)
const fullControl = await Client.connectFromFile('creds.txt');#include <thetadatadx.hpp>
#include <cstdio>
int main() {
// Pass your API key directly. Add .stage() before .connect() for staging.
auto client = thetadatadx::Client::builder()
.api_key("td1_...")
.connect();
auto greeks = client.market_data().option_history_greeks_first_order("SPY", "20260619", thetadatadx::EndpointRequestOptions{}.with_date("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-rs = "0.2.0"use thetadatadx::Client;
async fn run() -> Result<(), thetadatadx::Error> {
// Pass your API key directly. Add .stage() before .connect() for staging.
let client = Client::builder().api_key("td1_...").connect().await?;
let greeks = client
.market_data()
.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(())
}Call the async function from your application's runtime.
Every market-data 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-py[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 Request sizing.
One connection, one authentication. Market-data 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. The full-trade feed
sends a quote and an OHLC bar before each trade, so add an Ohlcvc case to the
callback to handle the bars:
from thetadatadx import Ohlcvc
def on_full_trade(event):
match event:
case Ohlcvc(open=o, high=h, low=lo, close=cl, volume=v, contract=c):
print(
f"{c.symbol} {c.expiration} {c.strike:g} {c.right} bar "
f"o={o:.2f} h={h:.2f} l={lo:.2f} c={cl:.2f} volume={v}"
)
case _:
on_event(event) # reuse the quote/trade handling above
with client.streaming(on_full_trade) as session:
session.subscribe(SecType.OPTION.full_trades())
time.sleep(60) # the callback runs on the streaming thread; keep it fastTip
The callback above is one of two delivery modes. It pushes one typed event at
a time for the lowest latency, ideal when you react to each trade or quote.
For bulk and analytics, client.stream.batches(...) delivers the same
subscriptions as Apache Arrow RecordBatch values under a fixed schema, ready
to pull into pandas, Polars, or DuckDB. Open the reader first, since it starts
the session, then subscribe. Every binding has both; the
streaming guide covers them.
Tip
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.
65 typed endpoints across stocks, options, indices, the market calendar, and interest rates, plus real-time streaming.
| Category | Endpoints | Examples |
|---|---|---|
| Stock | 16 | EOD, OHLC, trades, quotes, snapshots, at-time |
| Option | 36 | 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 |
|---|---|---|
thetadatadx-rs |
thetadatadx-rs (crates.io) |
The Rust SDK: tick types, decoders, and the network client in one crate |
thetadatadx-py |
thetadatadx-py (PyPI) |
Python package with DataFrame adapters |
thetadatadx-ts |
thetadatadx-ts (npm) |
TypeScript / Node.js package, prebuilt binaries |
thetadatadx-cpp |
header + prebuilt library | C++ wrapper over the C ABI |
thetadatadx-ffi |
release artifacts | C ABI for embedders |
tools/server |
thetadatadx-server |
Local HTTP / WebSocket server |
tools/mcp |
thetadatadx-mcp-server (npm) |
MCP server exposing every market-data endpoint to AI clients |
docs-site |
— | Documentation site (GitHub Pages) |
- Documentation site: getting started, API reference, streaming, server, and MCP
- Changelog
See ROADMAP.md for where the project is headed. Up next: a native Go SDK and a self-updating server. The MCP server now runs straight from npm — npx -y thetadatadx-mcp-server.
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.