@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.
@@ -135,16 +135,20 @@ export declare class TastyTradeClient {
135
135
  private readonly apiBaseUrl;
136
136
  /** Whether to use sandbox environment */
137
137
  private readonly sandbox;
138
+ /** Whether to log verbose debug information */
139
+ private readonly verbose;
138
140
  /**
139
141
  * Creates a new TastyTradeClient instance.
140
142
  *
141
143
  * @param options - Client configuration options
142
144
  * @param options.sessionToken - TastyTrade session token (required)
143
145
  * @param options.sandbox - Whether to use sandbox environment (default: false)
146
+ * @param options.verbose - Whether to log verbose debug information (default: false)
144
147
  */
145
148
  constructor(options: {
146
149
  sessionToken: string;
147
150
  sandbox?: boolean;
151
+ verbose?: boolean;
148
152
  });
149
153
  /**
150
154
  * Creates a TastyTradeClient by logging in with username/password.
@@ -52,6 +52,7 @@ class TastyTradeClient {
52
52
  * @param options - Client configuration options
53
53
  * @param options.sessionToken - TastyTrade session token (required)
54
54
  * @param options.sandbox - Whether to use sandbox environment (default: false)
55
+ * @param options.verbose - Whether to log verbose debug information (default: false)
55
56
  */
56
57
  constructor(options) {
57
58
  /** DxLink API quote token */
@@ -98,6 +99,7 @@ class TastyTradeClient {
98
99
  this.keepaliveTimeoutSeconds = 60;
99
100
  this.sessionToken = options.sessionToken;
100
101
  this.sandbox = options.sandbox ?? false;
102
+ this.verbose = options.verbose ?? false;
101
103
  this.apiBaseUrl = this.sandbox
102
104
  ? 'https://api.cert.tastyworks.com'
103
105
  : 'https://api.tastyworks.com';
@@ -291,6 +293,9 @@ class TastyTradeClient {
291
293
  // Store base OI
292
294
  if (item['open-interest'] !== undefined) {
293
295
  this.baseOpenInterest.set(occSymbol, item['open-interest']);
296
+ if (this.verbose) {
297
+ console.log(`[TastyTrade:OI] Base OI set for ${occSymbol}: ${item['open-interest']}`);
298
+ }
294
299
  }
295
300
  if (!this.cumulativeOIChange.has(occSymbol)) {
296
301
  this.cumulativeOIChange.set(occSymbol, 0);
@@ -694,6 +699,9 @@ class TastyTradeClient {
694
699
  this.authorized = true;
695
700
  this.startKeepalive();
696
701
  this.openFeedChannel();
702
+ if (this.verbose) {
703
+ console.log('[TastyTrade:DxLink] Authorized and connected');
704
+ }
697
705
  this.emit('connected', undefined);
698
706
  connectResolve?.();
699
707
  }
@@ -824,6 +832,9 @@ class TastyTradeClient {
824
832
  // Update base OI if not set
825
833
  if (!this.baseOpenInterest.has(symbol)) {
826
834
  this.baseOpenInterest.set(symbol, openInterest);
835
+ if (this.verbose) {
836
+ console.log(`[TastyTrade:OI] Base OI set from stream for ${symbol}: ${openInterest}`);
837
+ }
827
838
  }
828
839
  }
829
840
  }
@@ -936,6 +947,11 @@ class TastyTradeClient {
936
947
  const estimatedOIChange = this.calculateOIChangeFromTrade(aggressorSide, size, parsed.optionType);
937
948
  const currentChange = this.cumulativeOIChange.get(occSymbol) ?? 0;
938
949
  this.cumulativeOIChange.set(occSymbol, currentChange + estimatedOIChange);
950
+ if (this.verbose && estimatedOIChange !== 0) {
951
+ const baseOI = this.baseOpenInterest.get(occSymbol) ?? 0;
952
+ const newLiveOI = Math.max(0, baseOI + currentChange + estimatedOIChange);
953
+ 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})`);
954
+ }
939
955
  // Record trade
940
956
  const trade = {
941
957
  occSymbol,
@@ -1022,6 +1038,9 @@ class TastyTradeClient {
1022
1038
  }
1023
1039
  this.reconnectAttempts++;
1024
1040
  const delay = this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
1041
+ if (this.verbose) {
1042
+ console.log(`[TastyTrade:DxLink] Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
1043
+ }
1025
1044
  await this.sleep(delay);
1026
1045
  try {
1027
1046
  await this.connect();
@@ -0,0 +1,361 @@
1
+ import { NormalizedOption, NormalizedTicker } from '../../types';
2
+ /**
3
+ * TradeStation symbol details response
4
+ */
5
+ interface TradeStationSymbolDetails {
6
+ Symbols: Array<{
7
+ Symbol: string;
8
+ Description?: string;
9
+ Exchange?: string;
10
+ Currency?: string;
11
+ PointValue?: number;
12
+ AssetType?: string;
13
+ FutureType?: string;
14
+ ExpirationDate?: string;
15
+ StrikePrice?: number;
16
+ OptionType?: string;
17
+ Root?: string;
18
+ Underlying?: string;
19
+ }>;
20
+ Errors: Array<{
21
+ Symbol: string;
22
+ Error: string;
23
+ }>;
24
+ }
25
+ /**
26
+ * Aggressor side of a trade
27
+ */
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;
49
+ }
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
+ /**
59
+ * TradeStationClient handles real-time streaming connections to the TradeStation API
60
+ * via HTTP chunked transfer encoding.
61
+ *
62
+ * @remarks
63
+ * This client manages HTTP streaming connections to TradeStation's market data API,
64
+ * normalizes incoming quote data, and emits events for upstream consumption by
65
+ * the FloeClient.
66
+ *
67
+ * TradeStation uses HTTP streaming (chunked transfer encoding) instead of WebSockets.
68
+ * Each stream is a long-lived HTTP connection that returns JSON objects separated
69
+ * by newlines.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const client = new TradeStationClient({
74
+ * accessToken: 'your-oauth-access-token'
75
+ * });
76
+ *
77
+ * client.on('tickerUpdate', (ticker) => {
78
+ * console.log(`${ticker.symbol}: ${ticker.spot}`);
79
+ * });
80
+ *
81
+ * await client.connect();
82
+ * client.subscribe(['MSFT', 'AAPL']);
83
+ * ```
84
+ */
85
+ export declare class TradeStationClient {
86
+ /** TradeStation OAuth access token */
87
+ private accessToken;
88
+ /** Connection state */
89
+ private connected;
90
+ /** Currently subscribed ticker symbols */
91
+ private subscribedTickers;
92
+ /** Currently subscribed option symbols */
93
+ private subscribedOptions;
94
+ /** Active AbortControllers for streams */
95
+ 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
+ /** TradeStation API base URL */
115
+ private readonly apiBaseUrl;
116
+ /** Whether to use simulation environment */
117
+ private readonly simulation;
118
+ /** Refresh token for token refresh */
119
+ private refreshToken;
120
+ /** Token refresh callback */
121
+ private onTokenRefresh;
122
+ /** Whether to log verbose debug information */
123
+ private readonly verbose;
124
+ /**
125
+ * Creates a new TradeStationClient instance.
126
+ *
127
+ * @param options - Client configuration options
128
+ * @param options.accessToken - TradeStation OAuth access token (required)
129
+ * @param options.refreshToken - OAuth refresh token for automatic token renewal
130
+ * @param options.simulation - Whether to use simulation environment (default: false)
131
+ * @param options.onTokenRefresh - Callback when token is refreshed
132
+ * @param options.verbose - Whether to log verbose debug information (default: false)
133
+ */
134
+ constructor(options: {
135
+ accessToken: string;
136
+ refreshToken?: string;
137
+ simulation?: boolean;
138
+ onTokenRefresh?: (newToken: string) => void;
139
+ verbose?: boolean;
140
+ });
141
+ /**
142
+ * Establishes connection state for TradeStation streaming.
143
+ *
144
+ * @returns Promise that resolves when ready to stream
145
+ * @throws {Error} If token validation fails
146
+ *
147
+ * @remarks
148
+ * Unlike WebSocket-based clients, TradeStation uses HTTP streaming.
149
+ * This method validates the access token by making a test API call.
150
+ */
151
+ connect(): Promise<void>;
152
+ /**
153
+ * Disconnects from all TradeStation streaming APIs.
154
+ */
155
+ disconnect(): void;
156
+ /**
157
+ * Subscribes to real-time updates for the specified symbols.
158
+ *
159
+ * @param symbols - Array of ticker symbols and/or option symbols
160
+ *
161
+ * @remarks
162
+ * TradeStation uses different streaming endpoints for equities and options.
163
+ * This method automatically routes symbols to the appropriate endpoint.
164
+ *
165
+ * Option symbols can be in either:
166
+ * - TradeStation format: "MSFT 220916C305"
167
+ * - OCC format: "MSFT220916C00305000"
168
+ */
169
+ subscribe(symbols: string[]): void;
170
+ /**
171
+ * Unsubscribes from real-time updates for the specified symbols.
172
+ *
173
+ * @param symbols - Array of symbols to unsubscribe from
174
+ *
175
+ * @remarks
176
+ * For TradeStation, unsubscribing requires stopping the stream and
177
+ * restarting with the remaining symbols.
178
+ */
179
+ unsubscribe(symbols: string[]): void;
180
+ /**
181
+ * Returns whether the client is currently connected.
182
+ */
183
+ isConnected(): boolean;
184
+ /**
185
+ * Fetches quote snapshots for the specified symbols.
186
+ *
187
+ * @param symbols - Array of symbols (max 100)
188
+ * @returns Array of normalized tickers
189
+ */
190
+ fetchQuotes(symbols: string[]): Promise<NormalizedTicker[]>;
191
+ /**
192
+ * Fetches option expirations for an underlying symbol.
193
+ *
194
+ * @param underlying - Underlying symbol (e.g., 'AAPL')
195
+ * @returns Array of expiration dates
196
+ */
197
+ fetchOptionExpirations(underlying: string): Promise<string[]>;
198
+ /**
199
+ * Streams option chain data for an underlying symbol.
200
+ *
201
+ * @param underlying - Underlying symbol (e.g., 'AAPL')
202
+ * @param options - Stream options
203
+ * @returns Promise that resolves when stream is established
204
+ */
205
+ streamOptionChain(underlying: string, options?: {
206
+ expiration?: string;
207
+ strikeProximity?: number;
208
+ enableGreeks?: boolean;
209
+ optionType?: 'All' | 'Call' | 'Put';
210
+ }): Promise<void>;
211
+ /**
212
+ * Fetches symbol details for the specified symbols.
213
+ *
214
+ * @param symbols - Array of symbols (max 50)
215
+ * @returns Symbol details response
216
+ */
217
+ fetchSymbolDetails(symbols: string[]): Promise<TradeStationSymbolDetails>;
218
+ /**
219
+ * Returns cached option data for a symbol.
220
+ */
221
+ getOption(occSymbol: string): NormalizedOption | undefined;
222
+ /**
223
+ * Returns all cached options.
224
+ */
225
+ getAllOptions(): Map<string, NormalizedOption>;
226
+ /**
227
+ * Returns cached ticker data for a symbol.
228
+ */
229
+ getTicker(symbol: string): NormalizedTicker | undefined;
230
+ /**
231
+ * Returns all cached tickers.
232
+ */
233
+ getAllTickers(): Map<string, NormalizedTicker>;
234
+ /**
235
+ * Registers an event listener.
236
+ */
237
+ on<T>(event: TradeStationClientEventType, listener: TradeStationEventListener<T>): this;
238
+ /**
239
+ * Removes an event listener.
240
+ */
241
+ off<T>(event: TradeStationClientEventType, listener: TradeStationEventListener<T>): this;
242
+ /**
243
+ * Returns intraday trades for an option.
244
+ */
245
+ getIntradayTrades(occSymbol: string): IntradayTrade[];
246
+ /**
247
+ * Returns flow summary for an option.
248
+ */
249
+ getFlowSummary(occSymbol: string): {
250
+ buyVolume: number;
251
+ sellVolume: number;
252
+ unknownVolume: number;
253
+ netOIChange: number;
254
+ tradeCount: number;
255
+ };
256
+ /**
257
+ * Resets intraday tracking data.
258
+ */
259
+ resetIntradayData(occSymbols?: string[]): void;
260
+ /**
261
+ * Updates the access token (for token refresh scenarios).
262
+ */
263
+ updateAccessToken(newToken: string): void;
264
+ /**
265
+ * Gets authorization headers for API requests.
266
+ */
267
+ private getAuthHeaders;
268
+ /**
269
+ * Gets headers for streaming requests.
270
+ */
271
+ private getStreamHeaders;
272
+ /**
273
+ * Starts a quote stream for ticker symbols.
274
+ */
275
+ private startQuoteStream;
276
+ /**
277
+ * Starts an option quote stream.
278
+ */
279
+ private startOptionQuoteStream;
280
+ /**
281
+ * Starts an HTTP streaming connection.
282
+ */
283
+ private startHttpStream;
284
+ /**
285
+ * Processes an HTTP chunked stream.
286
+ */
287
+ private processStream;
288
+ /**
289
+ * Handles stream end/disconnect.
290
+ */
291
+ private handleStreamEnd;
292
+ /**
293
+ * Stops a stream by ID.
294
+ */
295
+ private stopStream;
296
+ /**
297
+ * Handles incoming quote stream data.
298
+ */
299
+ private handleQuoteData;
300
+ /**
301
+ * Handles incoming option quote stream data.
302
+ */
303
+ private handleOptionQuoteData;
304
+ /**
305
+ * Handles option chain stream data.
306
+ */
307
+ private handleOptionChainData;
308
+ /**
309
+ * Handles GoAway stream status - server is terminating stream.
310
+ */
311
+ private handleStreamGoAway;
312
+ /**
313
+ * Normalizes TradeStation quote to NormalizedTicker.
314
+ */
315
+ private normalizeQuote;
316
+ /**
317
+ * Normalizes TradeStation option quote to NormalizedOption.
318
+ */
319
+ private normalizeOptionQuote;
320
+ /**
321
+ * Converts TradeStation option symbol to OCC format.
322
+ * TradeStation format: "MSFT 220916C305" or "MSFT 220916C305.00"
323
+ * OCC format: "MSFT220916C00305000"
324
+ */
325
+ private toOCCSymbol;
326
+ /**
327
+ * Converts OCC symbol to TradeStation format.
328
+ * OCC format: "MSFT220916C00305000"
329
+ * TradeStation format: "MSFT 220916C305"
330
+ */
331
+ private toTradeStationOptionSymbol;
332
+ /**
333
+ * Determines aggressor side from trade price vs NBBO.
334
+ */
335
+ private determineAggressorSide;
336
+ /**
337
+ * Calculates estimated OI change from trade.
338
+ */
339
+ private calculateOIChangeFromTrade;
340
+ /**
341
+ * Calculates live open interest.
342
+ */
343
+ private calculateLiveOpenInterest;
344
+ /**
345
+ * Checks if a symbol is an option symbol.
346
+ */
347
+ private isOptionSymbol;
348
+ /**
349
+ * Parses a numeric string value.
350
+ */
351
+ private parseNumber;
352
+ /**
353
+ * Emits an event to all listeners.
354
+ */
355
+ private emit;
356
+ /**
357
+ * Sleep utility.
358
+ */
359
+ private sleep;
360
+ }
361
+ export {};