@fullstackcraftllc/floe 0.0.8 → 0.0.10

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.
@@ -1,4 +1,8 @@
1
- import { NormalizedOption, NormalizedTicker } from '../../types';
1
+ import { NormalizedTicker } from '../../types';
2
+ import { BaseBrokerClient, BaseBrokerClientOptions, AggressorSide, IntradayTrade, FlowSummary, BrokerClientEventType, BrokerEventListener } from './BaseBrokerClient';
3
+ export { AggressorSide, IntradayTrade, FlowSummary };
4
+ export type TradeStationClientEventType = BrokerClientEventType;
5
+ export type TradeStationEventListener<T> = BrokerEventListener<T>;
2
6
  /**
3
7
  * TradeStation symbol details response
4
8
  */
@@ -23,38 +27,18 @@ interface TradeStationSymbolDetails {
23
27
  }>;
24
28
  }
25
29
  /**
26
- * Aggressor side of a trade
30
+ * TradeStation client configuration options
27
31
  */
28
- export type AggressorSide = 'buy' | 'sell' | 'unknown';
29
- /**
30
- * Intraday trade information with aggressor classification
31
- */
32
- export interface IntradayTrade {
33
- /** OCC option symbol */
34
- occSymbol: string;
35
- /** Trade price */
36
- price: number;
37
- /** Trade size (number of contracts) */
38
- size: number;
39
- /** Bid at time of trade */
40
- bid: number;
41
- /** Ask at time of trade */
42
- ask: number;
43
- /** Aggressor side determined from price vs NBBO */
44
- aggressorSide: AggressorSide;
45
- /** Timestamp of the trade */
46
- timestamp: number;
47
- /** Estimated OI change */
48
- estimatedOIChange: number;
32
+ export interface TradeStationClientOptions extends BaseBrokerClientOptions {
33
+ /** TradeStation OAuth access token (required) */
34
+ accessToken: string;
35
+ /** OAuth refresh token for automatic token renewal */
36
+ refreshToken?: string;
37
+ /** Whether to use simulation environment (default: false) */
38
+ simulation?: boolean;
39
+ /** Callback when token is refreshed */
40
+ onTokenRefresh?: (newToken: string) => void;
49
41
  }
50
- /**
51
- * Event types emitted by TradeStationClient
52
- */
53
- type TradeStationClientEventType = 'tickerUpdate' | 'optionUpdate' | 'optionTrade' | 'connected' | 'disconnected' | 'error';
54
- /**
55
- * Event listener callback type
56
- */
57
- type TradeStationEventListener<T> = (data: T) => void;
58
42
  /**
59
43
  * TradeStationClient handles real-time streaming connections to the TradeStation API
60
44
  * via HTTP chunked transfer encoding.
@@ -82,7 +66,8 @@ type TradeStationEventListener<T> = (data: T) => void;
82
66
  * client.subscribe(['MSFT', 'AAPL']);
83
67
  * ```
84
68
  */
85
- export declare class TradeStationClient {
69
+ export declare class TradeStationClient extends BaseBrokerClient {
70
+ protected readonly brokerName = "TradeStation";
86
71
  /** TradeStation OAuth access token */
87
72
  private accessToken;
88
73
  /** Connection state */
@@ -93,24 +78,6 @@ export declare class TradeStationClient {
93
78
  private subscribedOptions;
94
79
  /** Active AbortControllers for streams */
95
80
  private activeStreams;
96
- /** Cached ticker data */
97
- private tickerCache;
98
- /** Cached option data */
99
- private optionCache;
100
- /** Base open interest from REST API */
101
- private baseOpenInterest;
102
- /** Cumulative estimated OI change from intraday trades */
103
- private cumulativeOIChange;
104
- /** History of intraday trades */
105
- private intradayTrades;
106
- /** Event listeners */
107
- private eventListeners;
108
- /** Reconnection attempt counter */
109
- private reconnectAttempts;
110
- /** Maximum reconnection attempts */
111
- private readonly maxReconnectAttempts;
112
- /** Reconnection delay in ms */
113
- private readonly baseReconnectDelay;
114
81
  /** TradeStation API base URL */
115
82
  private readonly apiBaseUrl;
116
83
  /** Whether to use simulation environment */
@@ -119,8 +86,6 @@ export declare class TradeStationClient {
119
86
  private refreshToken;
120
87
  /** Token refresh callback */
121
88
  private onTokenRefresh;
122
- /** Whether to log verbose debug information */
123
- private readonly verbose;
124
89
  /**
125
90
  * Creates a new TradeStationClient instance.
126
91
  *
@@ -131,13 +96,7 @@ export declare class TradeStationClient {
131
96
  * @param options.onTokenRefresh - Callback when token is refreshed
132
97
  * @param options.verbose - Whether to log verbose debug information (default: false)
133
98
  */
134
- constructor(options: {
135
- accessToken: string;
136
- refreshToken?: string;
137
- simulation?: boolean;
138
- onTokenRefresh?: (newToken: string) => void;
139
- verbose?: boolean;
140
- });
99
+ constructor(options: TradeStationClientOptions);
141
100
  /**
142
101
  * Establishes connection state for TradeStation streaming.
143
102
  *
@@ -219,52 +178,21 @@ export declare class TradeStationClient {
219
178
  * @returns Symbol details response
220
179
  */
221
180
  fetchSymbolDetails(symbols: string[]): Promise<TradeStationSymbolDetails>;
222
- /**
223
- * Returns cached option data for a symbol.
224
- */
225
- getOption(occSymbol: string): NormalizedOption | undefined;
226
- /**
227
- * Returns all cached options.
228
- */
229
- getAllOptions(): Map<string, NormalizedOption>;
230
- /**
231
- * Returns cached ticker data for a symbol.
232
- */
233
- getTicker(symbol: string): NormalizedTicker | undefined;
234
- /**
235
- * Returns all cached tickers.
236
- */
237
- getAllTickers(): Map<string, NormalizedTicker>;
238
- /**
239
- * Registers an event listener.
240
- */
241
- on<T>(event: TradeStationClientEventType, listener: TradeStationEventListener<T>): this;
242
- /**
243
- * Removes an event listener.
244
- */
245
- off<T>(event: TradeStationClientEventType, listener: TradeStationEventListener<T>): this;
246
- /**
247
- * Returns intraday trades for an option.
248
- */
249
- getIntradayTrades(occSymbol: string): IntradayTrade[];
250
- /**
251
- * Returns flow summary for an option.
252
- */
253
- getFlowSummary(occSymbol: string): {
254
- buyVolume: number;
255
- sellVolume: number;
256
- unknownVolume: number;
257
- netOIChange: number;
258
- tradeCount: number;
259
- };
260
- /**
261
- * Resets intraday tracking data.
262
- */
263
- resetIntradayData(occSymbols?: string[]): void;
264
181
  /**
265
182
  * Updates the access token (for token refresh scenarios).
266
183
  */
267
184
  updateAccessToken(newToken: string): void;
185
+ /**
186
+ * Fetches open interest and other static data for subscribed options.
187
+ *
188
+ * @param occSymbols - Array of OCC option symbols to fetch data for
189
+ *
190
+ * @remarks
191
+ * TradeStation provides open interest via the streaming option quotes.
192
+ * This method is a no-op placeholder to satisfy the abstract interface.
193
+ * OI data will be populated automatically through the streaming connection.
194
+ */
195
+ fetchOpenInterest(occSymbols: string[]): Promise<void>;
268
196
  /**
269
197
  * Gets authorization headers for API requests.
270
198
  */
@@ -334,32 +262,7 @@ export declare class TradeStationClient {
334
262
  */
335
263
  private toTradeStationOptionSymbol;
336
264
  /**
337
- * Determines aggressor side from trade price vs NBBO.
338
- */
339
- private determineAggressorSide;
340
- /**
341
- * Calculates estimated OI change from trade.
342
- */
343
- private calculateOIChangeFromTrade;
344
- /**
345
- * Calculates live open interest.
346
- */
347
- private calculateLiveOpenInterest;
348
- /**
349
- * Checks if a symbol is an option symbol.
350
- */
351
- private isOptionSymbol;
352
- /**
353
- * Parses a numeric string value.
354
- */
355
- private parseNumber;
356
- /**
357
- * Emits an event to all listeners.
358
- */
359
- private emit;
360
- /**
361
- * Sleep utility.
265
+ * Checks if a symbol is an option symbol (TradeStation or OCC format).
362
266
  */
363
- private sleep;
267
+ private isTradeStationOptionSymbol;
364
268
  }
365
- export {};
@@ -2,16 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TradeStationClient = void 0;
4
4
  const occ_1 = require("../../utils/occ");
5
+ const BaseBrokerClient_1 = require("./BaseBrokerClient");
5
6
  /**
6
7
  * Regex pattern to identify TradeStation option symbols
7
8
  * TradeStation uses space-padded format: "MSFT 220916C305"
8
9
  */
9
10
  const TS_OPTION_PATTERN = /^[A-Z]+\s+\d{6}[CP]\d+(\.\d+)?$/;
10
- /**
11
- * Regex pattern to identify OCC option symbols
12
- * Format: ROOT + YYMMDD + C/P + 8-digit strike
13
- */
14
- const OCC_OPTION_PATTERN = /^.{1,6}\d{6}[CP]\d{8}$/;
15
11
  /**
16
12
  * TradeStationClient handles real-time streaming connections to the TradeStation API
17
13
  * via HTTP chunked transfer encoding.
@@ -39,7 +35,7 @@ const OCC_OPTION_PATTERN = /^.{1,6}\d{6}[CP]\d{8}$/;
39
35
  * client.subscribe(['MSFT', 'AAPL']);
40
36
  * ```
41
37
  */
42
- class TradeStationClient {
38
+ class TradeStationClient extends BaseBrokerClient_1.BaseBrokerClient {
43
39
  /**
44
40
  * Creates a new TradeStationClient instance.
45
41
  *
@@ -51,6 +47,8 @@ class TradeStationClient {
51
47
  * @param options.verbose - Whether to log verbose debug information (default: false)
52
48
  */
53
49
  constructor(options) {
50
+ super(options);
51
+ this.brokerName = 'TradeStation';
54
52
  /** Connection state */
55
53
  this.connected = false;
56
54
  /** Currently subscribed ticker symbols */
@@ -59,24 +57,6 @@ class TradeStationClient {
59
57
  this.subscribedOptions = new Set();
60
58
  /** Active AbortControllers for streams */
61
59
  this.activeStreams = new Map();
62
- /** Cached ticker data */
63
- this.tickerCache = new Map();
64
- /** Cached option data */
65
- this.optionCache = new Map();
66
- /** Base open interest from REST API */
67
- this.baseOpenInterest = new Map();
68
- /** Cumulative estimated OI change from intraday trades */
69
- this.cumulativeOIChange = new Map();
70
- /** History of intraday trades */
71
- this.intradayTrades = new Map();
72
- /** Event listeners */
73
- this.eventListeners = new Map();
74
- /** Reconnection attempt counter */
75
- this.reconnectAttempts = 0;
76
- /** Maximum reconnection attempts */
77
- this.maxReconnectAttempts = 5;
78
- /** Reconnection delay in ms */
79
- this.baseReconnectDelay = 1000;
80
60
  /** TradeStation API base URL */
81
61
  this.apiBaseUrl = 'https://api.tradestation.com/v3';
82
62
  /** Refresh token for token refresh */
@@ -87,14 +67,6 @@ class TradeStationClient {
87
67
  this.refreshToken = options.refreshToken ?? null;
88
68
  this.simulation = options.simulation ?? false;
89
69
  this.onTokenRefresh = options.onTokenRefresh ?? null;
90
- this.verbose = options.verbose ?? false;
91
- // Initialize event listener maps
92
- this.eventListeners.set('tickerUpdate', new Set());
93
- this.eventListeners.set('optionUpdate', new Set());
94
- this.eventListeners.set('optionTrade', new Set());
95
- this.eventListeners.set('connected', new Set());
96
- this.eventListeners.set('disconnected', new Set());
97
- this.eventListeners.set('error', new Set());
98
70
  }
99
71
  // ==================== Public API ====================
100
72
  /**
@@ -163,7 +135,7 @@ class TradeStationClient {
163
135
  const tickers = [];
164
136
  const options = [];
165
137
  for (const symbol of symbols) {
166
- if (this.isOptionSymbol(symbol)) {
138
+ if (this.isTradeStationOptionSymbol(symbol)) {
167
139
  options.push(symbol);
168
140
  this.subscribedOptions.add(symbol);
169
141
  }
@@ -192,7 +164,7 @@ class TradeStationClient {
192
164
  const tickersToRemove = [];
193
165
  const optionsToRemove = [];
194
166
  for (const symbol of symbols) {
195
- if (this.isOptionSymbol(symbol)) {
167
+ if (this.isTradeStationOptionSymbol(symbol)) {
196
168
  this.subscribedOptions.delete(symbol);
197
169
  optionsToRemove.push(symbol);
198
170
  }
@@ -340,100 +312,29 @@ class TradeStationClient {
340
312
  }
341
313
  }
342
314
  /**
343
- * Returns cached option data for a symbol.
344
- */
345
- getOption(occSymbol) {
346
- return this.optionCache.get(occSymbol);
347
- }
348
- /**
349
- * Returns all cached options.
350
- */
351
- getAllOptions() {
352
- return new Map(this.optionCache);
353
- }
354
- /**
355
- * Returns cached ticker data for a symbol.
356
- */
357
- getTicker(symbol) {
358
- return this.tickerCache.get(symbol);
359
- }
360
- /**
361
- * Returns all cached tickers.
362
- */
363
- getAllTickers() {
364
- return new Map(this.tickerCache);
365
- }
366
- /**
367
- * Registers an event listener.
368
- */
369
- on(event, listener) {
370
- const listeners = this.eventListeners.get(event);
371
- if (listeners) {
372
- listeners.add(listener);
373
- }
374
- return this;
375
- }
376
- /**
377
- * Removes an event listener.
378
- */
379
- off(event, listener) {
380
- const listeners = this.eventListeners.get(event);
381
- if (listeners) {
382
- listeners.delete(listener);
383
- }
384
- return this;
385
- }
386
- /**
387
- * Returns intraday trades for an option.
388
- */
389
- getIntradayTrades(occSymbol) {
390
- return this.intradayTrades.get(occSymbol) ?? [];
391
- }
392
- /**
393
- * Returns flow summary for an option.
315
+ * Updates the access token (for token refresh scenarios).
394
316
  */
395
- getFlowSummary(occSymbol) {
396
- const trades = this.intradayTrades.get(occSymbol) ?? [];
397
- let buyVolume = 0;
398
- let sellVolume = 0;
399
- let unknownVolume = 0;
400
- for (const trade of trades) {
401
- switch (trade.aggressorSide) {
402
- case 'buy':
403
- buyVolume += trade.size;
404
- break;
405
- case 'sell':
406
- sellVolume += trade.size;
407
- break;
408
- case 'unknown':
409
- unknownVolume += trade.size;
410
- break;
411
- }
412
- }
413
- return {
414
- buyVolume,
415
- sellVolume,
416
- unknownVolume,
417
- netOIChange: this.cumulativeOIChange.get(occSymbol) ?? 0,
418
- tradeCount: trades.length,
419
- };
317
+ updateAccessToken(newToken) {
318
+ this.accessToken = newToken;
420
319
  }
421
320
  /**
422
- * Resets intraday tracking data.
321
+ * Fetches open interest and other static data for subscribed options.
322
+ *
323
+ * @param occSymbols - Array of OCC option symbols to fetch data for
324
+ *
325
+ * @remarks
326
+ * TradeStation provides open interest via the streaming option quotes.
327
+ * This method is a no-op placeholder to satisfy the abstract interface.
328
+ * OI data will be populated automatically through the streaming connection.
423
329
  */
424
- resetIntradayData(occSymbols) {
425
- const symbolsToReset = occSymbols ?? Array.from(this.intradayTrades.keys());
426
- for (const symbol of symbolsToReset) {
427
- this.intradayTrades.delete(symbol);
428
- this.cumulativeOIChange.set(symbol, 0);
330
+ async fetchOpenInterest(occSymbols) {
331
+ // TradeStation provides OI through the option quote stream
332
+ // The subscribedOptions will receive OI data automatically
333
+ // This is a no-op to satisfy the abstract method requirement
334
+ if (this.verbose) {
335
+ console.log(`[TradeStation] fetchOpenInterest called for ${occSymbols.length} symbols - data comes via stream`);
429
336
  }
430
337
  }
431
- /**
432
- * Updates the access token (for token refresh scenarios).
433
- */
434
- updateAccessToken(newToken) {
435
- this.accessToken = newToken;
436
- }
437
338
  // ==================== Private Methods ====================
438
339
  /**
439
340
  * Gets authorization headers for API requests.
@@ -699,18 +600,18 @@ class TradeStationClient {
699
600
  * Normalizes TradeStation quote to NormalizedTicker.
700
601
  */
701
602
  normalizeQuote(quote) {
702
- const bid = this.parseNumber(quote.Bid);
703
- const ask = this.parseNumber(quote.Ask);
704
- const last = this.parseNumber(quote.Last);
603
+ const bid = this.toNumber(quote.Bid);
604
+ const ask = this.toNumber(quote.Ask);
605
+ const last = this.toNumber(quote.Last);
705
606
  return {
706
607
  symbol: quote.Symbol,
707
608
  spot: bid > 0 && ask > 0 ? (bid + ask) / 2 : last,
708
609
  bid,
709
- bidSize: this.parseNumber(quote.BidSize),
610
+ bidSize: this.toNumber(quote.BidSize),
710
611
  ask,
711
- askSize: this.parseNumber(quote.AskSize),
612
+ askSize: this.toNumber(quote.AskSize),
712
613
  last,
713
- volume: this.parseNumber(quote.Volume),
614
+ volume: this.toNumber(quote.Volume),
714
615
  timestamp: quote.TradeTime ? new Date(quote.TradeTime).getTime() : Date.now(),
715
616
  };
716
617
  }
@@ -739,9 +640,9 @@ class TradeStationClient {
739
640
  strike: parseFloat(leg.StrikePrice),
740
641
  };
741
642
  }
742
- const bid = this.parseNumber(data.Bid);
743
- const ask = this.parseNumber(data.Ask);
744
- const last = this.parseNumber(data.Last);
643
+ const bid = this.toNumber(data.Bid);
644
+ const ask = this.toNumber(data.Ask);
645
+ const last = this.toNumber(data.Last);
745
646
  const existingOI = this.baseOpenInterest.get(occSymbol) ?? 0;
746
647
  return {
747
648
  occSymbol,
@@ -759,7 +660,7 @@ class TradeStationClient {
759
660
  volume: data.Volume ?? 0,
760
661
  openInterest: data.DailyOpenInterest ?? leg.OpenInterest ?? existingOI,
761
662
  liveOpenInterest: this.calculateLiveOpenInterest(occSymbol),
762
- impliedVolatility: this.parseNumber(data.ImpliedVolatility),
663
+ impliedVolatility: this.toNumber(data.ImpliedVolatility),
763
664
  timestamp: Date.now(),
764
665
  };
765
666
  }
@@ -772,7 +673,7 @@ class TradeStationClient {
772
673
  if (!tsSymbol)
773
674
  return null;
774
675
  // Already in OCC format?
775
- if (OCC_OPTION_PATTERN.test(tsSymbol.replace(/\s+/g, ''))) {
676
+ if (BaseBrokerClient_1.OCC_OPTION_PATTERN.test(tsSymbol.replace(/\s+/g, ''))) {
776
677
  return tsSymbol.replace(/\s+/g, '');
777
678
  }
778
679
  // Parse TradeStation format
@@ -815,75 +716,10 @@ class TradeStationClient {
815
716
  }
816
717
  }
817
718
  /**
818
- * Determines aggressor side from trade price vs NBBO.
819
- */
820
- determineAggressorSide(tradePrice, bid, ask) {
821
- if (bid <= 0 || ask <= 0)
822
- return 'unknown';
823
- const spread = ask - bid;
824
- const tolerance = spread > 0 ? spread * 0.001 : 0.001;
825
- if (tradePrice >= ask - tolerance) {
826
- return 'buy';
827
- }
828
- else if (tradePrice <= bid + tolerance) {
829
- return 'sell';
830
- }
831
- return 'unknown';
832
- }
833
- /**
834
- * Calculates estimated OI change from trade.
835
- */
836
- calculateOIChangeFromTrade(aggressorSide, size, _optionType) {
837
- if (aggressorSide === 'unknown')
838
- return 0;
839
- return aggressorSide === 'buy' ? size : -size;
840
- }
841
- /**
842
- * Calculates live open interest.
843
- */
844
- calculateLiveOpenInterest(occSymbol) {
845
- const baseOI = this.baseOpenInterest.get(occSymbol) ?? 0;
846
- const cumulativeChange = this.cumulativeOIChange.get(occSymbol) ?? 0;
847
- return Math.max(0, baseOI + cumulativeChange);
848
- }
849
- /**
850
- * Checks if a symbol is an option symbol.
851
- */
852
- isOptionSymbol(symbol) {
853
- return TS_OPTION_PATTERN.test(symbol) || OCC_OPTION_PATTERN.test(symbol);
854
- }
855
- /**
856
- * Parses a numeric string value.
857
- */
858
- parseNumber(value) {
859
- if (value === undefined || value === null)
860
- return 0;
861
- if (typeof value === 'number')
862
- return isNaN(value) ? 0 : value;
863
- const num = parseFloat(value);
864
- return isNaN(num) ? 0 : num;
865
- }
866
- /**
867
- * Emits an event to all listeners.
868
- */
869
- emit(event, data) {
870
- const listeners = this.eventListeners.get(event);
871
- if (listeners) {
872
- listeners.forEach(listener => {
873
- try {
874
- listener(data);
875
- }
876
- catch (error) {
877
- console.error('Event listener error:', error);
878
- }
879
- });
880
- }
881
- }
882
- /**
883
- * Sleep utility.
719
+ * Checks if a symbol is an option symbol (TradeStation or OCC format).
884
720
  */
885
- sleep(ms) {
886
- return new Promise(resolve => setTimeout(resolve, ms));
721
+ isTradeStationOptionSymbol(symbol) {
722
+ return TS_OPTION_PATTERN.test(symbol) || BaseBrokerClient_1.OCC_OPTION_PATTERN.test(symbol);
887
723
  }
888
724
  }
889
725
  exports.TradeStationClient = TradeStationClient;