@fullstackcraftllc/floe 0.0.4 → 0.0.6
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 +15 -11
- package/dist/client/FloeClient.d.ts +25 -4
- package/dist/client/FloeClient.js +119 -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 +4 -0
- package/dist/client/brokers/TastyTradeClient.js +19 -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 +9 -3
- package/dist/client/brokers/TradierClient.js +22 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
|
-
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).
|
|
7
|
+
The same library that is used in [Full Stack Craft's](https://fullstackcraft.com) 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
|
|
|
9
9
|
## Quick Start / Documentation / Examples
|
|
10
10
|
|
|
@@ -23,12 +23,15 @@ The same library that is used in Full Stack Craft's various fintech products inc
|
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
26
|
+
- **Black-Scholes Pricing** - Fast, accurate options pricing
|
|
27
|
+
- **Greeks Calculations** - Delta, gamma, theta, vega, rho
|
|
28
|
+
- **Dealer Exposure Metrics** - GEX, VEX, and CEX exposures
|
|
29
|
+
- **Implied Volatility & Surfaces** - Calculate IV from market prices and build volatility surfaces
|
|
30
|
+
- **Implied PDF** - Risk-neutral probability density functions
|
|
31
|
+
- **Real-Time Data** - Stream normalized options data from multiple brokers
|
|
32
|
+
- **Broker-Agnostic** - Normalize data from any broker
|
|
33
|
+
- **Type-Safe** - Full TypeScript support
|
|
34
|
+
- **Zero Dependencies** - Lightweight and fast
|
|
32
35
|
|
|
33
36
|
## Broker Support Roadmap
|
|
34
37
|
|
|
@@ -36,10 +39,11 @@ Due to the overwhelming variety of how broker APIs structure their data (and how
|
|
|
36
39
|
|
|
37
40
|
| Broker | Black-Scholes | Greeks | Open Interest Based Exposures | Options-Book Based Exposures | Implied PDF Calculations |
|
|
38
41
|
|-----------------------|--------------|--------|-------------------------------|------------------------------|-------------------------|
|
|
39
|
-
| Tradier (via WebSocket) | ✅ | ✅ | ✅ | ✅ |
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
42
|
+
| Tradier (via WebSocket) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
43
|
+
| Tastytrade (via WebSocket - DXLink Streamer) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
44
|
+
| TradeStation (via HTTP Streaming) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
45
|
+
| Schwab (via WebSocket) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
46
|
+
| Interactive Brokers (via WebSocket) | Coming Soon | Coming Soon | Coming Soon | Coming Soon | Coming Soon |
|
|
43
47
|
|
|
44
48
|
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
49
|
|
|
@@ -7,7 +7,11 @@ export declare enum Broker {
|
|
|
7
7
|
/** Tradier brokerage API */
|
|
8
8
|
TRADIER = "tradier",
|
|
9
9
|
/** TastyTrade brokerage API (uses DxLink WebSocket) */
|
|
10
|
-
TASTYTRADE = "tastytrade"
|
|
10
|
+
TASTYTRADE = "tastytrade",
|
|
11
|
+
/** TradeStation brokerage API (uses HTTP streaming) */
|
|
12
|
+
TRADESTATION = "tradestation",
|
|
13
|
+
/** Charles Schwab brokerage API (uses WebSocket streaming) */
|
|
14
|
+
SCHWAB = "schwab"
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
13
17
|
* Event types emitted by the FloeClient.
|
|
@@ -75,30 +79,47 @@ export declare class FloeClient {
|
|
|
75
79
|
private tradierClient;
|
|
76
80
|
/** TastyTrade broker client instance */
|
|
77
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
|
*
|
|
100
121
|
* @param broker - The broker to connect to (e.g., Broker.TRADIER)
|
|
101
|
-
* @param
|
|
122
|
+
* @param authToken - The API authentication token for the broker
|
|
102
123
|
*
|
|
103
124
|
* @throws {Error} Throws if the specified broker is not supported
|
|
104
125
|
*
|
|
@@ -111,7 +132,7 @@ export declare class FloeClient {
|
|
|
111
132
|
* await client.connect(Broker.TRADIER, 'your-tradier-api-key');
|
|
112
133
|
* ```
|
|
113
134
|
*/
|
|
114
|
-
connect(broker: Broker,
|
|
135
|
+
connect(broker: Broker, authToken: string): Promise<void>;
|
|
115
136
|
/**
|
|
116
137
|
* Disconnects from the current broker.
|
|
117
138
|
*
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FloeClient = exports.Broker = void 0;
|
|
4
4
|
const TradierClient_1 = require("./brokers/TradierClient");
|
|
5
5
|
const TastyTradeClient_1 = require("./brokers/TastyTradeClient");
|
|
6
|
+
const TradeStationClient_1 = require("./brokers/TradeStationClient");
|
|
7
|
+
const SchwabClient_1 = require("./brokers/SchwabClient");
|
|
6
8
|
/**
|
|
7
9
|
* Supported broker integrations for the FloeClient.
|
|
8
10
|
* @enum {string}
|
|
@@ -13,6 +15,10 @@ var Broker;
|
|
|
13
15
|
Broker["TRADIER"] = "tradier";
|
|
14
16
|
/** TastyTrade brokerage API (uses DxLink WebSocket) */
|
|
15
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";
|
|
16
22
|
})(Broker || (exports.Broker = Broker = {}));
|
|
17
23
|
/**
|
|
18
24
|
* FloeClient provides a unified, broker-agnostic interface for subscribing to
|
|
@@ -47,16 +53,25 @@ class FloeClient {
|
|
|
47
53
|
/**
|
|
48
54
|
* Creates a new FloeClient instance.
|
|
49
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
|
+
*
|
|
50
61
|
* @remarks
|
|
51
62
|
* The client is created in a disconnected state. Call {@link connect} to
|
|
52
63
|
* establish a connection to a broker before subscribing to data.
|
|
53
64
|
*
|
|
54
65
|
* @example
|
|
55
66
|
* ```typescript
|
|
67
|
+
* // Create client with default settings
|
|
56
68
|
* const client = new FloeClient();
|
|
69
|
+
*
|
|
70
|
+
* // Create client with verbose logging enabled
|
|
71
|
+
* const verboseClient = new FloeClient({ verbose: true });
|
|
57
72
|
* ```
|
|
58
73
|
*/
|
|
59
|
-
constructor() {
|
|
74
|
+
constructor(options) {
|
|
60
75
|
/** Currently connected broker, or null if not connected */
|
|
61
76
|
this.currentBroker = null;
|
|
62
77
|
/** List of ticker symbols currently subscribed to */
|
|
@@ -67,12 +82,17 @@ class FloeClient {
|
|
|
67
82
|
this.tradierClient = null;
|
|
68
83
|
/** TastyTrade broker client instance */
|
|
69
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());
|
|
@@ -84,7 +104,7 @@ class FloeClient {
|
|
|
84
104
|
* Establishes a connection to a broker's API.
|
|
85
105
|
*
|
|
86
106
|
* @param broker - The broker to connect to (e.g., Broker.TRADIER)
|
|
87
|
-
* @param
|
|
107
|
+
* @param authToken - The API authentication token for the broker
|
|
88
108
|
*
|
|
89
109
|
* @throws {Error} Throws if the specified broker is not supported
|
|
90
110
|
*
|
|
@@ -97,12 +117,12 @@ class FloeClient {
|
|
|
97
117
|
* await client.connect(Broker.TRADIER, 'your-tradier-api-key');
|
|
98
118
|
* ```
|
|
99
119
|
*/
|
|
100
|
-
async connect(broker,
|
|
120
|
+
async connect(broker, authToken) {
|
|
101
121
|
this.currentBroker = broker;
|
|
102
|
-
// Connection logic to the broker's API using the
|
|
122
|
+
// Connection logic to the broker's API using the authToken
|
|
103
123
|
switch (broker.toLowerCase()) {
|
|
104
124
|
case Broker.TRADIER:
|
|
105
|
-
this.tradierClient = new TradierClient_1.TradierClient(
|
|
125
|
+
this.tradierClient = new TradierClient_1.TradierClient(authToken, { 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);
|
|
@@ -120,9 +140,10 @@ class FloeClient {
|
|
|
120
140
|
await this.tradierClient.connect();
|
|
121
141
|
break;
|
|
122
142
|
case Broker.TASTYTRADE:
|
|
123
|
-
// For TastyTrade,
|
|
143
|
+
// For TastyTrade, authToken is the session token
|
|
124
144
|
this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
|
|
125
|
-
sessionToken:
|
|
145
|
+
sessionToken: authToken,
|
|
146
|
+
verbose: this.verbose,
|
|
126
147
|
});
|
|
127
148
|
// Wire up TastyTradeClient events to FloeClient events
|
|
128
149
|
this.tastyTradeClient.on('tickerUpdate', (ticker) => {
|
|
@@ -140,6 +161,50 @@ class FloeClient {
|
|
|
140
161
|
// Connect to the streaming API
|
|
141
162
|
await this.tastyTradeClient.connect();
|
|
142
163
|
break;
|
|
164
|
+
case Broker.TRADESTATION:
|
|
165
|
+
// For TradeStation, authToken is the OAuth access token
|
|
166
|
+
this.tradeStationClient = new TradeStationClient_1.TradeStationClient({
|
|
167
|
+
accessToken: authToken,
|
|
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, authToken is the OAuth access token
|
|
188
|
+
this.schwabClient = new SchwabClient_1.SchwabClient({
|
|
189
|
+
accessToken: authToken,
|
|
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;
|
|
143
208
|
default:
|
|
144
209
|
throw new Error(`Unsupported broker: ${broker}`);
|
|
145
210
|
}
|
|
@@ -165,6 +230,14 @@ class FloeClient {
|
|
|
165
230
|
this.tastyTradeClient.disconnect();
|
|
166
231
|
this.tastyTradeClient = null;
|
|
167
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
|
+
}
|
|
168
241
|
const broker = this.currentBroker;
|
|
169
242
|
this.currentBroker = null;
|
|
170
243
|
this.currentSubscribedTickers = [];
|
|
@@ -198,6 +271,12 @@ class FloeClient {
|
|
|
198
271
|
case Broker.TASTYTRADE:
|
|
199
272
|
this.tastyTradeClient?.subscribe(tickers);
|
|
200
273
|
break;
|
|
274
|
+
case Broker.TRADESTATION:
|
|
275
|
+
this.tradeStationClient?.subscribe(tickers);
|
|
276
|
+
break;
|
|
277
|
+
case Broker.SCHWAB:
|
|
278
|
+
this.schwabClient?.subscribe(tickers);
|
|
279
|
+
break;
|
|
201
280
|
default:
|
|
202
281
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
203
282
|
}
|
|
@@ -235,6 +314,12 @@ class FloeClient {
|
|
|
235
314
|
case Broker.TASTYTRADE:
|
|
236
315
|
this.tastyTradeClient?.subscribe(symbols);
|
|
237
316
|
break;
|
|
317
|
+
case Broker.TRADESTATION:
|
|
318
|
+
this.tradeStationClient?.subscribe(symbols);
|
|
319
|
+
break;
|
|
320
|
+
case Broker.SCHWAB:
|
|
321
|
+
this.schwabClient?.subscribe(symbols);
|
|
322
|
+
break;
|
|
238
323
|
default:
|
|
239
324
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
240
325
|
}
|
|
@@ -264,6 +349,12 @@ class FloeClient {
|
|
|
264
349
|
case Broker.TASTYTRADE:
|
|
265
350
|
this.tastyTradeClient?.unsubscribe(tickers);
|
|
266
351
|
break;
|
|
352
|
+
case Broker.TRADESTATION:
|
|
353
|
+
this.tradeStationClient?.unsubscribe(tickers);
|
|
354
|
+
break;
|
|
355
|
+
case Broker.SCHWAB:
|
|
356
|
+
this.schwabClient?.unsubscribe(tickers);
|
|
357
|
+
break;
|
|
267
358
|
default:
|
|
268
359
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
269
360
|
}
|
|
@@ -293,6 +384,12 @@ class FloeClient {
|
|
|
293
384
|
case Broker.TASTYTRADE:
|
|
294
385
|
this.tastyTradeClient?.unsubscribe(symbols);
|
|
295
386
|
break;
|
|
387
|
+
case Broker.TRADESTATION:
|
|
388
|
+
this.tradeStationClient?.unsubscribe(symbols);
|
|
389
|
+
break;
|
|
390
|
+
case Broker.SCHWAB:
|
|
391
|
+
this.schwabClient?.unsubscribe(symbols);
|
|
392
|
+
break;
|
|
296
393
|
default:
|
|
297
394
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
298
395
|
}
|
|
@@ -340,6 +437,13 @@ class FloeClient {
|
|
|
340
437
|
case Broker.TASTYTRADE:
|
|
341
438
|
await this.tastyTradeClient?.fetchOpenInterest(symbolsToFetch);
|
|
342
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;
|
|
343
447
|
default:
|
|
344
448
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
345
449
|
}
|
|
@@ -362,6 +466,10 @@ class FloeClient {
|
|
|
362
466
|
return this.tradierClient?.getOption(occSymbol);
|
|
363
467
|
case Broker.TASTYTRADE:
|
|
364
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);
|
|
365
473
|
default:
|
|
366
474
|
return undefined;
|
|
367
475
|
}
|
|
@@ -385,6 +493,10 @@ class FloeClient {
|
|
|
385
493
|
return this.tradierClient?.getAllOptions() ?? new Map();
|
|
386
494
|
case Broker.TASTYTRADE:
|
|
387
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();
|
|
388
500
|
default:
|
|
389
501
|
return new Map();
|
|
390
502
|
}
|