@fullstackcraftllc/floe 0.0.5 → 0.0.7

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
 
@@ -119,7 +119,7 @@ export declare class FloeClient {
119
119
  * Establishes a connection to a broker's API.
120
120
  *
121
121
  * @param broker - The broker to connect to (e.g., Broker.TRADIER)
122
- * @param authKey - The API authentication key or token for the broker
122
+ * @param authToken - The API authentication token for the broker
123
123
  *
124
124
  * @throws {Error} Throws if the specified broker is not supported
125
125
  *
@@ -132,7 +132,7 @@ export declare class FloeClient {
132
132
  * await client.connect(Broker.TRADIER, 'your-tradier-api-key');
133
133
  * ```
134
134
  */
135
- connect(broker: Broker, authKey: string): Promise<void>;
135
+ connect(broker: Broker, authToken: string): Promise<void>;
136
136
  /**
137
137
  * Disconnects from the current broker.
138
138
  *
@@ -221,6 +221,21 @@ export declare class FloeClient {
221
221
  * ```
222
222
  */
223
223
  unsubscribeFromOptions(symbols: Array<string>): void;
224
+ /**
225
+ * Unsubscribes from all currently subscribed tickers and options.
226
+ *
227
+ * @throws {Error} Throws if no broker connection has been established
228
+ *
229
+ * @remarks
230
+ * After calling this method, no further updates will be received for any
231
+ * previously subscribed tickers or options.
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * client.unsubscribeFromAll();
236
+ * ```
237
+ */
238
+ unsubscribeFromAll(): void;
224
239
  /**
225
240
  * Fetches open interest and initial option data via REST API.
226
241
  *
@@ -104,7 +104,7 @@ class FloeClient {
104
104
  * Establishes a connection to a broker's API.
105
105
  *
106
106
  * @param broker - The broker to connect to (e.g., Broker.TRADIER)
107
- * @param authKey - The API authentication key or token for the broker
107
+ * @param authToken - The API authentication token for the broker
108
108
  *
109
109
  * @throws {Error} Throws if the specified broker is not supported
110
110
  *
@@ -117,12 +117,12 @@ class FloeClient {
117
117
  * await client.connect(Broker.TRADIER, 'your-tradier-api-key');
118
118
  * ```
119
119
  */
120
- async connect(broker, authKey) {
120
+ async connect(broker, authToken) {
121
121
  this.currentBroker = broker;
122
- // Connection logic to the broker's API using the authKey
122
+ // Connection logic to the broker's API using the authToken
123
123
  switch (broker.toLowerCase()) {
124
124
  case Broker.TRADIER:
125
- this.tradierClient = new TradierClient_1.TradierClient(authKey, { verbose: this.verbose });
125
+ this.tradierClient = new TradierClient_1.TradierClient(authToken, { verbose: this.verbose });
126
126
  // Wire up TradierClient events to FloeClient events
127
127
  this.tradierClient.on('tickerUpdate', (ticker) => {
128
128
  this.emit('tickerUpdate', ticker);
@@ -140,9 +140,9 @@ class FloeClient {
140
140
  await this.tradierClient.connect();
141
141
  break;
142
142
  case Broker.TASTYTRADE:
143
- // For TastyTrade, authKey is the session token
143
+ // For TastyTrade, authToken is the session token
144
144
  this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
145
- sessionToken: authKey,
145
+ sessionToken: authToken,
146
146
  verbose: this.verbose,
147
147
  });
148
148
  // Wire up TastyTradeClient events to FloeClient events
@@ -162,9 +162,9 @@ class FloeClient {
162
162
  await this.tastyTradeClient.connect();
163
163
  break;
164
164
  case Broker.TRADESTATION:
165
- // For TradeStation, authKey is the OAuth access token
165
+ // For TradeStation, authToken is the OAuth access token
166
166
  this.tradeStationClient = new TradeStationClient_1.TradeStationClient({
167
- accessToken: authKey,
167
+ accessToken: authToken,
168
168
  verbose: this.verbose,
169
169
  });
170
170
  // Wire up TradeStationClient events to FloeClient events
@@ -184,9 +184,9 @@ class FloeClient {
184
184
  await this.tradeStationClient.connect();
185
185
  break;
186
186
  case Broker.SCHWAB:
187
- // For Schwab, authKey is the OAuth access token
187
+ // For Schwab, authToken is the OAuth access token
188
188
  this.schwabClient = new SchwabClient_1.SchwabClient({
189
- accessToken: authKey,
189
+ accessToken: authToken,
190
190
  verbose: this.verbose,
191
191
  });
192
192
  // Wire up SchwabClient events to FloeClient events
@@ -394,6 +394,40 @@ class FloeClient {
394
394
  throw new Error(`Unsupported broker: ${this.currentBroker}`);
395
395
  }
396
396
  }
397
+ /**
398
+ * Unsubscribes from all currently subscribed tickers and options.
399
+ *
400
+ * @throws {Error} Throws if no broker connection has been established
401
+ *
402
+ * @remarks
403
+ * After calling this method, no further updates will be received for any
404
+ * previously subscribed tickers or options.
405
+ *
406
+ * @example
407
+ * ```typescript
408
+ * client.unsubscribeFromAll();
409
+ * ```
410
+ */
411
+ unsubscribeFromAll() {
412
+ this.currentSubscribedTickers = [];
413
+ this.currentSubscribedOptions = [];
414
+ switch (this.currentBroker) {
415
+ case Broker.TRADIER:
416
+ this.tradierClient?.unsubscribeFromAll();
417
+ break;
418
+ case Broker.TASTYTRADE:
419
+ this.tastyTradeClient?.unsubscribeFromAll();
420
+ break;
421
+ case Broker.TRADESTATION:
422
+ this.tradeStationClient?.unsubscribeFromAll();
423
+ break;
424
+ case Broker.SCHWAB:
425
+ this.schwabClient?.unsubscribeFromAll();
426
+ break;
427
+ default:
428
+ throw new Error(`Unsupported broker: ${this.currentBroker}`);
429
+ }
430
+ }
397
431
  /**
398
432
  * Fetches open interest and initial option data via REST API.
399
433
  *
@@ -251,6 +251,10 @@ export declare class SchwabClient {
251
251
  * @param symbols - Array of symbols to unsubscribe from
252
252
  */
253
253
  unsubscribe(symbols: string[]): void;
254
+ /**
255
+ * Unsubscribes from all real-time updates.
256
+ */
257
+ unsubscribeFromAll(): void;
254
258
  /**
255
259
  * Returns whether the client is currently connected.
256
260
  */
@@ -276,6 +276,31 @@ class SchwabClient {
276
276
  this.unsubscribeOptionsBook(options);
277
277
  }
278
278
  }
279
+ /**
280
+ * Unsubscribes from all real-time updates.
281
+ */
282
+ unsubscribeFromAll() {
283
+ const allSymbols = Array.from(this.subscribedSymbols);
284
+ const allOptionSymbols = allSymbols.filter(s => this.isOptionSymbol(s)).map(s => this.toSchwabOptionSymbol(s));
285
+ this.subscribedSymbols.clear();
286
+ // unsub from all equities
287
+ if (allSymbols.length > 0) {
288
+ const request = this.makeRequest('LEVELONE_EQUITIES', 'UNSUBS', {
289
+ keys: allSymbols.join(','),
290
+ });
291
+ this.sendMessage({ requests: [request] });
292
+ }
293
+ // unsub from all options (quotes and book)
294
+ if (allOptionSymbols.length > 0) {
295
+ const requestOptions = this.makeRequest('LEVELONE_OPTIONS', 'UNSUBS', {
296
+ keys: allOptionSymbols.join(','),
297
+ });
298
+ const requestBook = this.makeRequest('OPTIONS_BOOK', 'UNSUBS', {
299
+ keys: allOptionSymbols.join(','),
300
+ });
301
+ this.sendMessage({ requests: [requestOptions, requestBook] });
302
+ }
303
+ }
279
304
  /**
280
305
  * Returns whether the client is currently connected.
281
306
  */
@@ -201,6 +201,10 @@ export declare class TastyTradeClient {
201
201
  * @param symbols - Array of symbols to unsubscribe from
202
202
  */
203
203
  unsubscribe(symbols: string[]): void;
204
+ /**
205
+ * Unsubscribes from all real-time updates.
206
+ */
207
+ unsubscribeFromAll(): void;
204
208
  /**
205
209
  * Returns whether the client is currently connected.
206
210
  */
@@ -226,6 +226,17 @@ class TastyTradeClient {
226
226
  }
227
227
  this.sendFeedSubscription(symbols, 'remove');
228
228
  }
229
+ /**
230
+ * Unsubscribes from all real-time updates.
231
+ */
232
+ unsubscribeFromAll() {
233
+ const symbols = Array.from(this.subscribedSymbols);
234
+ this.subscribedSymbols.clear();
235
+ if (!this.connected || !this.feedChannelOpened) {
236
+ return;
237
+ }
238
+ this.sendFeedSubscription(symbols, 'remove');
239
+ }
229
240
  /**
230
241
  * Returns whether the client is currently connected.
231
242
  */
@@ -177,6 +177,10 @@ export declare class TradeStationClient {
177
177
  * restarting with the remaining symbols.
178
178
  */
179
179
  unsubscribe(symbols: string[]): void;
180
+ /**
181
+ * Unsubscribes from all real-time updates.
182
+ */
183
+ unsubscribeFromAll(): void;
180
184
  /**
181
185
  * Returns whether the client is currently connected.
182
186
  */
@@ -217,6 +217,15 @@ class TradeStationClient {
217
217
  this.stopStream('options');
218
218
  }
219
219
  }
220
+ /**
221
+ * Unsubscribes from all real-time updates.
222
+ */
223
+ unsubscribeFromAll() {
224
+ this.subscribedTickers.clear();
225
+ this.subscribedOptions.clear();
226
+ this.stopStream('quotes');
227
+ this.stopStream('options');
228
+ }
220
229
  /**
221
230
  * Returns whether the client is currently connected.
222
231
  */
@@ -107,7 +107,7 @@ type TradierEventListener<T> = (data: T) => void;
107
107
  */
108
108
  export declare class TradierClient {
109
109
  /** Tradier API authentication token */
110
- private authKey;
110
+ private authToken;
111
111
  /** Current streaming session */
112
112
  private streamSession;
113
113
  /** WebSocket connection */
@@ -152,11 +152,11 @@ export declare class TradierClient {
152
152
  /**
153
153
  * Creates a new TradierClient instance.
154
154
  *
155
- * @param authKey - Tradier API access token
155
+ * @param authToken - Tradier API auth token
156
156
  * @param options - Optional configuration options
157
157
  * @param options.verbose - Whether to log verbose debug information (default: false)
158
158
  */
159
- constructor(authKey: string, options?: {
159
+ constructor(authToken: string, options?: {
160
160
  verbose?: boolean;
161
161
  });
162
162
  /**
@@ -182,11 +182,18 @@ export declare class TradierClient {
182
182
  * @param symbols - Array of symbols to unsubscribe from
183
183
  *
184
184
  * @remarks
185
- * Note: Tradier's streaming API doesn't support unsubscription for individual
186
- * symbols. This method removes them from local tracking. To fully unsubscribe,
187
- * you would need to disconnect and reconnect with the new symbol list.
185
+ * Since Tradier's streaming API doesn't support selective unsubscription,
186
+ * this method disconnects and reconnects with the updated symbol list.
188
187
  */
189
- unsubscribe(symbols: string[]): void;
188
+ unsubscribe(symbols: string[]): Promise<void>;
189
+ /**
190
+ * Unsubscribes from all symbols.
191
+ *
192
+ * @remarks
193
+ * Since Tradier's streaming API doesn't support selective unsubscription,
194
+ * this method disconnects and reconnects without any symbols.
195
+ */
196
+ unsubscribeFromAll(): Promise<void>;
190
197
  /**
191
198
  * Returns whether the client is currently connected.
192
199
  */
@@ -251,6 +258,11 @@ export declare class TradierClient {
251
258
  * Attempts to reconnect with exponential backoff.
252
259
  */
253
260
  private attemptReconnect;
261
+ /**
262
+ * Reconnects with the current symbol list.
263
+ * Used for unsubscribe operations since Tradier doesn't support selective unsubscription.
264
+ */
265
+ private reconnectWithSymbols;
254
266
  /**
255
267
  * Handles incoming WebSocket messages.
256
268
  */
@@ -33,11 +33,11 @@ class TradierClient {
33
33
  /**
34
34
  * Creates a new TradierClient instance.
35
35
  *
36
- * @param authKey - Tradier API access token
36
+ * @param authToken - Tradier API auth token
37
37
  * @param options - Optional configuration options
38
38
  * @param options.verbose - Whether to log verbose debug information (default: false)
39
39
  */
40
- constructor(authKey, options) {
40
+ constructor(authToken, options) {
41
41
  /** Current streaming session */
42
42
  this.streamSession = null;
43
43
  /** WebSocket connection */
@@ -77,7 +77,7 @@ class TradierClient {
77
77
  this.apiBaseUrl = 'https://api.tradier.com/v1';
78
78
  /** Tradier WebSocket URL */
79
79
  this.wsUrl = 'wss://ws.tradier.com/v1/markets/events';
80
- this.authKey = authKey;
80
+ this.authToken = authToken;
81
81
  this.verbose = options?.verbose ?? false;
82
82
  // Initialize event listener maps
83
83
  this.eventListeners.set('tickerUpdate', new Set());
@@ -142,14 +142,29 @@ class TradierClient {
142
142
  * @param symbols - Array of symbols to unsubscribe from
143
143
  *
144
144
  * @remarks
145
- * Note: Tradier's streaming API doesn't support unsubscription for individual
146
- * symbols. This method removes them from local tracking. To fully unsubscribe,
147
- * you would need to disconnect and reconnect with the new symbol list.
145
+ * Since Tradier's streaming API doesn't support selective unsubscription,
146
+ * this method disconnects and reconnects with the updated symbol list.
148
147
  */
149
- unsubscribe(symbols) {
148
+ async unsubscribe(symbols) {
150
149
  symbols.forEach(s => this.subscribedSymbols.delete(s));
151
- // Note: Tradier doesn't support selective unsubscribe
152
- // Would need to reconnect with new symbol list for full effect
150
+ // If connected, reconnect with the new symbol list
151
+ if (this.connected) {
152
+ await this.reconnectWithSymbols();
153
+ }
154
+ }
155
+ /**
156
+ * Unsubscribes from all symbols.
157
+ *
158
+ * @remarks
159
+ * Since Tradier's streaming API doesn't support selective unsubscription,
160
+ * this method disconnects and reconnects without any symbols.
161
+ */
162
+ async unsubscribeFromAll() {
163
+ this.subscribedSymbols.clear();
164
+ // If connected, reconnect with empty symbol list
165
+ if (this.connected) {
166
+ await this.reconnectWithSymbols();
167
+ }
153
168
  }
154
169
  /**
155
170
  * Returns whether the client is currently connected.
@@ -176,7 +191,7 @@ class TradierClient {
176
191
  const response = await fetch(url, {
177
192
  method: 'GET',
178
193
  headers: {
179
- 'Authorization': `Bearer ${this.authKey}`,
194
+ 'Authorization': `Bearer ${this.authToken}`,
180
195
  'Accept': 'application/json',
181
196
  },
182
197
  });
@@ -354,7 +369,7 @@ class TradierClient {
354
369
  const response = await fetch(`${this.apiBaseUrl}/markets/events/session`, {
355
370
  method: 'POST',
356
371
  headers: {
357
- 'Authorization': `Bearer ${this.authKey}`,
372
+ 'Authorization': `Bearer ${this.authToken}`,
358
373
  'Accept': 'application/json',
359
374
  },
360
375
  });
@@ -431,6 +446,21 @@ class TradierClient {
431
446
  // Reconnect attempt failed, will try again via onclose
432
447
  }
433
448
  }
449
+ /**
450
+ * Reconnects with the current symbol list.
451
+ * Used for unsubscribe operations since Tradier doesn't support selective unsubscription.
452
+ */
453
+ async reconnectWithSymbols() {
454
+ if (this.verbose) {
455
+ console.log(`[Tradier:WS] Reconnecting with ${this.subscribedSymbols.size} symbols`);
456
+ }
457
+ // Disconnect cleanly
458
+ this.disconnect();
459
+ // Wait briefly to ensure clean disconnect
460
+ await this.sleep(100);
461
+ // Reconnect with current symbol list
462
+ await this.connect();
463
+ }
434
464
  /**
435
465
  * Handles incoming WebSocket messages.
436
466
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullstackcraftllc/floe",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Production-ready options analytics toolkit. Normalize broker data structures and calculate Black-Scholes, Greeks, and exposures with a clean, type-safe API. Built for trading platforms and fintech applications.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",