@fullstackcraftllc/floe 0.0.3 → 0.0.5
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 -0
- package/dist/client/FloeClient.d.ts +27 -6
- package/dist/client/FloeClient.js +163 -7
- package/dist/client/brokers/SchwabClient.d.ts +448 -0
- package/dist/client/brokers/SchwabClient.js +1052 -0
- package/dist/client/brokers/TastyTradeClient.d.ts +388 -0
- package/dist/client/brokers/TastyTradeClient.js +1100 -0
- package/dist/client/brokers/TradeStationClient.d.ts +361 -0
- package/dist/client/brokers/TradeStationClient.js +880 -0
- package/dist/client/brokers/TradierClient.d.ts +7 -1
- package/dist/client/brokers/TradierClient.js +18 -1
- package/dist/impliedpdf/index.d.ts +148 -0
- package/dist/impliedpdf/index.js +277 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -1
- package/dist/volatility/index.d.ts +1 -1
- package/dist/volatility/index.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -30,6 +30,29 @@ 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) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
40
|
+
| Tastytrade (via WebSocket - DXLink Streamer) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
41
|
+
| TradeStation (via HTTP Streaming) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
42
|
+
| Schwab (via WebSocket) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
43
|
+
| Interactive Brokers (via WebSocket) | Coming Soon | Coming Soon | Coming Soon | Coming Soon | Coming Soon |
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
## Unsupported Brokers
|
|
48
|
+
|
|
49
|
+
The following brokers have no public API:
|
|
50
|
+
|
|
51
|
+
- Fidelity
|
|
52
|
+
- Robinhood
|
|
53
|
+
|
|
54
|
+
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.)
|
|
55
|
+
|
|
33
56
|
## Installation
|
|
34
57
|
|
|
35
58
|
```bash
|
|
@@ -5,7 +5,13 @@ 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",
|
|
11
|
+
/** TradeStation brokerage API (uses HTTP streaming) */
|
|
12
|
+
TRADESTATION = "tradestation",
|
|
13
|
+
/** Charles Schwab brokerage API (uses WebSocket streaming) */
|
|
14
|
+
SCHWAB = "schwab"
|
|
9
15
|
}
|
|
10
16
|
/**
|
|
11
17
|
* Event types emitted by the FloeClient.
|
|
@@ -69,31 +75,46 @@ export declare class FloeClient {
|
|
|
69
75
|
private currentSubscribedTickers;
|
|
70
76
|
/** List of option symbols (OCC format) currently subscribed to */
|
|
71
77
|
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
78
|
/** Tradier broker client instance */
|
|
77
79
|
private tradierClient;
|
|
80
|
+
/** TastyTrade broker client instance */
|
|
81
|
+
private tastyTradeClient;
|
|
82
|
+
/** TradeStation broker client instance */
|
|
83
|
+
private tradeStationClient;
|
|
84
|
+
/** Schwab broker client instance */
|
|
85
|
+
private schwabClient;
|
|
78
86
|
/** Event listeners registry for the EventEmitter pattern */
|
|
79
87
|
private eventListeners;
|
|
80
88
|
/** Callback for ticker data changes (legacy callback pattern) */
|
|
81
89
|
private tickerDataCallback;
|
|
82
90
|
/** Callback for option data changes (legacy callback pattern) */
|
|
83
91
|
private optionDataCallback;
|
|
92
|
+
/** Whether to log verbose debug information */
|
|
93
|
+
private readonly verbose;
|
|
84
94
|
/**
|
|
85
95
|
* Creates a new FloeClient instance.
|
|
86
96
|
*
|
|
97
|
+
* @param options - Optional configuration options
|
|
98
|
+
* @param options.verbose - Whether to log verbose debug information for all broker clients (default: false).
|
|
99
|
+
* When enabled, logs critical debug info such as live open interest changes,
|
|
100
|
+
* connection events, and reconnection attempts.
|
|
101
|
+
*
|
|
87
102
|
* @remarks
|
|
88
103
|
* The client is created in a disconnected state. Call {@link connect} to
|
|
89
104
|
* establish a connection to a broker before subscribing to data.
|
|
90
105
|
*
|
|
91
106
|
* @example
|
|
92
107
|
* ```typescript
|
|
108
|
+
* // Create client with default settings
|
|
93
109
|
* const client = new FloeClient();
|
|
110
|
+
*
|
|
111
|
+
* // Create client with verbose logging enabled
|
|
112
|
+
* const verboseClient = new FloeClient({ verbose: true });
|
|
94
113
|
* ```
|
|
95
114
|
*/
|
|
96
|
-
constructor(
|
|
115
|
+
constructor(options?: {
|
|
116
|
+
verbose?: boolean;
|
|
117
|
+
});
|
|
97
118
|
/**
|
|
98
119
|
* Establishes a connection to a broker's API.
|
|
99
120
|
*
|
|
@@ -2,6 +2,9 @@
|
|
|
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");
|
|
6
|
+
const TradeStationClient_1 = require("./brokers/TradeStationClient");
|
|
7
|
+
const SchwabClient_1 = require("./brokers/SchwabClient");
|
|
5
8
|
/**
|
|
6
9
|
* Supported broker integrations for the FloeClient.
|
|
7
10
|
* @enum {string}
|
|
@@ -10,7 +13,12 @@ var Broker;
|
|
|
10
13
|
(function (Broker) {
|
|
11
14
|
/** Tradier brokerage API */
|
|
12
15
|
Broker["TRADIER"] = "tradier";
|
|
13
|
-
|
|
16
|
+
/** TastyTrade brokerage API (uses DxLink WebSocket) */
|
|
17
|
+
Broker["TASTYTRADE"] = "tastytrade";
|
|
18
|
+
/** TradeStation brokerage API (uses HTTP streaming) */
|
|
19
|
+
Broker["TRADESTATION"] = "tradestation";
|
|
20
|
+
/** Charles Schwab brokerage API (uses WebSocket streaming) */
|
|
21
|
+
Broker["SCHWAB"] = "schwab";
|
|
14
22
|
})(Broker || (exports.Broker = Broker = {}));
|
|
15
23
|
/**
|
|
16
24
|
* FloeClient provides a unified, broker-agnostic interface for subscribing to
|
|
@@ -45,34 +53,46 @@ class FloeClient {
|
|
|
45
53
|
/**
|
|
46
54
|
* Creates a new FloeClient instance.
|
|
47
55
|
*
|
|
56
|
+
* @param options - Optional configuration options
|
|
57
|
+
* @param options.verbose - Whether to log verbose debug information for all broker clients (default: false).
|
|
58
|
+
* When enabled, logs critical debug info such as live open interest changes,
|
|
59
|
+
* connection events, and reconnection attempts.
|
|
60
|
+
*
|
|
48
61
|
* @remarks
|
|
49
62
|
* The client is created in a disconnected state. Call {@link connect} to
|
|
50
63
|
* establish a connection to a broker before subscribing to data.
|
|
51
64
|
*
|
|
52
65
|
* @example
|
|
53
66
|
* ```typescript
|
|
67
|
+
* // Create client with default settings
|
|
54
68
|
* const client = new FloeClient();
|
|
69
|
+
*
|
|
70
|
+
* // Create client with verbose logging enabled
|
|
71
|
+
* const verboseClient = new FloeClient({ verbose: true });
|
|
55
72
|
* ```
|
|
56
73
|
*/
|
|
57
|
-
constructor() {
|
|
74
|
+
constructor(options) {
|
|
58
75
|
/** Currently connected broker, or null if not connected */
|
|
59
76
|
this.currentBroker = null;
|
|
60
77
|
/** List of ticker symbols currently subscribed to */
|
|
61
78
|
this.currentSubscribedTickers = [];
|
|
62
79
|
/** List of option symbols (OCC format) currently subscribed to */
|
|
63
80
|
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
81
|
/** Tradier broker client instance */
|
|
69
82
|
this.tradierClient = null;
|
|
83
|
+
/** TastyTrade broker client instance */
|
|
84
|
+
this.tastyTradeClient = null;
|
|
85
|
+
/** TradeStation broker client instance */
|
|
86
|
+
this.tradeStationClient = null;
|
|
87
|
+
/** Schwab broker client instance */
|
|
88
|
+
this.schwabClient = null;
|
|
70
89
|
/** Event listeners registry for the EventEmitter pattern */
|
|
71
90
|
this.eventListeners = new Map();
|
|
72
91
|
/** Callback for ticker data changes (legacy callback pattern) */
|
|
73
92
|
this.tickerDataCallback = null;
|
|
74
93
|
/** Callback for option data changes (legacy callback pattern) */
|
|
75
94
|
this.optionDataCallback = null;
|
|
95
|
+
this.verbose = options?.verbose ?? false;
|
|
76
96
|
// Initialize event listener maps for each event type
|
|
77
97
|
this.eventListeners.set('tickerUpdate', new Set());
|
|
78
98
|
this.eventListeners.set('optionUpdate', new Set());
|
|
@@ -102,7 +122,7 @@ class FloeClient {
|
|
|
102
122
|
// Connection logic to the broker's API using the authKey
|
|
103
123
|
switch (broker.toLowerCase()) {
|
|
104
124
|
case Broker.TRADIER:
|
|
105
|
-
this.tradierClient = new TradierClient_1.TradierClient(authKey);
|
|
125
|
+
this.tradierClient = new TradierClient_1.TradierClient(authKey, { verbose: this.verbose });
|
|
106
126
|
// Wire up TradierClient events to FloeClient events
|
|
107
127
|
this.tradierClient.on('tickerUpdate', (ticker) => {
|
|
108
128
|
this.emit('tickerUpdate', ticker);
|
|
@@ -119,6 +139,72 @@ class FloeClient {
|
|
|
119
139
|
// Connect to the streaming API
|
|
120
140
|
await this.tradierClient.connect();
|
|
121
141
|
break;
|
|
142
|
+
case Broker.TASTYTRADE:
|
|
143
|
+
// For TastyTrade, authKey is the session token
|
|
144
|
+
this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
|
|
145
|
+
sessionToken: authKey,
|
|
146
|
+
verbose: this.verbose,
|
|
147
|
+
});
|
|
148
|
+
// Wire up TastyTradeClient events to FloeClient events
|
|
149
|
+
this.tastyTradeClient.on('tickerUpdate', (ticker) => {
|
|
150
|
+
this.emit('tickerUpdate', ticker);
|
|
151
|
+
});
|
|
152
|
+
this.tastyTradeClient.on('optionUpdate', (option) => {
|
|
153
|
+
this.emit('optionUpdate', option);
|
|
154
|
+
});
|
|
155
|
+
this.tastyTradeClient.on('error', (error) => {
|
|
156
|
+
this.emit('error', error);
|
|
157
|
+
});
|
|
158
|
+
this.tastyTradeClient.on('disconnected', () => {
|
|
159
|
+
this.emit('disconnected', { broker, reason: 'DxLink WebSocket disconnected' });
|
|
160
|
+
});
|
|
161
|
+
// Connect to the streaming API
|
|
162
|
+
await this.tastyTradeClient.connect();
|
|
163
|
+
break;
|
|
164
|
+
case Broker.TRADESTATION:
|
|
165
|
+
// For TradeStation, authKey is the OAuth access token
|
|
166
|
+
this.tradeStationClient = new TradeStationClient_1.TradeStationClient({
|
|
167
|
+
accessToken: authKey,
|
|
168
|
+
verbose: this.verbose,
|
|
169
|
+
});
|
|
170
|
+
// Wire up TradeStationClient events to FloeClient events
|
|
171
|
+
this.tradeStationClient.on('tickerUpdate', (ticker) => {
|
|
172
|
+
this.emit('tickerUpdate', ticker);
|
|
173
|
+
});
|
|
174
|
+
this.tradeStationClient.on('optionUpdate', (option) => {
|
|
175
|
+
this.emit('optionUpdate', option);
|
|
176
|
+
});
|
|
177
|
+
this.tradeStationClient.on('error', (error) => {
|
|
178
|
+
this.emit('error', error);
|
|
179
|
+
});
|
|
180
|
+
this.tradeStationClient.on('disconnected', () => {
|
|
181
|
+
this.emit('disconnected', { broker, reason: 'HTTP stream disconnected' });
|
|
182
|
+
});
|
|
183
|
+
// Connect to the streaming API
|
|
184
|
+
await this.tradeStationClient.connect();
|
|
185
|
+
break;
|
|
186
|
+
case Broker.SCHWAB:
|
|
187
|
+
// For Schwab, authKey is the OAuth access token
|
|
188
|
+
this.schwabClient = new SchwabClient_1.SchwabClient({
|
|
189
|
+
accessToken: authKey,
|
|
190
|
+
verbose: this.verbose,
|
|
191
|
+
});
|
|
192
|
+
// Wire up SchwabClient events to FloeClient events
|
|
193
|
+
this.schwabClient.on('tickerUpdate', (ticker) => {
|
|
194
|
+
this.emit('tickerUpdate', ticker);
|
|
195
|
+
});
|
|
196
|
+
this.schwabClient.on('optionUpdate', (option) => {
|
|
197
|
+
this.emit('optionUpdate', option);
|
|
198
|
+
});
|
|
199
|
+
this.schwabClient.on('error', (error) => {
|
|
200
|
+
this.emit('error', error);
|
|
201
|
+
});
|
|
202
|
+
this.schwabClient.on('disconnected', () => {
|
|
203
|
+
this.emit('disconnected', { broker, reason: 'Schwab WebSocket disconnected' });
|
|
204
|
+
});
|
|
205
|
+
// Connect to the streaming API
|
|
206
|
+
await this.schwabClient.connect();
|
|
207
|
+
break;
|
|
122
208
|
default:
|
|
123
209
|
throw new Error(`Unsupported broker: ${broker}`);
|
|
124
210
|
}
|
|
@@ -140,6 +226,18 @@ class FloeClient {
|
|
|
140
226
|
this.tradierClient.disconnect();
|
|
141
227
|
this.tradierClient = null;
|
|
142
228
|
}
|
|
229
|
+
if (this.tastyTradeClient) {
|
|
230
|
+
this.tastyTradeClient.disconnect();
|
|
231
|
+
this.tastyTradeClient = null;
|
|
232
|
+
}
|
|
233
|
+
if (this.tradeStationClient) {
|
|
234
|
+
this.tradeStationClient.disconnect();
|
|
235
|
+
this.tradeStationClient = null;
|
|
236
|
+
}
|
|
237
|
+
if (this.schwabClient) {
|
|
238
|
+
this.schwabClient.disconnect();
|
|
239
|
+
this.schwabClient = null;
|
|
240
|
+
}
|
|
143
241
|
const broker = this.currentBroker;
|
|
144
242
|
this.currentBroker = null;
|
|
145
243
|
this.currentSubscribedTickers = [];
|
|
@@ -170,6 +268,15 @@ class FloeClient {
|
|
|
170
268
|
case Broker.TRADIER:
|
|
171
269
|
this.tradierClient?.subscribe(tickers);
|
|
172
270
|
break;
|
|
271
|
+
case Broker.TASTYTRADE:
|
|
272
|
+
this.tastyTradeClient?.subscribe(tickers);
|
|
273
|
+
break;
|
|
274
|
+
case Broker.TRADESTATION:
|
|
275
|
+
this.tradeStationClient?.subscribe(tickers);
|
|
276
|
+
break;
|
|
277
|
+
case Broker.SCHWAB:
|
|
278
|
+
this.schwabClient?.subscribe(tickers);
|
|
279
|
+
break;
|
|
173
280
|
default:
|
|
174
281
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
175
282
|
}
|
|
@@ -204,6 +311,15 @@ class FloeClient {
|
|
|
204
311
|
case Broker.TRADIER:
|
|
205
312
|
this.tradierClient?.subscribe(symbols);
|
|
206
313
|
break;
|
|
314
|
+
case Broker.TASTYTRADE:
|
|
315
|
+
this.tastyTradeClient?.subscribe(symbols);
|
|
316
|
+
break;
|
|
317
|
+
case Broker.TRADESTATION:
|
|
318
|
+
this.tradeStationClient?.subscribe(symbols);
|
|
319
|
+
break;
|
|
320
|
+
case Broker.SCHWAB:
|
|
321
|
+
this.schwabClient?.subscribe(symbols);
|
|
322
|
+
break;
|
|
207
323
|
default:
|
|
208
324
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
209
325
|
}
|
|
@@ -230,6 +346,15 @@ class FloeClient {
|
|
|
230
346
|
case Broker.TRADIER:
|
|
231
347
|
this.tradierClient?.unsubscribe(tickers);
|
|
232
348
|
break;
|
|
349
|
+
case Broker.TASTYTRADE:
|
|
350
|
+
this.tastyTradeClient?.unsubscribe(tickers);
|
|
351
|
+
break;
|
|
352
|
+
case Broker.TRADESTATION:
|
|
353
|
+
this.tradeStationClient?.unsubscribe(tickers);
|
|
354
|
+
break;
|
|
355
|
+
case Broker.SCHWAB:
|
|
356
|
+
this.schwabClient?.unsubscribe(tickers);
|
|
357
|
+
break;
|
|
233
358
|
default:
|
|
234
359
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
235
360
|
}
|
|
@@ -256,6 +381,15 @@ class FloeClient {
|
|
|
256
381
|
case Broker.TRADIER:
|
|
257
382
|
this.tradierClient?.unsubscribe(symbols);
|
|
258
383
|
break;
|
|
384
|
+
case Broker.TASTYTRADE:
|
|
385
|
+
this.tastyTradeClient?.unsubscribe(symbols);
|
|
386
|
+
break;
|
|
387
|
+
case Broker.TRADESTATION:
|
|
388
|
+
this.tradeStationClient?.unsubscribe(symbols);
|
|
389
|
+
break;
|
|
390
|
+
case Broker.SCHWAB:
|
|
391
|
+
this.schwabClient?.unsubscribe(symbols);
|
|
392
|
+
break;
|
|
259
393
|
default:
|
|
260
394
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
261
395
|
}
|
|
@@ -300,6 +434,16 @@ class FloeClient {
|
|
|
300
434
|
case Broker.TRADIER:
|
|
301
435
|
await this.tradierClient?.fetchOpenInterest(symbolsToFetch);
|
|
302
436
|
break;
|
|
437
|
+
case Broker.TASTYTRADE:
|
|
438
|
+
await this.tastyTradeClient?.fetchOpenInterest(symbolsToFetch);
|
|
439
|
+
break;
|
|
440
|
+
case Broker.TRADESTATION:
|
|
441
|
+
// TradeStation provides open interest via stream, no separate fetch needed
|
|
442
|
+
// OI is automatically populated when streaming option chains
|
|
443
|
+
break;
|
|
444
|
+
case Broker.SCHWAB:
|
|
445
|
+
await this.schwabClient?.fetchOpenInterest(symbolsToFetch);
|
|
446
|
+
break;
|
|
303
447
|
default:
|
|
304
448
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
305
449
|
}
|
|
@@ -320,6 +464,12 @@ class FloeClient {
|
|
|
320
464
|
switch (this.currentBroker) {
|
|
321
465
|
case Broker.TRADIER:
|
|
322
466
|
return this.tradierClient?.getOption(occSymbol);
|
|
467
|
+
case Broker.TASTYTRADE:
|
|
468
|
+
return this.tastyTradeClient?.getOption(occSymbol);
|
|
469
|
+
case Broker.TRADESTATION:
|
|
470
|
+
return this.tradeStationClient?.getOption(occSymbol);
|
|
471
|
+
case Broker.SCHWAB:
|
|
472
|
+
return this.schwabClient?.getOption(occSymbol);
|
|
323
473
|
default:
|
|
324
474
|
return undefined;
|
|
325
475
|
}
|
|
@@ -341,6 +491,12 @@ class FloeClient {
|
|
|
341
491
|
switch (this.currentBroker) {
|
|
342
492
|
case Broker.TRADIER:
|
|
343
493
|
return this.tradierClient?.getAllOptions() ?? new Map();
|
|
494
|
+
case Broker.TASTYTRADE:
|
|
495
|
+
return this.tastyTradeClient?.getAllOptions() ?? new Map();
|
|
496
|
+
case Broker.TRADESTATION:
|
|
497
|
+
return this.tradeStationClient?.getAllOptions() ?? new Map();
|
|
498
|
+
case Broker.SCHWAB:
|
|
499
|
+
return this.schwabClient?.getAllOptions() ?? new Map();
|
|
344
500
|
default:
|
|
345
501
|
return new Map();
|
|
346
502
|
}
|