thetadatadx 9.0.1 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -32
- package/index.d.ts +419 -92
- package/index.js +60 -53
- package/package.json +10 -8
- package/streaming-session.d.ts +74 -0
- package/streaming-session.js +175 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Node.js SDK for ThetaData market data. napi-rs bindings over the `thetadatadx` R
|
|
|
4
4
|
|
|
5
5
|
Every call crosses the napi boundary into compiled Rust: gRPC, protobuf, zstd, FIT decoding, and TCP streaming run inside the `thetadatadx` crate.
|
|
6
6
|
|
|
7
|
-
> **
|
|
7
|
+
> **Surface coverage:** the TypeScript binding exposes all three ThetaData surfaces — MDDS (historical), FPSS (streaming), and FLATFILES (whole-universe daily blobs). Flat files land via `tdx.flatFiles.*()` with `.toArrowIpc()` and `.toJson()` terminals plus a `tdx.flatFileToPath(...)` raw-bytes helper — see the [Flat Files](#flat-files) section for the full method list.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -20,12 +20,12 @@ Pre-built binaries are downloaded automatically for your platform. Supported:
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
22
22
|
```js
|
|
23
|
-
const {
|
|
23
|
+
const { ThetaDataDxClient } = require('thetadatadx');
|
|
24
24
|
|
|
25
25
|
async function main() {
|
|
26
26
|
// Connect (requires ThetaData credentials)
|
|
27
|
-
const tdx =
|
|
28
|
-
// Or: const tdx =
|
|
27
|
+
const tdx = ThetaDataDxClient.connectFromFile('creds.txt');
|
|
28
|
+
// Or: const tdx = ThetaDataDxClient.connect('user@example.com', 'password');
|
|
29
29
|
|
|
30
30
|
// Historical endpoints return an array of typed tick objects
|
|
31
31
|
// (`OhlcTick[]`, `QuoteTick[]`, ...). Index into the array to
|
|
@@ -36,25 +36,90 @@ async function main() {
|
|
|
36
36
|
// With timeout
|
|
37
37
|
const snap = tdx.stockSnapshotQuote(['AAPL', 'MSFT'], null, null, 5000);
|
|
38
38
|
|
|
39
|
-
// Streaming —
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
// Streaming — primary fluent contract-first API.
|
|
40
|
+
// `Contract.stock("AAPL").quote()` returns a typed `Subscription`
|
|
41
|
+
// value the polymorphic `client.subscribe(...)` accepts directly,
|
|
42
|
+
// matching the documented Rust / Python shape.
|
|
43
|
+
const stock = ContractRef.stock('AAPL');
|
|
44
|
+
const option = ContractRef.option('SPY', '20260620', '550', 'C');
|
|
45
|
+
|
|
46
|
+
await using session = await tdx.streaming((event) => {
|
|
46
47
|
if (event.kind === 'quote') {
|
|
47
48
|
console.log(event.quote.bid, event.quote.ask);
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
|
-
tdx.
|
|
51
|
+
tdx.subscribe(stock.quote());
|
|
52
|
+
tdx.subscribe(option.trade());
|
|
53
|
+
tdx.subscribe(SecType.option().fullTrades());
|
|
54
|
+
tdx.subscribeMany([stock.quote(), option.quote()]);
|
|
51
55
|
// ...do other work; the callback fires on incoming events...
|
|
52
|
-
|
|
56
|
+
// `[Symbol.asyncDispose]` runs on scope exit:
|
|
57
|
+
// stopStreaming(); await awaitDrain(5000);
|
|
58
|
+
// If the drain barrier times out, `console.warn` fires but the
|
|
59
|
+
// `using` scope still exits cleanly.
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
main().catch(console.error);
|
|
56
63
|
```
|
|
57
64
|
|
|
65
|
+
The `await using` form is the recommended API. The `StreamingSession`
|
|
66
|
+
forwards every method call (`subscribe`, `subscribeMany`,
|
|
67
|
+
`unsubscribe`, `unsubscribeMany`, `activeSubscriptions`,
|
|
68
|
+
`droppedEventCount`, `reconnect`, ...) to the underlying `ThetaDataDxClient`
|
|
69
|
+
through a `Proxy`, so adding a new method to the napi binding makes it
|
|
70
|
+
callable through the session automatically -- the streaming surface is
|
|
71
|
+
a single source of truth rooted in the Rust crate.
|
|
72
|
+
|
|
73
|
+
For the lower-level escape hatch (e.g. cross-process lifecycle
|
|
74
|
+
management, custom shutdown ordering), call the lifecycle methods
|
|
75
|
+
explicitly:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
tdx.startStreaming((event) => { /* ... */ });
|
|
79
|
+
tdx.subscribe(ContractRef.stock('AAPL').quote());
|
|
80
|
+
// ...do other work...
|
|
81
|
+
tdx.stopStreaming();
|
|
82
|
+
// Drain barrier: by the time `awaitDrain(5000)` resolves, the
|
|
83
|
+
// consumer thread is guaranteed to have finished firing the
|
|
84
|
+
// callback, so the JS closure can be released without a
|
|
85
|
+
// use-after-free race against the LMAX Disruptor consumer.
|
|
86
|
+
const drained = await tdx.awaitDrain(5000);
|
|
87
|
+
if (!drained) console.warn('drain timed out');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Pull-iter delivery — `for await (const event of iter)` (high-throughput drain)
|
|
91
|
+
|
|
92
|
+
Push-callback (`tdx.streaming(callback)` above) is the recommended
|
|
93
|
+
default for low-latency single-event reaction. Pull-iter is the
|
|
94
|
+
sibling delivery mode for high-throughput batch processing where
|
|
95
|
+
the dominant cost is per-event JS work rather than per-event vendor
|
|
96
|
+
latency:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const iter = tdx.startStreamingIter();
|
|
100
|
+
tdx.subscribe(SecType.option().fullTrades());
|
|
101
|
+
for await (const event of iter) {
|
|
102
|
+
if (event.kind === 'trade') {
|
|
103
|
+
buf.push([event.trade.price, event.trade.size]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Stop the streaming session when done. The async-iterator
|
|
107
|
+
// protocol handles `break` cleanly via `return()`, which calls
|
|
108
|
+
// `iter.close()` so the worker thread stops blocking on the queue.
|
|
109
|
+
tdx.stopStreaming();
|
|
110
|
+
await tdx.awaitDrain(5000);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The Disruptor consumer pushes events into a per-client bounded
|
|
114
|
+
queue; the `for await` loop drains the queue from the Node main
|
|
115
|
+
thread in batches, with the actual queue wait happening on
|
|
116
|
+
`tokio::task::spawn_blocking` so the event loop is never blocked.
|
|
117
|
+
|
|
118
|
+
Mode is chosen at start. Push and pull are mutually exclusive on a
|
|
119
|
+
given client; switch by calling `stopStreaming()` first. Backpressure
|
|
120
|
+
surfaces on the same `droppedEventCount()` counter as the callback
|
|
121
|
+
path.
|
|
122
|
+
|
|
58
123
|
## TypeScript types
|
|
59
124
|
|
|
60
125
|
Every tick type and FPSS event is emitted as a `#[napi(object)]` struct on
|
|
@@ -72,22 +137,50 @@ discriminated `FpssEvent`, narrowed on `event.kind`:
|
|
|
72
137
|
```ts
|
|
73
138
|
tdx.startStreaming((event: FpssEvent) => {
|
|
74
139
|
switch (event.kind) {
|
|
75
|
-
|
|
76
|
-
case '
|
|
77
|
-
case '
|
|
78
|
-
case '
|
|
79
|
-
case '
|
|
80
|
-
|
|
140
|
+
// Market-data ticks
|
|
141
|
+
case 'quote': /* event.quote is Quote */ break;
|
|
142
|
+
case 'trade': /* event.trade is Trade */ break;
|
|
143
|
+
case 'ohlcvc': /* event.ohlcvc is Ohlcvc */ break;
|
|
144
|
+
case 'open_interest': /* event.openInterest is OpenInterest */ break;
|
|
145
|
+
|
|
146
|
+
// Control / lifecycle events — one typed payload per FpssControl variant
|
|
147
|
+
case 'login_success': /* event.loginSuccess is LoginSuccess */ break;
|
|
148
|
+
case 'contract_assigned': /* event.contractAssigned is ContractAssigned */ break;
|
|
149
|
+
case 'req_response': /* event.reqResponse is ReqResponse */ break;
|
|
150
|
+
case 'market_open': /* event.marketOpen is MarketOpen */ break;
|
|
151
|
+
case 'market_close': /* event.marketClose is MarketClose */ break;
|
|
152
|
+
case 'server_error': /* event.serverError is ServerError */ break;
|
|
153
|
+
case 'disconnected': /* event.disconnected is Disconnected */ break;
|
|
154
|
+
case 'reconnecting': /* event.reconnecting is Reconnecting */ break;
|
|
155
|
+
case 'reconnected': /* event.reconnected is Reconnected */ break;
|
|
156
|
+
case 'error': /* event.error is Error */ break;
|
|
157
|
+
case 'unknown_frame': /* event.unknownFrame is UnknownFrame */ break;
|
|
158
|
+
case 'connected': /* event.connected is Connected */ break;
|
|
159
|
+
case 'ping': /* event.ping is Ping */ break;
|
|
160
|
+
case 'reconnected_server': /* event.reconnectedServer is ReconnectedServer */ break;
|
|
161
|
+
case 'restart': /* event.restart is Restart */ break;
|
|
162
|
+
case 'unknown_control': /* event.unknownControl is UnknownControl */ break;
|
|
81
163
|
}
|
|
82
164
|
});
|
|
83
165
|
```
|
|
84
166
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
167
|
+
Truncated / unrecognised wire frames are filtered before the callback
|
|
168
|
+
fires and accounted on the `thetadatadx.fpss.decode_failures` metric
|
|
169
|
+
counter on the Rust side; they never surface as an `FpssEvent`.
|
|
170
|
+
|
|
171
|
+
The `kind` field is typed as a string-literal union narrowed by the
|
|
172
|
+
generated `index.d.ts` — plain strings, not a TS `enum` (the previous
|
|
173
|
+
`const enum FpssEventKind` was removed in #376 because it broke
|
|
174
|
+
downstream consumers with `"isolatedModules": true`), so it works in
|
|
175
|
+
every toolchain including Vite, esbuild, ts-jest, and Next.js.
|
|
176
|
+
|
|
177
|
+
Each typed control payload mirrors the corresponding `FpssControl::*`
|
|
178
|
+
Rust variant one-for-one — `Disconnected.reason` / `Reconnecting.reason`
|
|
179
|
+
carry the `RemoveReason` discriminant as `i32`, `Reconnecting.delayMs` is
|
|
180
|
+
`bigint` (`u64`), `Ping.payload` and `UnknownFrame.payload` are
|
|
181
|
+
`Buffer`-backed byte arrays. Both Python and TypeScript surfaces are
|
|
182
|
+
generated from `fpss_event_schema.toml`, so consumer code ports between
|
|
183
|
+
the two languages without a discriminator rewrite.
|
|
91
184
|
|
|
92
185
|
### `bigint` fields
|
|
93
186
|
|
|
@@ -98,12 +191,40 @@ OHLC / EOD tick, `droppedEventCount()` on the streaming client, and
|
|
|
98
191
|
(`42n`) for comparisons or widen to `Number(x)` at the point of
|
|
99
192
|
display (watch for loss of precision beyond 2^53).
|
|
100
193
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
194
|
+
## Flat Files
|
|
195
|
+
|
|
196
|
+
Whole-universe daily snapshots over the legacy MDDS port. Decoded schema
|
|
197
|
+
is determined at runtime by `(SecType, ReqType)`, so the binding emits
|
|
198
|
+
Arrow IPC stream bytes — pair with `apache-arrow`'s `tableFromIPC` to
|
|
199
|
+
materialise a typed `Table`.
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { ThetaDataDxClient } from "thetadatadx";
|
|
203
|
+
import { tableFromIPC } from "apache-arrow"; // peer dep
|
|
204
|
+
|
|
205
|
+
const tdx = ThetaDataDxClient.connectFromFile("creds.txt");
|
|
206
|
+
|
|
207
|
+
const rows = tdx.flatFiles.optionQuote("20260428");
|
|
208
|
+
console.log(rows.len());
|
|
209
|
+
|
|
210
|
+
const ipc = rows.toArrowIpc();
|
|
211
|
+
const table = tableFromIPC(ipc);
|
|
212
|
+
|
|
213
|
+
// Or skip Arrow and emit a JSON array of objects
|
|
214
|
+
const json = JSON.parse(rows.toJson());
|
|
215
|
+
|
|
216
|
+
// Generic dispatcher
|
|
217
|
+
const oi = tdx.flatFiles.request("OPTION", "OPEN_INTEREST", "20260428");
|
|
218
|
+
|
|
219
|
+
// Raw vendor CSV / JSONL straight to disk
|
|
220
|
+
tdx.flatfileToPath("OPTION", "QUOTE", "20260428",
|
|
221
|
+
"/tmp/option-quote", "csv");
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Available `flatFiles.*` methods: `optionQuote`, `optionTrade`,
|
|
225
|
+
`optionTradeQuote`, `optionOhlc`, `optionOpenInterest`, `optionEod`,
|
|
226
|
+
`stockQuote`, `stockTrade`, `stockTradeQuote`, `stockEod`, plus
|
|
227
|
+
`request(secType, reqType, date)`.
|
|
107
228
|
|
|
108
229
|
## Building from source
|
|
109
230
|
|
|
@@ -117,7 +238,7 @@ npm run build # requires Rust stable + protoc
|
|
|
117
238
|
|
|
118
239
|
## API reference
|
|
119
240
|
|
|
120
|
-
Every historical endpoint from `endpoint_surface.toml` is exposed as a camelCase method on `
|
|
241
|
+
Every historical endpoint from `endpoint_surface.toml` is exposed as a camelCase method on `ThetaDataDxClient`. See `index.d.ts` for the complete method list with JSDoc comments.
|
|
121
242
|
|
|
122
243
|
## Docs
|
|
123
244
|
|