@fullstackcraftllc/floe 0.0.2 → 0.0.4
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 +23 -1
- package/dist/client/FloeClient.d.ts +64 -5
- package/dist/client/FloeClient.js +134 -5
- package/dist/client/brokers/SchwabClient.d.ts +2 -0
- package/dist/client/brokers/SchwabClient.js +6 -0
- package/dist/client/brokers/TastyTradeClient.d.ts +384 -0
- package/dist/client/brokers/TastyTradeClient.js +1081 -0
- package/dist/client/brokers/TradierClient.d.ts +233 -1
- package/dist/client/brokers/TradierClient.js +435 -0
- package/dist/impliedpdf/index.d.ts +148 -0
- package/dist/impliedpdf/index.js +277 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -1
- package/dist/utils/occ.d.ts +1 -1
- package/dist/utils/occ.js +5 -4
- package/dist/volatility/index.d.ts +1 -1
- package/dist/volatility/index.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
  
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Zero-dependency TypeScript functions for options flow: Black-Scholes, Greeks, and dealer exposures, and more, with a clean, type-safe API. Built for use in trading platforms and fintech applications.
|
|
6
6
|
|
|
7
7
|
The same library that is used in Full Stack Craft's various fintech products including [The Wheel Screener](https://wheelscreener.com), [LEAPS Screener](https://leapsscreener.com), [Option Screener](https://option-screener.com), [AMT JOY](https://amtjoy.com), and [VannaCharm](https://vannacharm.com).
|
|
8
8
|
|
|
@@ -30,6 +30,28 @@ The same library that is used in Full Stack Craft's various fintech products inc
|
|
|
30
30
|
- 💪 **Type-Safe** - Full TypeScript support
|
|
31
31
|
- ⚡ **Zero Dependencies** - Lightweight and fast
|
|
32
32
|
|
|
33
|
+
## Broker Support Roadmap
|
|
34
|
+
|
|
35
|
+
Due to the overwhelming variety of how broker APIs structure their data (and how they make it available), there is a wide variety of how much support we can provide out-of-the-box for different brokers, summarized in this table:
|
|
36
|
+
|
|
37
|
+
| Broker | Black-Scholes | Greeks | Open Interest Based Exposures | Options-Book Based Exposures | Implied PDF Calculations |
|
|
38
|
+
|-----------------------|--------------|--------|-------------------------------|------------------------------|-------------------------|
|
|
39
|
+
| Tradier (via WebSocket) | ✅ | ✅ | ✅ | ✅ | Coming soon |
|
|
40
|
+
| Schwab (via WebSocket) | Coming soon | Coming soon | Coming soon | Coming soon | Coming soon |
|
|
41
|
+
| Tastytrade (via WebSocket - DXLink Streamer) | Coming soon | Coming soon | Coming soon | Coming soon | Coming soon |
|
|
42
|
+
| TradeStation (via HTTP Streaming) | Coming soon | Coming soon | Coming soon | Coming soon | Coming soon |
|
|
43
|
+
|
|
44
|
+
Ideally all aspects of `floe` will be available for all brokers, but this will take time to determine as we work through the various data structures and formats that each broker provides.
|
|
45
|
+
|
|
46
|
+
## Unsupported Brokers
|
|
47
|
+
|
|
48
|
+
The following brokers have no public API:
|
|
49
|
+
|
|
50
|
+
- Fidelity
|
|
51
|
+
- Robinhood
|
|
52
|
+
|
|
53
|
+
If your broker is not listed above, you can still use `floe` by normalizing your broker's data structures to match the expected input types. With options, you can get quite far with `floe` just by having the market price for the underlying and each option. (From those alone you can back out the IV, greeks, and exposures.)
|
|
54
|
+
|
|
33
55
|
## Installation
|
|
34
56
|
|
|
35
57
|
```bash
|
|
@@ -5,7 +5,9 @@ import { NormalizedOption, NormalizedTicker } from "../types";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare enum Broker {
|
|
7
7
|
/** Tradier brokerage API */
|
|
8
|
-
TRADIER = "tradier"
|
|
8
|
+
TRADIER = "tradier",
|
|
9
|
+
/** TastyTrade brokerage API (uses DxLink WebSocket) */
|
|
10
|
+
TASTYTRADE = "tastytrade"
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Event types emitted by the FloeClient.
|
|
@@ -69,12 +71,10 @@ export declare class FloeClient {
|
|
|
69
71
|
private currentSubscribedTickers;
|
|
70
72
|
/** List of option symbols (OCC format) currently subscribed to */
|
|
71
73
|
private currentSubscribedOptions;
|
|
72
|
-
/** Cache of the latest normalized ticker data */
|
|
73
|
-
private normalizedTickers;
|
|
74
|
-
/** Cache of the latest normalized option data */
|
|
75
|
-
private normalizedOptions;
|
|
76
74
|
/** Tradier broker client instance */
|
|
77
75
|
private tradierClient;
|
|
76
|
+
/** TastyTrade broker client instance */
|
|
77
|
+
private tastyTradeClient;
|
|
78
78
|
/** Event listeners registry for the EventEmitter pattern */
|
|
79
79
|
private eventListeners;
|
|
80
80
|
/** Callback for ticker data changes (legacy callback pattern) */
|
|
@@ -200,6 +200,65 @@ export declare class FloeClient {
|
|
|
200
200
|
* ```
|
|
201
201
|
*/
|
|
202
202
|
unsubscribeFromOptions(symbols: Array<string>): void;
|
|
203
|
+
/**
|
|
204
|
+
* Fetches open interest and initial option data via REST API.
|
|
205
|
+
*
|
|
206
|
+
* @param symbols - Array of option symbols in OCC format to fetch data for.
|
|
207
|
+
* If not provided, fetches data for all currently subscribed options.
|
|
208
|
+
* @returns Promise that resolves when all data has been fetched
|
|
209
|
+
*
|
|
210
|
+
* @throws {Error} Throws if no broker connection has been established
|
|
211
|
+
*
|
|
212
|
+
* @remarks
|
|
213
|
+
* Open interest is not available via streaming and must be fetched via REST API.
|
|
214
|
+
* This method should be called after subscribing to options to populate
|
|
215
|
+
* open interest, volume, and initial bid/ask values.
|
|
216
|
+
*
|
|
217
|
+
* The fetched data is automatically merged into the option cache and
|
|
218
|
+
* emitted via 'optionUpdate' events.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* // Subscribe to options
|
|
223
|
+
* client.subscribeToOptions(optionSymbols);
|
|
224
|
+
*
|
|
225
|
+
* // Fetch open interest data
|
|
226
|
+
* await client.fetchOpenInterest();
|
|
227
|
+
*
|
|
228
|
+
* // Options now have open interest populated
|
|
229
|
+
* client.on('optionUpdate', (option) => {
|
|
230
|
+
* console.log(`${option.occSymbol}: OI=${option.openInterest}`);
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
fetchOpenInterest(symbols?: string[]): Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* Returns cached option data for a specific symbol.
|
|
237
|
+
*
|
|
238
|
+
* @param occSymbol - OCC option symbol
|
|
239
|
+
* @returns Cached option data, or undefined if not found
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const option = client.getOption('QQQ250117C00530000');
|
|
244
|
+
* console.log(`Open Interest: ${option?.openInterest}`);
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
getOption(occSymbol: string): NormalizedOption | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* Returns all cached options.
|
|
250
|
+
*
|
|
251
|
+
* @returns Map of OCC symbols to option data
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* const allOptions = client.getAllOptions();
|
|
256
|
+
* for (const [symbol, option] of allOptions) {
|
|
257
|
+
* console.log(`${symbol}: OI=${option.openInterest}`);
|
|
258
|
+
* }
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
getAllOptions(): Map<string, NormalizedOption>;
|
|
203
262
|
/**
|
|
204
263
|
* Registers an event listener for the specified event type.
|
|
205
264
|
*
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FloeClient = exports.Broker = void 0;
|
|
4
4
|
const TradierClient_1 = require("./brokers/TradierClient");
|
|
5
|
+
const TastyTradeClient_1 = require("./brokers/TastyTradeClient");
|
|
5
6
|
/**
|
|
6
7
|
* Supported broker integrations for the FloeClient.
|
|
7
8
|
* @enum {string}
|
|
@@ -10,7 +11,8 @@ var Broker;
|
|
|
10
11
|
(function (Broker) {
|
|
11
12
|
/** Tradier brokerage API */
|
|
12
13
|
Broker["TRADIER"] = "tradier";
|
|
13
|
-
|
|
14
|
+
/** TastyTrade brokerage API (uses DxLink WebSocket) */
|
|
15
|
+
Broker["TASTYTRADE"] = "tastytrade";
|
|
14
16
|
})(Broker || (exports.Broker = Broker = {}));
|
|
15
17
|
/**
|
|
16
18
|
* FloeClient provides a unified, broker-agnostic interface for subscribing to
|
|
@@ -61,12 +63,10 @@ class FloeClient {
|
|
|
61
63
|
this.currentSubscribedTickers = [];
|
|
62
64
|
/** List of option symbols (OCC format) currently subscribed to */
|
|
63
65
|
this.currentSubscribedOptions = [];
|
|
64
|
-
/** Cache of the latest normalized ticker data */
|
|
65
|
-
this.normalizedTickers = [];
|
|
66
|
-
/** Cache of the latest normalized option data */
|
|
67
|
-
this.normalizedOptions = [];
|
|
68
66
|
/** Tradier broker client instance */
|
|
69
67
|
this.tradierClient = null;
|
|
68
|
+
/** TastyTrade broker client instance */
|
|
69
|
+
this.tastyTradeClient = null;
|
|
70
70
|
/** Event listeners registry for the EventEmitter pattern */
|
|
71
71
|
this.eventListeners = new Map();
|
|
72
72
|
/** Callback for ticker data changes (legacy callback pattern) */
|
|
@@ -119,6 +119,27 @@ class FloeClient {
|
|
|
119
119
|
// Connect to the streaming API
|
|
120
120
|
await this.tradierClient.connect();
|
|
121
121
|
break;
|
|
122
|
+
case Broker.TASTYTRADE:
|
|
123
|
+
// For TastyTrade, authKey is the session token
|
|
124
|
+
this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
|
|
125
|
+
sessionToken: authKey,
|
|
126
|
+
});
|
|
127
|
+
// Wire up TastyTradeClient events to FloeClient events
|
|
128
|
+
this.tastyTradeClient.on('tickerUpdate', (ticker) => {
|
|
129
|
+
this.emit('tickerUpdate', ticker);
|
|
130
|
+
});
|
|
131
|
+
this.tastyTradeClient.on('optionUpdate', (option) => {
|
|
132
|
+
this.emit('optionUpdate', option);
|
|
133
|
+
});
|
|
134
|
+
this.tastyTradeClient.on('error', (error) => {
|
|
135
|
+
this.emit('error', error);
|
|
136
|
+
});
|
|
137
|
+
this.tastyTradeClient.on('disconnected', () => {
|
|
138
|
+
this.emit('disconnected', { broker, reason: 'DxLink WebSocket disconnected' });
|
|
139
|
+
});
|
|
140
|
+
// Connect to the streaming API
|
|
141
|
+
await this.tastyTradeClient.connect();
|
|
142
|
+
break;
|
|
122
143
|
default:
|
|
123
144
|
throw new Error(`Unsupported broker: ${broker}`);
|
|
124
145
|
}
|
|
@@ -140,6 +161,10 @@ class FloeClient {
|
|
|
140
161
|
this.tradierClient.disconnect();
|
|
141
162
|
this.tradierClient = null;
|
|
142
163
|
}
|
|
164
|
+
if (this.tastyTradeClient) {
|
|
165
|
+
this.tastyTradeClient.disconnect();
|
|
166
|
+
this.tastyTradeClient = null;
|
|
167
|
+
}
|
|
143
168
|
const broker = this.currentBroker;
|
|
144
169
|
this.currentBroker = null;
|
|
145
170
|
this.currentSubscribedTickers = [];
|
|
@@ -170,6 +195,9 @@ class FloeClient {
|
|
|
170
195
|
case Broker.TRADIER:
|
|
171
196
|
this.tradierClient?.subscribe(tickers);
|
|
172
197
|
break;
|
|
198
|
+
case Broker.TASTYTRADE:
|
|
199
|
+
this.tastyTradeClient?.subscribe(tickers);
|
|
200
|
+
break;
|
|
173
201
|
default:
|
|
174
202
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
175
203
|
}
|
|
@@ -204,6 +232,9 @@ class FloeClient {
|
|
|
204
232
|
case Broker.TRADIER:
|
|
205
233
|
this.tradierClient?.subscribe(symbols);
|
|
206
234
|
break;
|
|
235
|
+
case Broker.TASTYTRADE:
|
|
236
|
+
this.tastyTradeClient?.subscribe(symbols);
|
|
237
|
+
break;
|
|
207
238
|
default:
|
|
208
239
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
209
240
|
}
|
|
@@ -230,6 +261,9 @@ class FloeClient {
|
|
|
230
261
|
case Broker.TRADIER:
|
|
231
262
|
this.tradierClient?.unsubscribe(tickers);
|
|
232
263
|
break;
|
|
264
|
+
case Broker.TASTYTRADE:
|
|
265
|
+
this.tastyTradeClient?.unsubscribe(tickers);
|
|
266
|
+
break;
|
|
233
267
|
default:
|
|
234
268
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
235
269
|
}
|
|
@@ -256,10 +290,105 @@ class FloeClient {
|
|
|
256
290
|
case Broker.TRADIER:
|
|
257
291
|
this.tradierClient?.unsubscribe(symbols);
|
|
258
292
|
break;
|
|
293
|
+
case Broker.TASTYTRADE:
|
|
294
|
+
this.tastyTradeClient?.unsubscribe(symbols);
|
|
295
|
+
break;
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Fetches open interest and initial option data via REST API.
|
|
302
|
+
*
|
|
303
|
+
* @param symbols - Array of option symbols in OCC format to fetch data for.
|
|
304
|
+
* If not provided, fetches data for all currently subscribed options.
|
|
305
|
+
* @returns Promise that resolves when all data has been fetched
|
|
306
|
+
*
|
|
307
|
+
* @throws {Error} Throws if no broker connection has been established
|
|
308
|
+
*
|
|
309
|
+
* @remarks
|
|
310
|
+
* Open interest is not available via streaming and must be fetched via REST API.
|
|
311
|
+
* This method should be called after subscribing to options to populate
|
|
312
|
+
* open interest, volume, and initial bid/ask values.
|
|
313
|
+
*
|
|
314
|
+
* The fetched data is automatically merged into the option cache and
|
|
315
|
+
* emitted via 'optionUpdate' events.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* // Subscribe to options
|
|
320
|
+
* client.subscribeToOptions(optionSymbols);
|
|
321
|
+
*
|
|
322
|
+
* // Fetch open interest data
|
|
323
|
+
* await client.fetchOpenInterest();
|
|
324
|
+
*
|
|
325
|
+
* // Options now have open interest populated
|
|
326
|
+
* client.on('optionUpdate', (option) => {
|
|
327
|
+
* console.log(`${option.occSymbol}: OI=${option.openInterest}`);
|
|
328
|
+
* });
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
async fetchOpenInterest(symbols) {
|
|
332
|
+
const symbolsToFetch = symbols ?? this.currentSubscribedOptions;
|
|
333
|
+
if (symbolsToFetch.length === 0) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
switch (this.currentBroker) {
|
|
337
|
+
case Broker.TRADIER:
|
|
338
|
+
await this.tradierClient?.fetchOpenInterest(symbolsToFetch);
|
|
339
|
+
break;
|
|
340
|
+
case Broker.TASTYTRADE:
|
|
341
|
+
await this.tastyTradeClient?.fetchOpenInterest(symbolsToFetch);
|
|
342
|
+
break;
|
|
259
343
|
default:
|
|
260
344
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
261
345
|
}
|
|
262
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Returns cached option data for a specific symbol.
|
|
349
|
+
*
|
|
350
|
+
* @param occSymbol - OCC option symbol
|
|
351
|
+
* @returns Cached option data, or undefined if not found
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```typescript
|
|
355
|
+
* const option = client.getOption('QQQ250117C00530000');
|
|
356
|
+
* console.log(`Open Interest: ${option?.openInterest}`);
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
getOption(occSymbol) {
|
|
360
|
+
switch (this.currentBroker) {
|
|
361
|
+
case Broker.TRADIER:
|
|
362
|
+
return this.tradierClient?.getOption(occSymbol);
|
|
363
|
+
case Broker.TASTYTRADE:
|
|
364
|
+
return this.tastyTradeClient?.getOption(occSymbol);
|
|
365
|
+
default:
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Returns all cached options.
|
|
371
|
+
*
|
|
372
|
+
* @returns Map of OCC symbols to option data
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```typescript
|
|
376
|
+
* const allOptions = client.getAllOptions();
|
|
377
|
+
* for (const [symbol, option] of allOptions) {
|
|
378
|
+
* console.log(`${symbol}: OI=${option.openInterest}`);
|
|
379
|
+
* }
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
getAllOptions() {
|
|
383
|
+
switch (this.currentBroker) {
|
|
384
|
+
case Broker.TRADIER:
|
|
385
|
+
return this.tradierClient?.getAllOptions() ?? new Map();
|
|
386
|
+
case Broker.TASTYTRADE:
|
|
387
|
+
return this.tastyTradeClient?.getAllOptions() ?? new Map();
|
|
388
|
+
default:
|
|
389
|
+
return new Map();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
263
392
|
// ==================== Event Emitter Pattern ====================
|
|
264
393
|
/**
|
|
265
394
|
* Registers an event listener for the specified event type.
|