v0.1.0
[0.1.0] - 2026-07-07
The first public release of ThetaDataDx: a terminal-exact, drop-in market-data SDK across Rust, Python, TypeScript, and C++, plus a bundled HTTP + WebSocket server and an MCP server, all over one Rust engine. It connects straight to ThetaData with nothing to install and run locally, and delivers US stock, option, index, and interest-rate data three ways from a single authenticated client: point-in-time history, real-time streaming, and whole-universe flat files.
The SDK ships under per-language package names: thetadatadx-rs (crates.io), thetadatadx-py (PyPI), and thetadatadx-ts (npm, with its platform packages); thetadatadx-cpp is the in-repo CMake/header target; the MCP server is thetadatadx-mcp-server on npm. The import surface is unchanged — use thetadatadx::… in Rust, import thetadatadx in Python, the thetadatadx namespace in C++.
Added
- One client, three ways to the data. A single authenticated
Client(with Python's async companionAsyncClient) exposes point-in-time history undermarket_data, real-time streaming understream, and whole-universe flat files on the client directly (client.market_data.stock_history_eod(...),client.stream.subscribe(...)). Single-purposeMarketDataClientandStreamingClientare available on every binding. API-key authentication is supplied inline, read fromTHETADATA_API_KEY, or loaded from a.envfile, and authenticates both channels; email and password authentication is also supported. - The bundled server speaks the ThetaData v3 terminal contract, 1:1. The HTTP server serves the v3 REST and WebSocket surface: v3
{response}bodies, one ISO local-datetime per row,CALL/PUTrights, option rows grouped under{expiration, strike, right}, CSV default with the v3 column order, and plain-text error status. It exposes exactly the terminal's three unauthenticatedGET /v3/terminal/*routes (shutdown,fpss/status,mdds/status, one-word channel health) and reports all four connectivity states (CONNECTED/UNVERIFIED/DISCONNECTED/ERROR). REST data endpoints acceptformat=html; flat-file downloads acceptcsv(default),json,ndjson/jsonl, andhtmlon the terminal'sGET /v3/{sec_type}/flat_file/{req_type}route. On the WebSocket, an optionstrikedefaults to the terminal's 1/10-cent integer (--strike-format dollarsswitches to a dollar value), and every message header carries the streamingstatus. - MCP server on npm. The
thetadatadx-mcp-serverserver runs withnpx -y thetadatadx-mcp-server(no Rust toolchain required;cargo installstays for Rust users), and once authenticated it advertises only the tools the account's per-asset-class subscription grants. - History serves a single date or a date range on the same route. Every intraday history endpoint (stock and option
ohlc/trade/quote/trade_quote, optionopen_interest, the intraday greeks and trade-greeks families, and indexprice) takes an optionaldateplus optionalstart_date/end_date: supplydatefor a single day, or a range on the base route, matching the terminal. - Unambiguous field units and semantics.
strikemeans dollars on every typed surface (float/double), with the exact wire integer reachable under the unit-namedstrike_thousandths(a$550.00strike is550000). The optionrightis the logical character ('C'/'P'; auint32_tUnicode scalar in C).EodTicktime columns arecreated_ms_of_dayandlast_trade_ms_of_day.CalendarDay.is_openis a boolean andstatuscarries theopen/early_close/full_close/weekendvocabulary. Absent contract identity isNone/undefined(Arrow null) in Python and TypeScript; the C-layout rows carry documented fills withhas_contract_id().Contract.option(...)takes the leg as one namedOptionLeg { expiration, strike, right }. - Columnar and Arrow across every binding. A pull-based Arrow
RecordBatchreader,batches(), delivers decoded streaming events as Arrow record batches on a schedule you control (Rustfutures::Stream+.blocking(), Python sync/async iterable over the Arrow C-Data interface, TypeScriptAsyncIterable<RecordBatch>, C++arrow::RecordBatchReader), tunable bybatch_sizeand alingerflush withBlockorDropOldestbackpressure and adropped()/ring_dropped()loss signal. Every history result also emits a projected Arrow-IPC frame with exactly the columns the wire carried, drivable from a live call via TypeScript's<method>WithColumnsand the C / C++ column-presence out-param; multi-symbol snapshots attribute each row to its ownsymbol. Python DataFrame conversion releases the GIL and hands off zero-copy over the Arrow PyCapsule interface. - Live market value. A per-contract theoretical bid / ask computed from the real-time quote, delivered as
StreamData::MarketValuewithmarket_bid/market_ask/ integer-midpointmarket_price, subscribed viaContract::market_value(). - Async query surfaces on every binding. TypeScript is fully asynchronous — every network entry point returns a
Promise(connect factories, all history methods, the streaming lifecycle, flat files), each endpoint taking its required parameters positionally followed by one optional trailing options object (stockHistoryEOD("AAPL", { startDate, endDate, timeoutMs })). PythonAsyncClientgains awaitable constructors and*_asyncflat-file twins; C++ gains an<endpoint>_async(...)companion returningstd::futurefor every buffered query; and the C ABI and C++ stream market-data results through a tick-chunk callback so peak memory tracks one chunk. - Typed, unified error taxonomy. One branded hierarchy across every binding: a
ConfigErrorleaf for environmental faults,InvalidParameterErrorfor rejected configuration, sequence, and flat-file inputs (and, in Python, a subclass of the built-inValueError), andStreamErrorfor streaming faults. Python addsNotFoundError/DeadlineExceededError/UnavailableError(withNoDataFoundError/TimeoutErroraliases). Rate-limit errors expose the decoded server back-off (retry_after/retryAfter/retry_after(); the C-ABIthetadatadx_last_error_retry_after_ms()). - Observability and delivery controls. A slow-callback watchdog (
slow_callback_count()plus a microsecond threshold setter) counts over-budget callback invocations without ever cancelling one; an optionalconsumer_cpuknob pins the streaming consumer thread for low-jitter delivery; and epoch-instant accessors (*_timestamp_ms, plusthetadatadx::time::date_ms_to_epoch_ms) compute DST-aware Unix milliseconds on read from any row carrying a date and a milliseconds-of-day column. - Robust streaming lifecycle. The reconnect budget resets only after a stable connected window so a flapping connection cannot reconnect forever; reconnect marks the session live only after replay succeeds and prefers the last-known-good host; an in-session server rate-limit or restart signal reaches the reconnect classifier; and delta-decode state resets on reconnect so the first post-reconnect ticks decode against a fresh baseline. Subscriptions are de-duplicated and reference-counted so a duplicate subscribe, or a subscribe racing an in-flight reconnect under command-queue backpressure, stays tracked and replayed exactly once. Read, write, and connect timeouts bound every socket operation, and a panicking I/O thread surfaces as a failure to callback, columnar, and pull consumers alike rather than a false "still streaming".
- Memory-safe teardown across the bindings. The C++ destructor routes through the drained teardown path (stopping a stream from inside its own callback can no longer read a destroyed callback, and async market-data views are lvalue-only so a call on a dangling temporary is a compile error); the Node.js client never deadlocks on close behind a slow callback nor leaves a silently dead stream after
reconnect(); and the Python client runs teardown off the GIL. A retired streaming callback is dropped only after the consumer thread confirms quiescence. - Decode and data integrity. Truncated or drifted flat-file and EOD responses fail loud with a typed decode error instead of emitting garbage or zero-filled rows; a wrong-width streaming row is rejected before it poisons the per-contract field cache; market-value arithmetic saturates at the integer extremes instead of panicking on an adversarial quote; OHLCVC volume and count decode as unsigned; projected frames keep the trading
date; and OCC-21 parsing and FIT integer runs never panic or drop digits on hostile input. - Configuration and connect hygiene. Config invariants run at the single connect funnel every path routes through; the loader rejects a misspelled or unknown section at load time; a server retry hint is clamped to the configured ceiling; the C ABI installs its TLS crypto provider from every connect entrypoint; and the embedded async runtime honors the
worker_threadsknob. Config sections aremarket_dataandstreaming, each channel selected independently (MarketDataEnvironmentPROD/STAGE,StreamingEnvironmentPROD/DEV) via per-binding setters and theTHETADATA_MARKET_DATA_*/THETADATA_STREAMING_*environment variables; an unrecognized or cross-channel value is a hard error naming the key and the valid set. - Flat-file formats and parity.
FlatFileFormatcoversCsv,Jsonl,Json(a single JSON array), andHtml(an HTML table) on the SDK surface, restricted to the datasets actually served — optiontrade_quote/open_interest/eodand stocktrade_quote/eod, with an unsupported pair rejected by a typed error before any network round-trip. A machine-checked cross-binding parity contract (parity.toml) covers value-field types and the per-endpoint async and streaming families. Afrom_fileclient-construction convenience defaults to the production configuration, list-endpoint results are sorted, and every binding carries generated trade-flag accessors. - C ABI and layout.
ThetaDataDxContract.strikeisdoubledollars with a trailingint32_t strike_thousandths; per-tickrightfields areuint32_tUnicode scalars; andThetaDataDxCalendarDay.is_openis a C99bool. The workspace builds and publishes a singlethetadatadxartifact — every row carries its price as decodedf64dollars, and time-and-calendar support is a private internal module.
Security
- Every client TLS configuration is built with an explicit
ringcrypto provider and explicit protocol versions rather than a process-global default, soringis the sole provider in the graph and a connect never depends on an installed default. - The streaming login wipes the account password from memory the moment the login frame is sent, so the cleartext password is not retained in released heap or a buffered protocol frame after authentication, on the first connect and every reconnect.
- Authentication errors carry only the HTTP status and never the upstream response body, the auth client does not follow redirects, and session UUIDs are redacted from
Debugoutput.
What's Changed
- fix(docs,ci): stable-install docs for 13.0.0 GA and repoint semver baseline by @userFRM in #1180
- docs(readme): 13.0.0 production-ready banner + withdrawal notice by @userFRM in #1181
- docs: make 13.0.0 the first published release in changelog and site by @userFRM in #1182
- docs: split trade and quote conditions into separate pages by @userFRM in #1183
- test(python): stock EOD projection must keep the trading date by @userFRM in #1184
- release: rename SDK packages to per-language names and reset to 0.1.0 by @userFRM in #1185
Full Changelog: v13.0.0...v0.1.0