@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 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
- // Future brokers can be added here
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
  }