@fullstackcraftllc/floe 0.0.4 → 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 +5 -4
- package/dist/client/FloeClient.d.ts +23 -2
- package/dist/client/FloeClient.js +114 -2
- 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 +7 -1
- package/dist/client/brokers/TradierClient.js +18 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,10 +36,11 @@ Due to the overwhelming variety of how broker APIs structure their data (and how
|
|
|
36
36
|
|
|
37
37
|
| Broker | Black-Scholes | Greeks | Open Interest Based Exposures | Options-Book Based Exposures | Implied PDF Calculations |
|
|
38
38
|
|-----------------------|--------------|--------|-------------------------------|------------------------------|-------------------------|
|
|
39
|
-
| Tradier (via WebSocket) | ✅ | ✅ | ✅ | ✅ |
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
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 |
|
|
43
44
|
|
|
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.
|
|
45
46
|
|
|
@@ -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,25 +79,42 @@ 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
|
*
|
|
@@ -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());
|
|
@@ -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);
|
|
@@ -123,6 +143,7 @@ class FloeClient {
|
|
|
123
143
|
// For TastyTrade, authKey is the session token
|
|
124
144
|
this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
|
|
125
145
|
sessionToken: authKey,
|
|
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, 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;
|
|
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
|
}
|