@fullstackcraftllc/floe 0.0.1 → 0.0.3

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.
@@ -0,0 +1,550 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FloeClient = exports.Broker = void 0;
4
+ const TradierClient_1 = require("./brokers/TradierClient");
5
+ /**
6
+ * Supported broker integrations for the FloeClient.
7
+ * @enum {string}
8
+ */
9
+ var Broker;
10
+ (function (Broker) {
11
+ /** Tradier brokerage API */
12
+ Broker["TRADIER"] = "tradier";
13
+ // Future brokers can be added here
14
+ })(Broker || (exports.Broker = Broker = {}));
15
+ /**
16
+ * FloeClient provides a unified, broker-agnostic interface for subscribing to
17
+ * real-time market data including stock tickers and options.
18
+ *
19
+ * @remarks
20
+ * The client normalizes data from various brokers into a consistent format,
21
+ * allowing consumers to switch brokers without changing their application code.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const client = new FloeClient();
26
+ *
27
+ * // Connect to a broker
28
+ * client.connect(Broker.TRADIER, 'your-api-key');
29
+ *
30
+ * // Subscribe to updates using the event emitter pattern
31
+ * client.on('tickerUpdate', (ticker) => {
32
+ * console.log(`${ticker.symbol}: ${ticker.price}`);
33
+ * });
34
+ *
35
+ * // Or use the callback pattern
36
+ * client.onTickerDataChange((ticker) => {
37
+ * console.log(`${ticker.symbol}: ${ticker.price}`);
38
+ * });
39
+ *
40
+ * // Subscribe to specific tickers
41
+ * client.subscribeToTickers(['AAPL', 'GOOGL', 'MSFT']);
42
+ * ```
43
+ */
44
+ class FloeClient {
45
+ /**
46
+ * Creates a new FloeClient instance.
47
+ *
48
+ * @remarks
49
+ * The client is created in a disconnected state. Call {@link connect} to
50
+ * establish a connection to a broker before subscribing to data.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const client = new FloeClient();
55
+ * ```
56
+ */
57
+ constructor() {
58
+ /** Currently connected broker, or null if not connected */
59
+ this.currentBroker = null;
60
+ /** List of ticker symbols currently subscribed to */
61
+ this.currentSubscribedTickers = [];
62
+ /** List of option symbols (OCC format) currently subscribed to */
63
+ 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
+ /** Tradier broker client instance */
69
+ this.tradierClient = null;
70
+ /** Event listeners registry for the EventEmitter pattern */
71
+ this.eventListeners = new Map();
72
+ /** Callback for ticker data changes (legacy callback pattern) */
73
+ this.tickerDataCallback = null;
74
+ /** Callback for option data changes (legacy callback pattern) */
75
+ this.optionDataCallback = null;
76
+ // Initialize event listener maps for each event type
77
+ this.eventListeners.set('tickerUpdate', new Set());
78
+ this.eventListeners.set('optionUpdate', new Set());
79
+ this.eventListeners.set('error', new Set());
80
+ this.eventListeners.set('connected', new Set());
81
+ this.eventListeners.set('disconnected', new Set());
82
+ }
83
+ /**
84
+ * Establishes a connection to a broker's API.
85
+ *
86
+ * @param broker - The broker to connect to (e.g., Broker.TRADIER)
87
+ * @param authKey - The API authentication key or token for the broker
88
+ *
89
+ * @throws {Error} Throws if the specified broker is not supported
90
+ *
91
+ * @remarks
92
+ * Must be called before subscribing to any market data. Only one broker
93
+ * connection is active at a time; calling connect again will switch brokers.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * await client.connect(Broker.TRADIER, 'your-tradier-api-key');
98
+ * ```
99
+ */
100
+ async connect(broker, authKey) {
101
+ this.currentBroker = broker;
102
+ // Connection logic to the broker's API using the authKey
103
+ switch (broker.toLowerCase()) {
104
+ case Broker.TRADIER:
105
+ this.tradierClient = new TradierClient_1.TradierClient(authKey);
106
+ // Wire up TradierClient events to FloeClient events
107
+ this.tradierClient.on('tickerUpdate', (ticker) => {
108
+ this.emit('tickerUpdate', ticker);
109
+ });
110
+ this.tradierClient.on('optionUpdate', (option) => {
111
+ this.emit('optionUpdate', option);
112
+ });
113
+ this.tradierClient.on('error', (error) => {
114
+ this.emit('error', error);
115
+ });
116
+ this.tradierClient.on('disconnected', () => {
117
+ this.emit('disconnected', { broker, reason: 'WebSocket disconnected' });
118
+ });
119
+ // Connect to the streaming API
120
+ await this.tradierClient.connect();
121
+ break;
122
+ default:
123
+ throw new Error(`Unsupported broker: ${broker}`);
124
+ }
125
+ this.emit('connected', { broker });
126
+ }
127
+ /**
128
+ * Disconnects from the current broker.
129
+ *
130
+ * @remarks
131
+ * Closes the WebSocket connection and clears all subscriptions.
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * client.disconnect();
136
+ * ```
137
+ */
138
+ disconnect() {
139
+ if (this.tradierClient) {
140
+ this.tradierClient.disconnect();
141
+ this.tradierClient = null;
142
+ }
143
+ const broker = this.currentBroker;
144
+ this.currentBroker = null;
145
+ this.currentSubscribedTickers = [];
146
+ this.currentSubscribedOptions = [];
147
+ if (broker) {
148
+ this.emit('disconnected', { broker, reason: 'Client disconnect' });
149
+ }
150
+ }
151
+ /**
152
+ * Subscribes to real-time updates for the specified stock ticker symbols.
153
+ *
154
+ * @param tickers - Array of ticker symbols to subscribe to (e.g., ['AAPL', 'GOOGL'])
155
+ *
156
+ * @throws {Error} Throws if no broker connection has been established
157
+ *
158
+ * @remarks
159
+ * Ticker updates will be delivered via the 'tickerUpdate' event or through
160
+ * the callback registered with {@link onTickerDataChange}.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * client.subscribeToTickers(['AAPL', 'GOOGL', 'MSFT']);
165
+ * ```
166
+ */
167
+ subscribeToTickers(tickers) {
168
+ this.currentSubscribedTickers.push(...tickers);
169
+ switch (this.currentBroker) {
170
+ case Broker.TRADIER:
171
+ this.tradierClient?.subscribe(tickers);
172
+ break;
173
+ default:
174
+ throw new Error(`Unsupported broker: ${this.currentBroker}`);
175
+ }
176
+ }
177
+ /**
178
+ * Subscribes to real-time updates for the specified option contracts.
179
+ *
180
+ * @param symbols - Array of option symbols in OCC format
181
+ * (e.g., ['AAPL230120C00150000'] for AAPL $150 Call expiring Jan 20, 2023)
182
+ *
183
+ * @throws {Error} Throws if no broker connection has been established
184
+ *
185
+ * @remarks
186
+ * Option symbols must be in the standard OCC (Options Clearing Corporation) format:
187
+ * - Root symbol (up to 6 characters, left-padded)
188
+ * - Expiration date (YYMMDD)
189
+ * - Option type (C for call, P for put)
190
+ * - Strike price (8 digits, price × 1000, left-padded with zeros)
191
+ *
192
+ * Option updates will be delivered via the 'optionUpdate' event or through
193
+ * the callback registered with {@link onOptionDataChange}.
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * // Subscribe to AAPL $150 Call expiring Jan 20, 2023
198
+ * client.subscribeToOptions(['AAPL230120C00150000']);
199
+ * ```
200
+ */
201
+ subscribeToOptions(symbols) {
202
+ this.currentSubscribedOptions.push(...symbols);
203
+ switch (this.currentBroker) {
204
+ case Broker.TRADIER:
205
+ this.tradierClient?.subscribe(symbols);
206
+ break;
207
+ default:
208
+ throw new Error(`Unsupported broker: ${this.currentBroker}`);
209
+ }
210
+ }
211
+ /**
212
+ * Unsubscribes from real-time updates for the specified stock ticker symbols.
213
+ *
214
+ * @param tickers - Array of ticker symbols to unsubscribe from
215
+ *
216
+ * @throws {Error} Throws if no broker connection has been established
217
+ *
218
+ * @remarks
219
+ * After unsubscribing, no further updates will be received for these tickers.
220
+ * Has no effect if the specified tickers were not previously subscribed.
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * client.unsubscribeFromTickers(['AAPL', 'GOOGL']);
225
+ * ```
226
+ */
227
+ unsubscribeFromTickers(tickers) {
228
+ this.currentSubscribedTickers = this.currentSubscribedTickers.filter(ticker => !tickers.includes(ticker));
229
+ switch (this.currentBroker) {
230
+ case Broker.TRADIER:
231
+ this.tradierClient?.unsubscribe(tickers);
232
+ break;
233
+ default:
234
+ throw new Error(`Unsupported broker: ${this.currentBroker}`);
235
+ }
236
+ }
237
+ /**
238
+ * Unsubscribes from real-time updates for the specified option contracts.
239
+ *
240
+ * @param symbols - Array of option symbols in OCC format to unsubscribe from
241
+ *
242
+ * @throws {Error} Throws if no broker connection has been established
243
+ *
244
+ * @remarks
245
+ * After unsubscribing, no further updates will be received for these options.
246
+ * Has no effect if the specified options were not previously subscribed.
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * client.unsubscribeFromOptions(['AAPL230120C00150000']);
251
+ * ```
252
+ */
253
+ unsubscribeFromOptions(symbols) {
254
+ this.currentSubscribedOptions = this.currentSubscribedOptions.filter(symbol => !symbols.includes(symbol));
255
+ switch (this.currentBroker) {
256
+ case Broker.TRADIER:
257
+ this.tradierClient?.unsubscribe(symbols);
258
+ break;
259
+ default:
260
+ throw new Error(`Unsupported broker: ${this.currentBroker}`);
261
+ }
262
+ }
263
+ /**
264
+ * Fetches open interest and initial option data via REST API.
265
+ *
266
+ * @param symbols - Array of option symbols in OCC format to fetch data for.
267
+ * If not provided, fetches data for all currently subscribed options.
268
+ * @returns Promise that resolves when all data has been fetched
269
+ *
270
+ * @throws {Error} Throws if no broker connection has been established
271
+ *
272
+ * @remarks
273
+ * Open interest is not available via streaming and must be fetched via REST API.
274
+ * This method should be called after subscribing to options to populate
275
+ * open interest, volume, and initial bid/ask values.
276
+ *
277
+ * The fetched data is automatically merged into the option cache and
278
+ * emitted via 'optionUpdate' events.
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * // Subscribe to options
283
+ * client.subscribeToOptions(optionSymbols);
284
+ *
285
+ * // Fetch open interest data
286
+ * await client.fetchOpenInterest();
287
+ *
288
+ * // Options now have open interest populated
289
+ * client.on('optionUpdate', (option) => {
290
+ * console.log(`${option.occSymbol}: OI=${option.openInterest}`);
291
+ * });
292
+ * ```
293
+ */
294
+ async fetchOpenInterest(symbols) {
295
+ const symbolsToFetch = symbols ?? this.currentSubscribedOptions;
296
+ if (symbolsToFetch.length === 0) {
297
+ return;
298
+ }
299
+ switch (this.currentBroker) {
300
+ case Broker.TRADIER:
301
+ await this.tradierClient?.fetchOpenInterest(symbolsToFetch);
302
+ break;
303
+ default:
304
+ throw new Error(`Unsupported broker: ${this.currentBroker}`);
305
+ }
306
+ }
307
+ /**
308
+ * Returns cached option data for a specific symbol.
309
+ *
310
+ * @param occSymbol - OCC option symbol
311
+ * @returns Cached option data, or undefined if not found
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * const option = client.getOption('QQQ250117C00530000');
316
+ * console.log(`Open Interest: ${option?.openInterest}`);
317
+ * ```
318
+ */
319
+ getOption(occSymbol) {
320
+ switch (this.currentBroker) {
321
+ case Broker.TRADIER:
322
+ return this.tradierClient?.getOption(occSymbol);
323
+ default:
324
+ return undefined;
325
+ }
326
+ }
327
+ /**
328
+ * Returns all cached options.
329
+ *
330
+ * @returns Map of OCC symbols to option data
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * const allOptions = client.getAllOptions();
335
+ * for (const [symbol, option] of allOptions) {
336
+ * console.log(`${symbol}: OI=${option.openInterest}`);
337
+ * }
338
+ * ```
339
+ */
340
+ getAllOptions() {
341
+ switch (this.currentBroker) {
342
+ case Broker.TRADIER:
343
+ return this.tradierClient?.getAllOptions() ?? new Map();
344
+ default:
345
+ return new Map();
346
+ }
347
+ }
348
+ // ==================== Event Emitter Pattern ====================
349
+ /**
350
+ * Registers an event listener for the specified event type.
351
+ *
352
+ * @template T - The event type
353
+ * @param event - The event type to listen for
354
+ * @param listener - The callback function to invoke when the event occurs
355
+ * @returns The FloeClient instance for method chaining
356
+ *
357
+ * @remarks
358
+ * Multiple listeners can be registered for the same event type.
359
+ * Use {@link off} to remove a listener when it's no longer needed.
360
+ *
361
+ * @example
362
+ * ```typescript
363
+ * client
364
+ * .on('tickerUpdate', (ticker) => console.log(ticker))
365
+ * .on('error', (error) => console.error(error));
366
+ * ```
367
+ */
368
+ on(event, listener) {
369
+ const listeners = this.eventListeners.get(event);
370
+ if (listeners) {
371
+ listeners.add(listener);
372
+ }
373
+ return this;
374
+ }
375
+ /**
376
+ * Removes an event listener for the specified event type.
377
+ *
378
+ * @template T - The event type
379
+ * @param event - The event type to stop listening for
380
+ * @param listener - The callback function to remove
381
+ * @returns The FloeClient instance for method chaining
382
+ *
383
+ * @remarks
384
+ * The listener must be the exact same function reference that was passed to {@link on}.
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * const handler = (ticker) => console.log(ticker);
389
+ * client.on('tickerUpdate', handler);
390
+ * // Later...
391
+ * client.off('tickerUpdate', handler);
392
+ * ```
393
+ */
394
+ off(event, listener) {
395
+ const listeners = this.eventListeners.get(event);
396
+ if (listeners) {
397
+ listeners.delete(listener);
398
+ }
399
+ return this;
400
+ }
401
+ /**
402
+ * Registers a one-time event listener that automatically removes itself after firing.
403
+ *
404
+ * @template T - The event type
405
+ * @param event - The event type to listen for
406
+ * @param listener - The callback function to invoke once when the event occurs
407
+ * @returns The FloeClient instance for method chaining
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * client.once('connected', ({ broker }) => {
412
+ * console.log(`Connected to ${broker}`);
413
+ * });
414
+ * ```
415
+ */
416
+ once(event, listener) {
417
+ const onceWrapper = ((data) => {
418
+ this.off(event, onceWrapper);
419
+ listener(data);
420
+ });
421
+ return this.on(event, onceWrapper);
422
+ }
423
+ /**
424
+ * Emits an event to all registered listeners.
425
+ *
426
+ * @template T - The event type
427
+ * @param event - The event type to emit
428
+ * @param data - The event payload
429
+ *
430
+ * @internal
431
+ * @remarks
432
+ * This method is used internally to dispatch events. It also triggers
433
+ * legacy callback handlers for backwards compatibility.
434
+ */
435
+ emit(event, data) {
436
+ const listeners = this.eventListeners.get(event);
437
+ if (listeners) {
438
+ listeners.forEach(listener => {
439
+ try {
440
+ listener(data);
441
+ }
442
+ catch (error) {
443
+ // Emit error event if a listener throws (but avoid infinite loops)
444
+ if (event !== 'error') {
445
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
446
+ }
447
+ }
448
+ });
449
+ }
450
+ // Also trigger legacy callbacks for backwards compatibility
451
+ if (event === 'tickerUpdate' && this.tickerDataCallback) {
452
+ this.tickerDataCallback(data);
453
+ }
454
+ if (event === 'optionUpdate' && this.optionDataCallback) {
455
+ this.optionDataCallback(data);
456
+ }
457
+ }
458
+ // ==================== Callback Pattern (Legacy) ====================
459
+ /**
460
+ * Registers a callback to be invoked whenever ticker data is updated.
461
+ *
462
+ * @param callback - Function to call with the updated ticker data
463
+ *
464
+ * @deprecated Prefer using {@link on}('tickerUpdate', callback) for new code.
465
+ * The event emitter pattern supports multiple listeners and provides
466
+ * better lifecycle management.
467
+ *
468
+ * @remarks
469
+ * Only one callback can be registered at a time. Calling this method again
470
+ * will replace the previous callback. For multiple listeners, use {@link on}.
471
+ *
472
+ * @example
473
+ * ```typescript
474
+ * client.onTickerDataChange((ticker) => {
475
+ * console.log(`${ticker.symbol} updated: ${ticker.price}`);
476
+ * });
477
+ * ```
478
+ */
479
+ onTickerDataChange(callback) {
480
+ this.tickerDataCallback = callback;
481
+ }
482
+ /**
483
+ * Registers a callback to be invoked whenever option data is updated.
484
+ *
485
+ * @param callback - Function to call with the updated option data
486
+ *
487
+ * @deprecated Prefer using {@link on}('optionUpdate', callback) for new code.
488
+ * The event emitter pattern supports multiple listeners and provides
489
+ * better lifecycle management.
490
+ *
491
+ * @remarks
492
+ * Only one callback can be registered at a time. Calling this method again
493
+ * will replace the previous callback. For multiple listeners, use {@link on}.
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * client.onOptionDataChange((option) => {
498
+ * console.log(`${option.symbol} bid: ${option.bid}, ask: ${option.ask}`);
499
+ * });
500
+ * ```
501
+ */
502
+ onOptionDataChange(callback) {
503
+ this.optionDataCallback = callback;
504
+ }
505
+ // ==================== Utility Methods ====================
506
+ /**
507
+ * Returns the list of currently subscribed ticker symbols.
508
+ *
509
+ * @returns Array of ticker symbols currently subscribed to
510
+ *
511
+ * @example
512
+ * ```typescript
513
+ * const tickers = client.getSubscribedTickers();
514
+ * console.log(`Subscribed to: ${tickers.join(', ')}`);
515
+ * ```
516
+ */
517
+ getSubscribedTickers() {
518
+ return [...this.currentSubscribedTickers];
519
+ }
520
+ /**
521
+ * Returns the list of currently subscribed option symbols.
522
+ *
523
+ * @returns Array of option symbols (OCC format) currently subscribed to
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * const options = client.getSubscribedOptions();
528
+ * console.log(`Subscribed to ${options.length} options`);
529
+ * ```
530
+ */
531
+ getSubscribedOptions() {
532
+ return [...this.currentSubscribedOptions];
533
+ }
534
+ /**
535
+ * Returns whether the client is currently connected to a broker.
536
+ *
537
+ * @returns True if connected to a broker, false otherwise
538
+ *
539
+ * @example
540
+ * ```typescript
541
+ * if (client.isConnected()) {
542
+ * client.subscribeToTickers(['AAPL']);
543
+ * }
544
+ * ```
545
+ */
546
+ isConnected() {
547
+ return this.currentBroker !== null;
548
+ }
549
+ }
550
+ exports.FloeClient = FloeClient;
@@ -0,0 +1,2 @@
1
+ export declare class SchwabClient {
2
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchwabClient = void 0;
4
+ class SchwabClient {
5
+ }
6
+ exports.SchwabClient = SchwabClient;