@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 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) | ✅ | ✅ | ✅ | ✅ | 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 |
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
  }