@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,7 @@
1
- import { NormalizedOption } from '../../types';
1
+ import { BaseBrokerClient, BaseBrokerClientOptions, AggressorSide, IntradayTrade, FlowSummary, BrokerClientEventType, BrokerEventListener } from './BaseBrokerClient';
2
+ export { AggressorSide, IntradayTrade, FlowSummary };
3
+ export type TastyTradeClientEventType = BrokerClientEventType;
4
+ export type TastyTradeEventListener<T> = BrokerEventListener<T>;
2
5
  /**
3
6
  * TastyTrade option chain item
4
7
  */
@@ -26,38 +29,14 @@ interface TastyTradeOptionChainItem {
26
29
  'implied-volatility'?: number;
27
30
  }
28
31
  /**
29
- * Aggressor side of a trade
32
+ * TastyTrade client configuration options
30
33
  */
31
- export type AggressorSide = 'buy' | 'sell' | 'unknown';
32
- /**
33
- * Intraday trade information with aggressor classification
34
- */
35
- export interface IntradayTrade {
36
- /** OCC option symbol */
37
- occSymbol: string;
38
- /** Trade price */
39
- price: number;
40
- /** Trade size (number of contracts) */
41
- size: number;
42
- /** Bid at time of trade */
43
- bid: number;
44
- /** Ask at time of trade */
45
- ask: number;
46
- /** Aggressor side determined from price vs NBBO */
47
- aggressorSide: AggressorSide;
48
- /** Timestamp of the trade */
49
- timestamp: number;
50
- /** Estimated OI change */
51
- estimatedOIChange: number;
34
+ export interface TastyTradeClientOptions extends BaseBrokerClientOptions {
35
+ /** TastyTrade session token (required) */
36
+ sessionToken: string;
37
+ /** Whether to use sandbox environment (default: false) */
38
+ sandbox?: boolean;
52
39
  }
53
- /**
54
- * Event types emitted by TastyTradeClient
55
- */
56
- type TastyTradeClientEventType = 'tickerUpdate' | 'optionUpdate' | 'optionTrade' | 'connected' | 'disconnected' | 'error';
57
- /**
58
- * Event listener callback type
59
- */
60
- type TastyTradeEventListener<T> = (data: T) => void;
61
40
  /**
62
41
  * TastyTradeClient handles real-time streaming connections to the TastyTrade API
63
42
  * via DxLink WebSockets.
@@ -86,7 +65,8 @@ type TastyTradeEventListener<T> = (data: T) => void;
86
65
  * client.subscribe(['SPY', '.SPXW231215C4500']); // Equity and option
87
66
  * ```
88
67
  */
89
- export declare class TastyTradeClient {
68
+ export declare class TastyTradeClient extends BaseBrokerClient {
69
+ protected readonly brokerName = "TastyTrade";
90
70
  /** TastyTrade session token */
91
71
  private sessionToken;
92
72
  /** DxLink API quote token */
@@ -103,40 +83,20 @@ export declare class TastyTradeClient {
103
83
  private feedChannelId;
104
84
  /** Feed channel opened */
105
85
  private feedChannelOpened;
106
- /** Currently subscribed symbols */
107
- private subscribedSymbols;
108
86
  /** Map from streamer symbol to OCC symbol */
109
87
  private streamerToOccMap;
110
88
  /** Map from OCC symbol to streamer symbol */
111
89
  private occToStreamerMap;
112
- /** Cached ticker data */
113
- private tickerCache;
114
- /** Cached option data */
115
- private optionCache;
116
- /** Base open interest from REST API */
117
- private baseOpenInterest;
118
- /** Cumulative estimated OI change from intraday trades */
119
- private cumulativeOIChange;
120
- /** History of intraday trades */
121
- private intradayTrades;
122
- /** Event listeners */
123
- private eventListeners;
124
- /** Reconnection attempt counter */
125
- private reconnectAttempts;
126
- /** Maximum reconnection attempts */
127
- private readonly maxReconnectAttempts;
128
- /** Reconnection delay in ms */
129
- private readonly baseReconnectDelay;
130
90
  /** Keepalive interval handle */
131
91
  private keepaliveInterval;
92
+ /** Whether the first UNAUTHORIZED message has been handled yet - prevents incorrect handling of 'true' UNAUTHORIZED messages */
93
+ private firstUnauthorizedMessageHandled;
132
94
  /** Keepalive timeout in seconds */
133
95
  private readonly keepaliveTimeoutSeconds;
134
96
  /** TastyTrade API base URL */
135
97
  private readonly apiBaseUrl;
136
98
  /** Whether to use sandbox environment */
137
99
  private readonly sandbox;
138
- /** Whether to log verbose debug information */
139
- private readonly verbose;
140
100
  /**
141
101
  * Creates a new TastyTradeClient instance.
142
102
  *
@@ -145,11 +105,7 @@ export declare class TastyTradeClient {
145
105
  * @param options.sandbox - Whether to use sandbox environment (default: false)
146
106
  * @param options.verbose - Whether to log verbose debug information (default: false)
147
107
  */
148
- constructor(options: {
149
- sessionToken: string;
150
- sandbox?: boolean;
151
- verbose?: boolean;
152
- });
108
+ constructor(options: TastyTradeClientOptions);
153
109
  /**
154
110
  * Creates a TastyTradeClient by logging in with username/password.
155
111
  *
@@ -222,40 +178,6 @@ export declare class TastyTradeClient {
222
178
  * @param occSymbols - Array of OCC option symbols to fetch data for
223
179
  */
224
180
  fetchOpenInterest(occSymbols: string[]): Promise<void>;
225
- /**
226
- * Returns cached option data for a symbol.
227
- */
228
- getOption(occSymbol: string): NormalizedOption | undefined;
229
- /**
230
- * Returns all cached options.
231
- */
232
- getAllOptions(): Map<string, NormalizedOption>;
233
- /**
234
- * Registers an event listener.
235
- */
236
- on<T>(event: TastyTradeClientEventType, listener: TastyTradeEventListener<T>): this;
237
- /**
238
- * Removes an event listener.
239
- */
240
- off<T>(event: TastyTradeClientEventType, listener: TastyTradeEventListener<T>): this;
241
- /**
242
- * Returns intraday trades for an option.
243
- */
244
- getIntradayTrades(occSymbol: string): IntradayTrade[];
245
- /**
246
- * Returns flow summary for an option.
247
- */
248
- getFlowSummary(occSymbol: string): {
249
- buyVolume: number;
250
- sellVolume: number;
251
- unknownVolume: number;
252
- netOIChange: number;
253
- tradeCount: number;
254
- };
255
- /**
256
- * Resets intraday tracking data.
257
- */
258
- resetIntradayData(occSymbols?: string[]): void;
259
181
  /**
260
182
  * Gets API quote token from TastyTrade.
261
183
  */
@@ -348,18 +270,6 @@ export declare class TastyTradeClient {
348
270
  * Updates option from Trade event.
349
271
  */
350
272
  private updateOptionFromTrade;
351
- /**
352
- * Determines aggressor side from trade price vs NBBO.
353
- */
354
- private determineAggressorSide;
355
- /**
356
- * Calculates estimated OI change from trade.
357
- */
358
- private calculateOIChangeFromTrade;
359
- /**
360
- * Calculates live open interest.
361
- */
362
- private calculateLiveOpenInterest;
363
273
  /**
364
274
  * Handles DxLink error messages.
365
275
  */
@@ -369,24 +279,11 @@ export declare class TastyTradeClient {
369
279
  */
370
280
  private attemptReconnect;
371
281
  /**
372
- * Checks if symbol is an OCC option symbol.
282
+ * Checks if symbol is a TastyTrade option symbol.
373
283
  */
374
- private isOptionSymbol;
284
+ private isTastyTradeOptionSymbol;
375
285
  /**
376
286
  * Sends a message to the WebSocket.
377
287
  */
378
288
  private sendMessage;
379
- /**
380
- * Emits an event to all listeners.
381
- */
382
- private emit;
383
- /**
384
- * Converts value to number, handling NaN and null.
385
- */
386
- private toNumber;
387
- /**
388
- * Sleep utility.
389
- */
390
- private sleep;
391
289
  }
392
- export {};
@@ -2,10 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TastyTradeClient = void 0;
4
4
  const occ_1 = require("../../utils/occ");
5
- /**
6
- * Regex pattern to identify OCC option symbols
7
- */
8
- const OCC_OPTION_PATTERN = /^.{1,6}\d{6}[CP]\d{8}$/;
5
+ const BaseBrokerClient_1 = require("./BaseBrokerClient");
9
6
  /**
10
7
  * Event field configurations for different event types
11
8
  */
@@ -45,7 +42,7 @@ const FEED_EVENT_FIELDS = {
45
42
  * client.subscribe(['SPY', '.SPXW231215C4500']); // Equity and option
46
43
  * ```
47
44
  */
48
- class TastyTradeClient {
45
+ class TastyTradeClient extends BaseBrokerClient_1.BaseBrokerClient {
49
46
  /**
50
47
  * Creates a new TastyTradeClient instance.
51
48
  *
@@ -55,6 +52,8 @@ class TastyTradeClient {
55
52
  * @param options.verbose - Whether to log verbose debug information (default: false)
56
53
  */
57
54
  constructor(options) {
55
+ super(options);
56
+ this.brokerName = 'TastyTrade';
58
57
  /** DxLink API quote token */
59
58
  this.quoteToken = null;
60
59
  /** DxLink WebSocket URL */
@@ -69,47 +68,21 @@ class TastyTradeClient {
69
68
  this.feedChannelId = 1;
70
69
  /** Feed channel opened */
71
70
  this.feedChannelOpened = false;
72
- /** Currently subscribed symbols */
73
- this.subscribedSymbols = new Set();
74
71
  /** Map from streamer symbol to OCC symbol */
75
72
  this.streamerToOccMap = new Map();
76
73
  /** Map from OCC symbol to streamer symbol */
77
74
  this.occToStreamerMap = new Map();
78
- /** Cached ticker data */
79
- this.tickerCache = new Map();
80
- /** Cached option data */
81
- this.optionCache = new Map();
82
- /** Base open interest from REST API */
83
- this.baseOpenInterest = new Map();
84
- /** Cumulative estimated OI change from intraday trades */
85
- this.cumulativeOIChange = new Map();
86
- /** History of intraday trades */
87
- this.intradayTrades = new Map();
88
- /** Event listeners */
89
- this.eventListeners = new Map();
90
- /** Reconnection attempt counter */
91
- this.reconnectAttempts = 0;
92
- /** Maximum reconnection attempts */
93
- this.maxReconnectAttempts = 5;
94
- /** Reconnection delay in ms */
95
- this.baseReconnectDelay = 1000;
96
75
  /** Keepalive interval handle */
97
76
  this.keepaliveInterval = null;
77
+ /** Whether the first UNAUTHORIZED message has been handled yet - prevents incorrect handling of 'true' UNAUTHORIZED messages */
78
+ this.firstUnauthorizedMessageHandled = false;
98
79
  /** Keepalive timeout in seconds */
99
80
  this.keepaliveTimeoutSeconds = 60;
100
81
  this.sessionToken = options.sessionToken;
101
82
  this.sandbox = options.sandbox ?? false;
102
- this.verbose = options.verbose ?? false;
103
83
  this.apiBaseUrl = this.sandbox
104
84
  ? 'https://api.cert.tastyworks.com'
105
85
  : 'https://api.tastyworks.com';
106
- // Initialize event listener maps
107
- this.eventListeners.set('tickerUpdate', new Set());
108
- this.eventListeners.set('optionUpdate', new Set());
109
- this.eventListeners.set('optionTrade', new Set());
110
- this.eventListeners.set('connected', new Set());
111
- this.eventListeners.set('disconnected', new Set());
112
- this.eventListeners.set('error', new Set());
113
86
  }
114
87
  // ==================== Static Factory Methods ====================
115
88
  /**
@@ -339,83 +312,6 @@ class TastyTradeClient {
339
312
  });
340
313
  await Promise.all(fetchPromises);
341
314
  }
342
- /**
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
- * Registers an event listener.
356
- */
357
- on(event, listener) {
358
- const listeners = this.eventListeners.get(event);
359
- if (listeners) {
360
- listeners.add(listener);
361
- }
362
- return this;
363
- }
364
- /**
365
- * Removes an event listener.
366
- */
367
- off(event, listener) {
368
- const listeners = this.eventListeners.get(event);
369
- if (listeners) {
370
- listeners.delete(listener);
371
- }
372
- return this;
373
- }
374
- /**
375
- * Returns intraday trades for an option.
376
- */
377
- getIntradayTrades(occSymbol) {
378
- return this.intradayTrades.get(occSymbol) ?? [];
379
- }
380
- /**
381
- * Returns flow summary for an option.
382
- */
383
- getFlowSummary(occSymbol) {
384
- const trades = this.intradayTrades.get(occSymbol) ?? [];
385
- let buyVolume = 0;
386
- let sellVolume = 0;
387
- let unknownVolume = 0;
388
- for (const trade of trades) {
389
- switch (trade.aggressorSide) {
390
- case 'buy':
391
- buyVolume += trade.size;
392
- break;
393
- case 'sell':
394
- sellVolume += trade.size;
395
- break;
396
- case 'unknown':
397
- unknownVolume += trade.size;
398
- break;
399
- }
400
- }
401
- return {
402
- buyVolume,
403
- sellVolume,
404
- unknownVolume,
405
- netOIChange: this.cumulativeOIChange.get(occSymbol) ?? 0,
406
- tradeCount: trades.length,
407
- };
408
- }
409
- /**
410
- * Resets intraday tracking data.
411
- */
412
- resetIntradayData(occSymbols) {
413
- const symbolsToReset = occSymbols ?? Array.from(this.intradayTrades.keys());
414
- for (const symbol of symbolsToReset) {
415
- this.intradayTrades.delete(symbol);
416
- this.cumulativeOIChange.set(symbol, 0);
417
- }
418
- }
419
315
  // ==================== Private Methods ====================
420
316
  /**
421
317
  * Gets API quote token from TastyTrade.
@@ -547,7 +443,7 @@ class TastyTradeClient {
547
443
  const entries = [];
548
444
  for (const symbol of symbols) {
549
445
  const streamerSymbol = this.getStreamerSymbol(symbol);
550
- const isOption = this.isOptionSymbol(symbol) || streamerSymbol.startsWith('.');
446
+ const isOption = this.isTastyTradeOptionSymbol(symbol) || streamerSymbol.startsWith('.');
551
447
  if (isOption) {
552
448
  // Subscribe to option-relevant events
553
449
  entries.push({ type: 'Quote', symbol: streamerSymbol });
@@ -588,7 +484,7 @@ class TastyTradeClient {
588
484
  return symbol;
589
485
  }
590
486
  // If it's an OCC option symbol, try to convert
591
- if (this.isOptionSymbol(symbol)) {
487
+ if (this.isTastyTradeOptionSymbol(symbol)) {
592
488
  try {
593
489
  const parsed = (0, occ_1.parseOCCSymbol)(symbol);
594
490
  // TastyTrade streamer format: .UNDERLYING + YYMMDD + C/P + STRIKE
@@ -670,8 +566,7 @@ class TastyTradeClient {
670
566
  const message = JSON.parse(data);
671
567
  switch (message.type) {
672
568
  case 'SETUP':
673
- // Server acknowledged setup, send auth
674
- this.sendAuth();
569
+ // no op; the server will send it's own first AUTH_STATE UNAUTHORIZED message right after
675
570
  break;
676
571
  case 'AUTH_STATE':
677
572
  this.handleAuthState(message, connectResolve);
@@ -706,6 +601,15 @@ class TastyTradeClient {
706
601
  * Handles AUTH_STATE message.
707
602
  */
708
603
  handleAuthState(message, connectResolve) {
604
+ // the first message we get back (after SETUP response) is an UNAUTHORIZED state
605
+ // this is an expected part of the flow (though unintuitive)
606
+ // see https://developer.tastytrade.com/streaming-market-data/#dxlink-streamer
607
+ if (!this.firstUnauthorizedMessageHandled && message.state === 'UNAUTHORIZED') {
608
+ // Server acknowledged setup, send auth
609
+ this.sendAuth();
610
+ this.firstUnauthorizedMessageHandled = true;
611
+ }
612
+ // once we are authorized, we can proceed as normal
709
613
  if (message.state === 'AUTHORIZED') {
710
614
  this.authorized = true;
711
615
  this.startKeepalive();
@@ -716,8 +620,15 @@ class TastyTradeClient {
716
620
  this.emit('connected', undefined);
717
621
  connectResolve?.();
718
622
  }
623
+ // a true unauthorized message after being authorized indicates a problem
624
+ else if (message.state === 'UNAUTHORIZED' && this.authorized) {
625
+ this.authorized = false;
626
+ this.emit('error', new Error('DxLink authorization lost'));
627
+ this.disconnect();
628
+ this.attemptReconnect();
629
+ }
719
630
  else {
720
- this.emit('error', new Error('DxLink authorization failed'));
631
+ this.emit('error', new Error('Unknown AUTH_STATE message state'));
721
632
  }
722
633
  }
723
634
  /**
@@ -853,185 +764,25 @@ class TastyTradeClient {
853
764
  * Updates ticker from Quote event.
854
765
  */
855
766
  updateTickerFromQuote(symbol, bidPrice, askPrice, bidSize, askSize, timestamp) {
856
- const existing = this.tickerCache.get(symbol);
857
- const ticker = {
858
- symbol,
859
- spot: bidPrice > 0 && askPrice > 0 ? (bidPrice + askPrice) / 2 : existing?.spot ?? 0,
860
- bid: bidPrice,
861
- bidSize,
862
- ask: askPrice,
863
- askSize,
864
- last: existing?.last ?? 0,
865
- volume: existing?.volume ?? 0,
866
- timestamp,
867
- };
868
- this.tickerCache.set(symbol, ticker);
869
- this.emit('tickerUpdate', ticker);
767
+ this.updateTickerFromQuoteData(symbol, bidPrice, bidSize, askPrice, askSize, timestamp);
870
768
  }
871
769
  /**
872
770
  * Updates ticker from Trade event.
873
771
  */
874
772
  updateTickerFromTrade(symbol, price, size, dayVolume, timestamp) {
875
- const existing = this.tickerCache.get(symbol);
876
- const ticker = {
877
- symbol,
878
- spot: existing?.spot ?? price,
879
- bid: existing?.bid ?? 0,
880
- bidSize: existing?.bidSize ?? 0,
881
- ask: existing?.ask ?? 0,
882
- askSize: existing?.askSize ?? 0,
883
- last: price,
884
- volume: dayVolume > 0 ? dayVolume : (existing?.volume ?? 0) + size,
885
- timestamp,
886
- };
887
- this.tickerCache.set(symbol, ticker);
888
- this.emit('tickerUpdate', ticker);
773
+ this.updateTickerFromTradeData(symbol, price, size, dayVolume > 0 ? dayVolume : null, timestamp);
889
774
  }
890
775
  /**
891
776
  * Updates option from Quote event.
892
777
  */
893
778
  updateOptionFromQuote(occSymbol, bidPrice, askPrice, bidSize, askSize, timestamp) {
894
- const existing = this.optionCache.get(occSymbol);
895
- // Parse OCC symbol if we don't have existing data
896
- let parsed;
897
- try {
898
- parsed = (0, occ_1.parseOCCSymbol)(occSymbol);
899
- }
900
- catch {
901
- // Try to use existing data or skip
902
- if (!existing)
903
- return;
904
- parsed = {
905
- symbol: existing.underlying,
906
- expiration: new Date(existing.expirationTimestamp),
907
- optionType: existing.optionType,
908
- strike: existing.strike,
909
- };
910
- }
911
- const option = {
912
- occSymbol,
913
- underlying: parsed.symbol,
914
- strike: parsed.strike,
915
- expiration: parsed.expiration.toISOString().split('T')[0],
916
- expirationTimestamp: parsed.expiration.getTime(),
917
- optionType: parsed.optionType,
918
- bid: bidPrice,
919
- bidSize,
920
- ask: askPrice,
921
- askSize,
922
- mark: bidPrice > 0 && askPrice > 0 ? (bidPrice + askPrice) / 2 : existing?.mark ?? 0,
923
- last: existing?.last ?? 0,
924
- volume: existing?.volume ?? 0,
925
- openInterest: existing?.openInterest ?? 0,
926
- liveOpenInterest: this.calculateLiveOpenInterest(occSymbol),
927
- impliedVolatility: existing?.impliedVolatility ?? 0,
928
- timestamp,
929
- };
930
- this.optionCache.set(occSymbol, option);
931
- this.emit('optionUpdate', option);
779
+ this.updateOptionFromQuoteData(occSymbol, bidPrice, bidSize, askPrice, askSize, timestamp, occ_1.parseOCCSymbol);
932
780
  }
933
781
  /**
934
782
  * Updates option from Trade event.
935
783
  */
936
784
  updateOptionFromTrade(occSymbol, price, size, dayVolume, timestamp) {
937
- const existing = this.optionCache.get(occSymbol);
938
- // Parse OCC symbol
939
- let parsed;
940
- try {
941
- parsed = (0, occ_1.parseOCCSymbol)(occSymbol);
942
- }
943
- catch {
944
- if (!existing)
945
- return;
946
- parsed = {
947
- symbol: existing.underlying,
948
- expiration: new Date(existing.expirationTimestamp),
949
- optionType: existing.optionType,
950
- strike: existing.strike,
951
- };
952
- }
953
- // Determine aggressor side
954
- const bid = existing?.bid ?? 0;
955
- const ask = existing?.ask ?? 0;
956
- const aggressorSide = this.determineAggressorSide(price, bid, ask);
957
- // Calculate OI change
958
- const estimatedOIChange = this.calculateOIChangeFromTrade(aggressorSide, size, parsed.optionType);
959
- const currentChange = this.cumulativeOIChange.get(occSymbol) ?? 0;
960
- this.cumulativeOIChange.set(occSymbol, currentChange + estimatedOIChange);
961
- if (this.verbose && estimatedOIChange !== 0) {
962
- const baseOI = this.baseOpenInterest.get(occSymbol) ?? 0;
963
- const newLiveOI = Math.max(0, baseOI + currentChange + estimatedOIChange);
964
- console.log(`[TastyTrade:OI] ${occSymbol} trade: price=${price.toFixed(2)}, size=${size}, aggressor=${aggressorSide}, OI change=${estimatedOIChange > 0 ? '+' : ''}${estimatedOIChange}, liveOI=${newLiveOI} (base=${baseOI}, cumulative=${currentChange + estimatedOIChange})`);
965
- }
966
- // Record trade
967
- const trade = {
968
- occSymbol,
969
- price,
970
- size,
971
- bid,
972
- ask,
973
- aggressorSide,
974
- timestamp,
975
- estimatedOIChange,
976
- };
977
- if (!this.intradayTrades.has(occSymbol)) {
978
- this.intradayTrades.set(occSymbol, []);
979
- }
980
- this.intradayTrades.get(occSymbol).push(trade);
981
- this.emit('optionTrade', trade);
982
- const option = {
983
- occSymbol,
984
- underlying: parsed.symbol,
985
- strike: parsed.strike,
986
- expiration: parsed.expiration.toISOString().split('T')[0],
987
- expirationTimestamp: parsed.expiration.getTime(),
988
- optionType: parsed.optionType,
989
- bid,
990
- bidSize: existing?.bidSize ?? 0,
991
- ask,
992
- askSize: existing?.askSize ?? 0,
993
- mark: bid > 0 && ask > 0 ? (bid + ask) / 2 : price,
994
- last: price,
995
- volume: dayVolume > 0 ? dayVolume : (existing?.volume ?? 0) + size,
996
- openInterest: existing?.openInterest ?? 0,
997
- liveOpenInterest: this.calculateLiveOpenInterest(occSymbol),
998
- impliedVolatility: existing?.impliedVolatility ?? 0,
999
- timestamp,
1000
- };
1001
- this.optionCache.set(occSymbol, option);
1002
- this.emit('optionUpdate', option);
1003
- }
1004
- /**
1005
- * Determines aggressor side from trade price vs NBBO.
1006
- */
1007
- determineAggressorSide(tradePrice, bid, ask) {
1008
- if (bid <= 0 || ask <= 0)
1009
- return 'unknown';
1010
- const spread = ask - bid;
1011
- const tolerance = spread > 0 ? spread * 0.001 : 0.001;
1012
- if (tradePrice >= ask - tolerance) {
1013
- return 'buy';
1014
- }
1015
- else if (tradePrice <= bid + tolerance) {
1016
- return 'sell';
1017
- }
1018
- return 'unknown';
1019
- }
1020
- /**
1021
- * Calculates estimated OI change from trade.
1022
- */
1023
- calculateOIChangeFromTrade(aggressorSide, size, _optionType) {
1024
- if (aggressorSide === 'unknown')
1025
- return 0;
1026
- return aggressorSide === 'buy' ? size : -size;
1027
- }
1028
- /**
1029
- * Calculates live open interest.
1030
- */
1031
- calculateLiveOpenInterest(occSymbol) {
1032
- const baseOI = this.baseOpenInterest.get(occSymbol) ?? 0;
1033
- const cumulativeChange = this.cumulativeOIChange.get(occSymbol) ?? 0;
1034
- return Math.max(0, baseOI + cumulativeChange);
785
+ this.updateOptionFromTradeData(occSymbol, price, size, dayVolume > 0 ? dayVolume : null, timestamp, occ_1.parseOCCSymbol);
1035
786
  }
1036
787
  /**
1037
788
  * Handles DxLink error messages.
@@ -1048,10 +799,8 @@ class TastyTradeClient {
1048
799
  return;
1049
800
  }
1050
801
  this.reconnectAttempts++;
1051
- const delay = this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
1052
- if (this.verbose) {
1053
- console.log(`[TastyTrade:DxLink] Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
1054
- }
802
+ const delay = this.getReconnectDelay();
803
+ this.log(`Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
1055
804
  await this.sleep(delay);
1056
805
  try {
1057
806
  await this.connect();
@@ -1061,10 +810,10 @@ class TastyTradeClient {
1061
810
  }
1062
811
  }
1063
812
  /**
1064
- * Checks if symbol is an OCC option symbol.
813
+ * Checks if symbol is a TastyTrade option symbol.
1065
814
  */
1066
- isOptionSymbol(symbol) {
1067
- return OCC_OPTION_PATTERN.test(symbol);
815
+ isTastyTradeOptionSymbol(symbol) {
816
+ return BaseBrokerClient_1.OCC_OPTION_PATTERN.test(symbol);
1068
817
  }
1069
818
  /**
1070
819
  * Sends a message to the WebSocket.
@@ -1074,38 +823,5 @@ class TastyTradeClient {
1074
823
  this.ws.send(JSON.stringify(message));
1075
824
  }
1076
825
  }
1077
- /**
1078
- * Emits an event to all listeners.
1079
- */
1080
- emit(event, data) {
1081
- const listeners = this.eventListeners.get(event);
1082
- if (listeners) {
1083
- listeners.forEach(listener => {
1084
- try {
1085
- listener(data);
1086
- }
1087
- catch (error) {
1088
- console.error('Event listener error:', error);
1089
- }
1090
- });
1091
- }
1092
- }
1093
- /**
1094
- * Converts value to number, handling NaN and null.
1095
- */
1096
- toNumber(value) {
1097
- if (value === null || value === undefined)
1098
- return 0;
1099
- if (typeof value === 'number')
1100
- return isNaN(value) ? 0 : value;
1101
- const num = parseFloat(value);
1102
- return isNaN(num) ? 0 : num;
1103
- }
1104
- /**
1105
- * Sleep utility.
1106
- */
1107
- sleep(ms) {
1108
- return new Promise(resolve => setTimeout(resolve, ms));
1109
- }
1110
826
  }
1111
827
  exports.TastyTradeClient = TastyTradeClient;