Skip to content

userFRM/ThetaDataDx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

978 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

ThetaDataDx

ThetaDataDx

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.

Rust CI Python SDK TypeScript SDK Deploy Docs

Crates.io PyPI npm docs.rs

License Rust Python Node C++ Discord

Important

A valid ThetaData subscription is required. The SDK authenticates against ThetaData's Nexus API using your account credentials.

Features

  • 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.

Clients

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 stream

Tip

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.

Install

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              # Rust

Point 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.

Quick start

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).

Python

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_event

TypeScript

import { 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');

C++

#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);
    }
}

Rust

[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.

DataFrames

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.

Streaming

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 fast

Tip

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.

Endpoint coverage

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.

Errors

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.

Repository layout

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

Roadmap

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.

Contributing

See CONTRIBUTING.md for development setup and the pull-request process. Community discussion happens on the ThetaData Discord.

License

Licensed under the Apache License, Version 2.0. See LICENSE.

About

Market data SDKs for ThetaData — Rust, Python, TypeScript, and C++. Historical, real-time streaming, and bulk flat-file access through one authenticated client. No JVM, no local terminal.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

21 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors