thetadatadx-ts
v0.1.1
Published
Native ThetaData market-data SDK for Node.js
Downloads
295
Readme
thetadatadx (TypeScript / Node.js)
The Node.js SDK for ThetaData market data. 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; nothing to install and run locally, no local proxy.
[!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.
- Fully typed — every endpoint, tick, and streaming event ships with hand-checked
.d.tsdeclarations. - Greeks on demand — five tiers of Black-Scholes Greeks and implied volatility, served straight from the option endpoints.
- Arrow on the way out — flat-file results emit Arrow IPC for a zero-copy handoff to
apache-arrow. - No terminal to run — prebuilt native binaries; nothing to compile, nothing to babysit locally.
Install
npm install thetadatadx-tsPrebuilt binaries are downloaded automatically for Linux x64 (glibc), macOS arm64 (Apple Silicon), and Windows x64 (MSVC). No Rust toolchain is required.
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 call
Client.connectWith({ apiKey: "td1_..." }). The key can also come from the environment ({ apiKeyFromEnv: true }, readingTHETADATA_API_KEY) or a.envfile ({ apiKeyFromDotenv: ".env" }). Email and password is also supported:{ email, password }inline, or acreds.txtfile (email on line 1, password on line 2) viaconnectFromFile. Target staging withmarketDataType: "STAGE". For full control over hosts and timeouts, build a typedCredentials+Configand callClient.connect(creds, config).
import { Client } from 'thetadatadx-ts';
// Pass your API key directly. Add marketDataType: "STAGE" to target staging.
const client = await Client.connectWith({ apiKey: 'td1_...' });
// First-order Greeks for every strike on SPY's 2026-06-19 expiry, as of 2024-03-15
const greeks = await client.marketData.optionHistoryGreeksFirstOrder('SPY', '20260619', { date: '20240315' });
for (const t of greeks.slice(0, 5)) {
console.log(`K=${t.strike} ${t.right} delta=${t.delta.toFixed(4)} theta=${t.theta.toFixed(4)}`);
}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: '[email protected]', password: 'your_password' });
// Full control: load a typed credentials file (custom hosts, timeouts via Config)
const fullControl = await Client.connectFromFile('creds.txt');Every market-data method resolves a Promise of typed tick objects off the runtime's execution thread, so a fetch never holds the event loop:
const eod = await client.marketData.stockHistoryEOD('AAPL', '20240101', '20240301');
console.log(eod.length, eod[0].close);
const bars = await client.marketData.stockHistoryOHLC('AAPL', { date: '20240315', interval: '1m' });
const exps = await client.marketData.optionListExpirations('SPY');
// Optional parameters — including a per-call timeout — ride in the trailing options object
const snap = await client.marketData.stockSnapshotQuote(['AAPL', 'MSFT'], { timeoutMs: 5000 });Streaming
Real-time quotes and trades flow through the same client. Register a callback with startStreaming; events are discriminated on event.kind and the typed payload narrows automatically:
import { Contract, Client } from 'thetadatadx-ts';
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: '20260620', strike: '550', right: 'C' };
client.stream.subscribeMany([
Contract.option('SPY', leg).quote(),
Contract.option('SPY', leg).trade(),
]);Build subscriptions with the fluent Contract API and pass them — one at a time or in bulk — to subscribe / subscribeMany. Every subscription is the same typed value, so quotes, trades, and open interest across contracts mix freely in one array:
import { Contract, SecType } from 'thetadatadx-ts';
const stock = Contract.stock('AAPL');
const option = Contract.option('SPY', { expiration: '20260620', strike: '550', right: 'C' });
client.stream.subscribe(stock.quote());
client.stream.subscribeMany([option.quote(), option.trade(), option.openInterest()]);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 branch to the callback to handle the bars:
import { SecType } from 'thetadatadx-ts';
await client.stream.startStreaming((event) => {
if (event.kind === 'ohlcvc' && event.ohlcvc) {
const { contract, open, high, low, close, volume } = event.ohlcvc;
console.log(
`${contract.symbol} ${contract.expiration} ${contract.strike} ${contract.right} bar ` +
`o=${open} h=${high} l=${low} c=${close} volume=${volume}`,
);
}
// ...plus the quote/trade branches from above.
});
client.stream.subscribe(SecType.option().fullTrades()); // the callback runs per event — keep it fastWhen you are done, stop the stream and drain it. By the time awaitDrain resolves, the callback has stopped firing, so any state it closed over can be released safely:
client.stream.stopStreaming();
const drained = await client.stream.awaitDrain(5000);[!TIP] On an involuntary disconnect the client recovers on its own — exponential backoff with jitter, host failover, then a paced re-subscribe of every active contract.
Prefer columns? client.stream.batches(...) is a sibling to the callback: the same subscriptions, delivered as apache-arrow RecordBatch values under a fixed schema, consumed with for await. It closes (unsubscribe + tear down) on close(), or on Symbol.asyncDispose via await using where your runtime supports explicit resource management:
import { Contract } from 'thetadatadx-ts';
// `batches(...)` starts the streaming session, so open it first, then subscribe.
const batches = await client.stream.batches({ batchSize: 8192 });
try {
client.stream.subscribe(Contract.stock('AAPL').trade());
for await (const batch of batches) {
console.log(batch.numRows);
}
} finally {
batches.close();
}Decoding the batches needs apache-arrow installed alongside the SDK.
Types
Every tick type and streaming event is exported. Import the ones you need:
import type { OhlcTick, GreeksAllTick, Quote, Trade, StreamEvent } from 'thetadatadx-ts';The streaming callback receives a discriminated StreamEvent, narrowed on event.kind. Market-data events (trade, quote, ohlcvc, open_interest) carry their payload under a matching field; one typed payload also exists per lifecycle event (connected, loginSuccess, disconnected, reconnecting, …):
await client.stream.startStreaming((event: StreamEvent) => {
switch (event.kind) {
case 'trade': /* event.trade is Trade */ break;
case 'quote': /* event.quote is Quote */ break;
case 'ohlcvc': /* event.ohlcvc is Ohlcvc */ break;
case 'open_interest': /* event.openInterest is OpenInterest */ break;
}
});kind is a string-literal union (not a const enum), so the type information stays self-contained under "isolatedModules": true and works across Vite, esbuild, ts-jest, and Next.js.
[!NOTE] Wherever a 64-bit integer crosses the boundary it surfaces as
bigint, notnumber—volumeandcounton OHLC / EOD ticks,droppedEventCount(), andreceivedAtNson every event. Use42nliterals for comparisons, or widen withNumber(x)at the point of display.
Flat files
Whole-universe daily snapshots for one (security type, request type, date) at a time. The decoded schema follows the request type, so the binding emits Arrow IPC bytes — pair with apache-arrow's tableFromIPC to materialise a typed Table:
import { Client } from 'thetadatadx-ts';
import { tableFromIPC } from 'apache-arrow'; // peer dependency
const client = await Client.connectFromFile('creds.txt');
const rows = await client.flatFiles.optionTradeQuote('20260428');
console.log(rows.len());
const table = tableFromIPC(rows.toArrowIpc());
// Or skip Arrow entirely: const json = JSON.parse(rows.toJson());
// Generic dispatcher when security type / request type come from config
const oi = await client.flatFiles.request('OPTION', 'OPEN_INTEREST', '20260428');
// Or write the raw vendor file straight to disk
await client.flatFileToPath('OPTION', 'TRADE_QUOTE', '20260428', '/tmp/option-trade-quote', 'csv');The flat-file distribution serves a fixed set of datasets: option trade_quote / open_interest / eod, stock trade_quote / eod. Available flatFiles.* methods: optionTradeQuote, optionOpenInterest, optionEod, stockTradeQuote, stockEod, plus request(secType, reqType, date). The generic request(...) and flatFileToPath(...) paths reject an unserved (security, request) pair with a typed invalid-parameter error.
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 |
Every endpoint is a camelCase method on client.marketData. The full method list with JSDoc lives in index.d.ts and the API reference.
Errors
Every call rejects with a typed error under a common ThetaDataError base — authentication, rate limit, not found, deadline exceeded, invalid parameter, and the rest — so the same cases are catchable here exactly as they are in every other binding.
Building from source
Only needed when a platform has no prebuilt binary or you are developing locally:
cd thetadatadx-ts
npm install
npm run build # requires Rust stable + protocDocumentation
- Documentation site — getting started, API reference, streaming, flat files
- Repository, issues, contributing
- Community discussion on the ThetaData Discord
License
Licensed under the Apache License, Version 2.0.
