@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 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
- - 🎯 **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
- - 🔌 **Broker-Agnostic** - Normalize data from any broker
30
- - 💪 **Type-Safe** - Full TypeScript support
31
- - **Zero Dependencies** - Lightweight and fast
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) | ✅ | ✅ | ✅ | ✅ | 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 |
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 authKey - The API authentication key or token for the broker
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, authKey: string): Promise<void>;
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 authKey - The API authentication key or token for the broker
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, authKey) {
120
+ async connect(broker, authToken) {
101
121
  this.currentBroker = broker;
102
- // Connection logic to the broker's API using the authKey
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(authKey);
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, authKey is the session token
143
+ // For TastyTrade, authToken is the session token
124
144
  this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
125
- sessionToken: authKey,
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
  }