@fullstackcraftllc/floe 0.0.8 → 0.0.9

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,5 @@
1
- import { NormalizedOption } from '../../types';
1
+ import { BaseBrokerClient, BaseBrokerClientOptions, AggressorSide, IntradayTrade, FlowSummary } from './BaseBrokerClient';
2
+ export type { AggressorSide, IntradayTrade, FlowSummary };
2
3
  /**
3
4
  * Schwab option chain response
4
5
  */
@@ -104,38 +105,12 @@ interface SchwabOptionContract {
104
105
  mini: boolean;
105
106
  }
106
107
  /**
107
- * Aggressor side of a trade
108
+ * Schwab client configuration options
108
109
  */
109
- export type AggressorSide = 'buy' | 'sell' | 'unknown';
110
- /**
111
- * Intraday trade information with aggressor classification
112
- */
113
- export interface IntradayTrade {
114
- /** OCC option symbol */
115
- occSymbol: string;
116
- /** Trade price */
117
- price: number;
118
- /** Trade size (number of contracts) */
119
- size: number;
120
- /** Bid at time of trade */
121
- bid: number;
122
- /** Ask at time of trade */
123
- ask: number;
124
- /** Aggressor side determined from price vs NBBO */
125
- aggressorSide: AggressorSide;
126
- /** Timestamp of the trade */
127
- timestamp: number;
128
- /** Estimated OI change */
129
- estimatedOIChange: number;
110
+ export interface SchwabClientOptions extends BaseBrokerClientOptions {
111
+ /** Schwab OAuth access token (required) */
112
+ accessToken: string;
130
113
  }
131
- /**
132
- * Event types emitted by SchwabClient
133
- */
134
- type SchwabClientEventType = 'tickerUpdate' | 'optionUpdate' | 'optionTrade' | 'connected' | 'disconnected' | 'error';
135
- /**
136
- * Event listener callback type
137
- */
138
- type SchwabEventListener<T> = (data: T) => void;
139
114
  /**
140
115
  * SchwabClient handles real-time streaming connections to the Charles Schwab API
141
116
  * via WebSockets.
@@ -165,7 +140,8 @@ type SchwabEventListener<T> = (data: T) => void;
165
140
  * client.subscribe(['SPY', 'SPY 240517C00500000']); // Equity and option
166
141
  * ```
167
142
  */
168
- export declare class SchwabClient {
143
+ export declare class SchwabClient extends BaseBrokerClient {
144
+ protected readonly brokerName = "Schwab";
169
145
  /** Schwab OAuth access token */
170
146
  private accessToken;
171
147
  /** WebSocket connection */
@@ -182,36 +158,14 @@ export declare class SchwabClient {
182
158
  private streamSocketUrl;
183
159
  /** Request ID counter */
184
160
  private requestId;
185
- /** Currently subscribed symbols */
186
- private subscribedSymbols;
187
161
  /** Map from Schwab symbol to OCC symbol */
188
162
  private schwabToOccMap;
189
163
  /** Map from OCC symbol to Schwab symbol */
190
164
  private occToSchwabMap;
191
- /** Cached ticker data */
192
- private tickerCache;
193
- /** Cached option data */
194
- private optionCache;
195
- /** Base open interest from REST API */
196
- private baseOpenInterest;
197
- /** Cumulative estimated OI change from intraday trades */
198
- private cumulativeOIChange;
199
- /** History of intraday trades */
200
- private intradayTrades;
201
- /** Event listeners */
202
- private eventListeners;
203
- /** Reconnection attempt counter */
204
- private reconnectAttempts;
205
- /** Maximum reconnection attempts */
206
- private readonly maxReconnectAttempts;
207
- /** Reconnection delay in ms */
208
- private readonly baseReconnectDelay;
209
165
  /** Keepalive interval handle */
210
166
  private keepaliveInterval;
211
167
  /** Schwab API base URL */
212
168
  private readonly apiBaseUrl;
213
- /** Whether to log verbose debug information */
214
- private readonly verbose;
215
169
  /**
216
170
  * Creates a new SchwabClient instance.
217
171
  *
@@ -219,10 +173,7 @@ export declare class SchwabClient {
219
173
  * @param options.accessToken - Schwab OAuth access token (required)
220
174
  * @param options.verbose - Whether to log verbose debug information (default: false)
221
175
  */
222
- constructor(options: {
223
- accessToken: string;
224
- verbose?: boolean;
225
- });
176
+ constructor(options: SchwabClientOptions);
226
177
  /**
227
178
  * Establishes a streaming connection to Schwab.
228
179
  *
@@ -280,40 +231,6 @@ export declare class SchwabClient {
280
231
  * @param occSymbols - Array of OCC option symbols to fetch data for
281
232
  */
282
233
  fetchOpenInterest(occSymbols: string[]): Promise<void>;
283
- /**
284
- * Returns cached option data for a symbol.
285
- */
286
- getOption(occSymbol: string): NormalizedOption | undefined;
287
- /**
288
- * Returns all cached options.
289
- */
290
- getAllOptions(): Map<string, NormalizedOption>;
291
- /**
292
- * Registers an event listener.
293
- */
294
- on<T>(event: SchwabClientEventType, listener: SchwabEventListener<T>): this;
295
- /**
296
- * Removes an event listener.
297
- */
298
- off<T>(event: SchwabClientEventType, listener: SchwabEventListener<T>): this;
299
- /**
300
- * Returns intraday trades for an option.
301
- */
302
- getIntradayTrades(occSymbol: string): IntradayTrade[];
303
- /**
304
- * Returns flow summary for an option.
305
- */
306
- getFlowSummary(occSymbol: string): {
307
- buyVolume: number;
308
- sellVolume: number;
309
- unknownVolume: number;
310
- netOIChange: number;
311
- tradeCount: number;
312
- };
313
- /**
314
- * Resets intraday tracking data.
315
- */
316
- resetIntradayData(occSymbols?: string[]): void;
317
234
  /**
318
235
  * Gets user preferences containing streaming info from Schwab.
319
236
  */
@@ -388,10 +305,6 @@ export declare class SchwabClient {
388
305
  * This provides depth of market which can indicate intraday activity.
389
306
  */
390
307
  private handleOptionsBook;
391
- /**
392
- * Records a trade and updates OI tracking.
393
- */
394
- private recordTrade;
395
308
  /**
396
309
  * Processes a contract from the option chain response.
397
310
  */
@@ -400,36 +313,19 @@ export declare class SchwabClient {
400
313
  * Starts keepalive interval.
401
314
  */
402
315
  private startKeepalive;
403
- /**
404
- * Determines aggressor side from trade price vs NBBO.
405
- */
406
- private determineAggressorSide;
407
- /**
408
- * Calculates estimated OI change from trade.
409
- */
410
- private calculateOIChangeFromTrade;
411
- /**
412
- * Calculates live open interest.
413
- */
414
- private calculateLiveOpenInterest;
415
316
  /**
416
317
  * Converts Schwab option symbol to OCC format.
417
318
  * Schwab format: "AAPL 240517C00170000" (6-char padded underlying)
418
319
  */
419
320
  private schwabToOcc;
420
- /**
421
- * Normalizes an OCC symbol to consistent format.
422
- * Removes extra spaces, ensures proper formatting.
423
- */
424
- private normalizeOccSymbol;
425
321
  /**
426
322
  * Converts OCC symbol to Schwab format (space-padded).
427
323
  */
428
324
  private toSchwabOptionSymbol;
429
325
  /**
430
- * Checks if symbol is an option symbol.
326
+ * Checks if symbol is an option symbol (Schwab format allows spaces).
431
327
  */
432
- private isOptionSymbol;
328
+ private isSchwabOptionSymbol;
433
329
  /**
434
330
  * Attempts to reconnect with exponential backoff.
435
331
  */
@@ -438,17 +334,4 @@ export declare class SchwabClient {
438
334
  * Sends a message to the WebSocket.
439
335
  */
440
336
  private sendMessage;
441
- /**
442
- * Emits an event to all listeners.
443
- */
444
- private emit;
445
- /**
446
- * Converts value to number, handling NaN and null.
447
- */
448
- private toNumber;
449
- /**
450
- * Sleep utility.
451
- */
452
- private sleep;
453
337
  }
454
- export {};
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SchwabClient = void 0;
4
4
  const occ_1 = require("../../utils/occ");
5
+ const BaseBrokerClient_1 = require("./BaseBrokerClient");
5
6
  /**
6
7
  * Schwab streaming field enums for LEVELONE_OPTIONS
7
8
  */
@@ -78,7 +79,7 @@ var OptionsBookFields;
78
79
  * Regex pattern to identify OCC option symbols
79
80
  * Schwab uses space-padded format: "AAPL 240517C00170000"
80
81
  */
81
- const OCC_OPTION_PATTERN = /^.{1,6}\s*\d{6}[CP]\d{8}$/;
82
+ const SCHWAB_OCC_OPTION_PATTERN = /^.{1,6}\s*\d{6}[CP]\d{8}$/;
82
83
  /**
83
84
  * SchwabClient handles real-time streaming connections to the Charles Schwab API
84
85
  * via WebSockets.
@@ -108,7 +109,7 @@ const OCC_OPTION_PATTERN = /^.{1,6}\s*\d{6}[CP]\d{8}$/;
108
109
  * client.subscribe(['SPY', 'SPY 240517C00500000']); // Equity and option
109
110
  * ```
110
111
  */
111
- class SchwabClient {
112
+ class SchwabClient extends BaseBrokerClient_1.BaseBrokerClient {
112
113
  /**
113
114
  * Creates a new SchwabClient instance.
114
115
  *
@@ -117,6 +118,8 @@ class SchwabClient {
117
118
  * @param options.verbose - Whether to log verbose debug information (default: false)
118
119
  */
119
120
  constructor(options) {
121
+ super(options);
122
+ this.brokerName = 'Schwab';
120
123
  /** WebSocket connection */
121
124
  this.ws = null;
122
125
  /** Connection state */
@@ -131,43 +134,15 @@ class SchwabClient {
131
134
  this.streamSocketUrl = null;
132
135
  /** Request ID counter */
133
136
  this.requestId = 0;
134
- /** Currently subscribed symbols */
135
- this.subscribedSymbols = new Set();
136
137
  /** Map from Schwab symbol to OCC symbol */
137
138
  this.schwabToOccMap = new Map();
138
139
  /** Map from OCC symbol to Schwab symbol */
139
140
  this.occToSchwabMap = new Map();
140
- /** Cached ticker data */
141
- this.tickerCache = new Map();
142
- /** Cached option data */
143
- this.optionCache = new Map();
144
- /** Base open interest from REST API */
145
- this.baseOpenInterest = new Map();
146
- /** Cumulative estimated OI change from intraday trades */
147
- this.cumulativeOIChange = new Map();
148
- /** History of intraday trades */
149
- this.intradayTrades = new Map();
150
- /** Event listeners */
151
- this.eventListeners = new Map();
152
- /** Reconnection attempt counter */
153
- this.reconnectAttempts = 0;
154
- /** Maximum reconnection attempts */
155
- this.maxReconnectAttempts = 5;
156
- /** Reconnection delay in ms */
157
- this.baseReconnectDelay = 1000;
158
141
  /** Keepalive interval handle */
159
142
  this.keepaliveInterval = null;
160
143
  /** Schwab API base URL */
161
144
  this.apiBaseUrl = 'https://api.schwabapi.com';
162
145
  this.accessToken = options.accessToken;
163
- this.verbose = options.verbose ?? false;
164
- // Initialize event listener maps
165
- this.eventListeners.set('tickerUpdate', new Set());
166
- this.eventListeners.set('optionUpdate', new Set());
167
- this.eventListeners.set('optionTrade', new Set());
168
- this.eventListeners.set('connected', new Set());
169
- this.eventListeners.set('disconnected', new Set());
170
- this.eventListeners.set('error', new Set());
171
146
  }
172
147
  // ==================== Public API ====================
173
148
  /**
@@ -233,7 +208,7 @@ class SchwabClient {
233
208
  const tickers = [];
234
209
  const options = [];
235
210
  for (const symbol of symbols) {
236
- if (this.isOptionSymbol(symbol)) {
211
+ if (this.isSchwabOptionSymbol(symbol)) {
237
212
  options.push(this.toSchwabOptionSymbol(symbol));
238
213
  }
239
214
  else {
@@ -261,7 +236,7 @@ class SchwabClient {
261
236
  const tickers = [];
262
237
  const options = [];
263
238
  for (const symbol of symbols) {
264
- if (this.isOptionSymbol(symbol)) {
239
+ if (this.isSchwabOptionSymbol(symbol)) {
265
240
  options.push(this.toSchwabOptionSymbol(symbol));
266
241
  }
267
242
  else {
@@ -281,7 +256,7 @@ class SchwabClient {
281
256
  */
282
257
  unsubscribeFromAll() {
283
258
  const allSymbols = Array.from(this.subscribedSymbols);
284
- const allOptionSymbols = allSymbols.filter(s => this.isOptionSymbol(s)).map(s => this.toSchwabOptionSymbol(s));
259
+ const allOptionSymbols = allSymbols.filter(s => this.isSchwabOptionSymbol(s)).map(s => this.toSchwabOptionSymbol(s));
285
260
  this.subscribedSymbols.clear();
286
261
  // unsub from all equities
287
262
  if (allSymbols.length > 0) {
@@ -395,101 +370,41 @@ class SchwabClient {
395
370
  });
396
371
  await Promise.all(fetchPromises);
397
372
  }
398
- /**
399
- * Returns cached option data for a symbol.
400
- */
401
- getOption(occSymbol) {
402
- return this.optionCache.get(this.normalizeOccSymbol(occSymbol));
403
- }
404
- /**
405
- * Returns all cached options.
406
- */
407
- getAllOptions() {
408
- return new Map(this.optionCache);
409
- }
410
- /**
411
- * Registers an event listener.
412
- */
413
- on(event, listener) {
414
- const listeners = this.eventListeners.get(event);
415
- if (listeners) {
416
- listeners.add(listener);
417
- }
418
- return this;
419
- }
420
- /**
421
- * Removes an event listener.
422
- */
423
- off(event, listener) {
424
- const listeners = this.eventListeners.get(event);
425
- if (listeners) {
426
- listeners.delete(listener);
427
- }
428
- return this;
429
- }
430
- /**
431
- * Returns intraday trades for an option.
432
- */
433
- getIntradayTrades(occSymbol) {
434
- return this.intradayTrades.get(this.normalizeOccSymbol(occSymbol)) ?? [];
435
- }
436
- /**
437
- * Returns flow summary for an option.
438
- */
439
- getFlowSummary(occSymbol) {
440
- const normalizedSymbol = this.normalizeOccSymbol(occSymbol);
441
- const trades = this.intradayTrades.get(normalizedSymbol) ?? [];
442
- let buyVolume = 0;
443
- let sellVolume = 0;
444
- let unknownVolume = 0;
445
- for (const trade of trades) {
446
- switch (trade.aggressorSide) {
447
- case 'buy':
448
- buyVolume += trade.size;
449
- break;
450
- case 'sell':
451
- sellVolume += trade.size;
452
- break;
453
- case 'unknown':
454
- unknownVolume += trade.size;
455
- break;
456
- }
457
- }
458
- return {
459
- buyVolume,
460
- sellVolume,
461
- unknownVolume,
462
- netOIChange: this.cumulativeOIChange.get(normalizedSymbol) ?? 0,
463
- tradeCount: trades.length,
464
- };
465
- }
466
- /**
467
- * Resets intraday tracking data.
468
- */
469
- resetIntradayData(occSymbols) {
470
- const symbolsToReset = occSymbols?.map(s => this.normalizeOccSymbol(s))
471
- ?? Array.from(this.intradayTrades.keys());
472
- for (const symbol of symbolsToReset) {
473
- this.intradayTrades.delete(symbol);
474
- this.cumulativeOIChange.set(symbol, 0);
475
- }
476
- }
477
373
  // ==================== Private Methods ====================
478
374
  /**
479
375
  * Gets user preferences containing streaming info from Schwab.
480
376
  */
481
377
  async getUserPreferences() {
482
378
  try {
483
- const response = await fetch(`${this.apiBaseUrl}/trader/v1/userPreference`, {
379
+ const url = `${this.apiBaseUrl}/trader/v1/userPreference`;
380
+ const headers = this.getAuthHeaders();
381
+ const authHeader = (headers['Authorization'] ?? headers['authorization'] ?? '');
382
+ const maskedAuth = typeof authHeader === 'string' && authHeader.length > 12 ? `${authHeader.slice(0, 12)}...` : authHeader;
383
+ if (this.verbose) {
384
+ console.debug('[Schwab] GET userPreference', url, { maskedAuthorization: maskedAuth, headerKeys: Object.keys(headers) });
385
+ }
386
+ const response = await fetch(url, {
484
387
  method: 'GET',
485
- headers: this.getAuthHeaders(),
388
+ headers,
486
389
  });
390
+ const text = await response.text();
487
391
  if (!response.ok) {
488
- const errorText = await response.text();
489
- this.emit('error', new Error(`Failed to get user preferences: ${response.statusText} - ${errorText}`));
392
+ if (this.verbose) {
393
+ console.error('[Schwab] userPreference failed', response.status, response.statusText, text);
394
+ }
395
+ let parsed = text;
396
+ try {
397
+ parsed = JSON.parse(text);
398
+ }
399
+ catch { }
400
+ this.emit('error', new Error(`Failed to get user preferences: ${response.status} ${response.statusText} - ${typeof parsed === 'string' ? parsed : JSON.stringify(parsed)}`));
490
401
  return null;
491
402
  }
492
- return await response.json();
403
+ const json = JSON.parse(text);
404
+ if (this.verbose) {
405
+ console.debug('[Schwab] userPreference success', json);
406
+ }
407
+ return json;
493
408
  }
494
409
  catch (error) {
495
410
  this.emit('error', error instanceof Error ? error : new Error(String(error)));
@@ -828,46 +743,6 @@ class SchwabClient {
828
743
  this.optionCache.set(occSymbol, existing);
829
744
  this.emit('optionUpdate', existing);
830
745
  }
831
- /**
832
- * Records a trade and updates OI tracking.
833
- */
834
- recordTrade(occSymbol, price, size, bid, ask, timestamp) {
835
- const aggressorSide = this.determineAggressorSide(price, bid, ask);
836
- // Get option type for OI calculation
837
- let optionType = 'call';
838
- try {
839
- const parsed = (0, occ_1.parseOCCSymbol)(occSymbol);
840
- optionType = parsed.optionType;
841
- }
842
- catch {
843
- const existing = this.optionCache.get(occSymbol);
844
- if (existing)
845
- optionType = existing.optionType;
846
- }
847
- const estimatedOIChange = this.calculateOIChangeFromTrade(aggressorSide, size, optionType);
848
- const currentChange = this.cumulativeOIChange.get(occSymbol) ?? 0;
849
- this.cumulativeOIChange.set(occSymbol, currentChange + estimatedOIChange);
850
- if (this.verbose && estimatedOIChange !== 0) {
851
- const baseOI = this.baseOpenInterest.get(occSymbol) ?? 0;
852
- const newLiveOI = Math.max(0, baseOI + currentChange + estimatedOIChange);
853
- console.log(`[Schwab:OI] ${occSymbol} trade: price=${price.toFixed(2)}, size=${size}, aggressor=${aggressorSide}, OI change=${estimatedOIChange > 0 ? '+' : ''}${estimatedOIChange}, liveOI=${newLiveOI} (base=${baseOI}, cumulative=${currentChange + estimatedOIChange})`);
854
- }
855
- const trade = {
856
- occSymbol,
857
- price,
858
- size,
859
- bid,
860
- ask,
861
- aggressorSide,
862
- timestamp,
863
- estimatedOIChange,
864
- };
865
- if (!this.intradayTrades.has(occSymbol)) {
866
- this.intradayTrades.set(occSymbol, []);
867
- }
868
- this.intradayTrades.get(occSymbol).push(trade);
869
- this.emit('optionTrade', trade);
870
- }
871
746
  /**
872
747
  * Processes a contract from the option chain response.
873
748
  */
@@ -930,38 +805,6 @@ class SchwabClient {
930
805
  }
931
806
  }, 60000); // Every 60 seconds
932
807
  }
933
- /**
934
- * Determines aggressor side from trade price vs NBBO.
935
- */
936
- determineAggressorSide(tradePrice, bid, ask) {
937
- if (bid <= 0 || ask <= 0)
938
- return 'unknown';
939
- const spread = ask - bid;
940
- const tolerance = spread > 0 ? spread * 0.001 : 0.001;
941
- if (tradePrice >= ask - tolerance) {
942
- return 'buy';
943
- }
944
- else if (tradePrice <= bid + tolerance) {
945
- return 'sell';
946
- }
947
- return 'unknown';
948
- }
949
- /**
950
- * Calculates estimated OI change from trade.
951
- */
952
- calculateOIChangeFromTrade(aggressorSide, size, _optionType) {
953
- if (aggressorSide === 'unknown')
954
- return 0;
955
- return aggressorSide === 'buy' ? size : -size;
956
- }
957
- /**
958
- * Calculates live open interest.
959
- */
960
- calculateLiveOpenInterest(occSymbol) {
961
- const baseOI = this.baseOpenInterest.get(occSymbol) ?? 0;
962
- const cumulativeChange = this.cumulativeOIChange.get(occSymbol) ?? 0;
963
- return Math.max(0, baseOI + cumulativeChange);
964
- }
965
808
  /**
966
809
  * Converts Schwab option symbol to OCC format.
967
810
  * Schwab format: "AAPL 240517C00170000" (6-char padded underlying)
@@ -975,20 +818,6 @@ class SchwabClient {
975
818
  // Just normalize by removing extra spaces and ensuring proper padding
976
819
  return this.normalizeOccSymbol(schwabSymbol);
977
820
  }
978
- /**
979
- * Normalizes an OCC symbol to consistent format.
980
- * Removes extra spaces, ensures proper formatting.
981
- */
982
- normalizeOccSymbol(symbol) {
983
- // Remove all spaces and reformat
984
- const stripped = symbol.replace(/\s+/g, '');
985
- // Match the parts: ROOT + YYMMDD + C/P + 8-digit strike
986
- const match = stripped.match(/^([A-Z]+)(\d{6})([CP])(\d{8})$/);
987
- if (match) {
988
- return `${match[1]}${match[2]}${match[3]}${match[4]}`;
989
- }
990
- return stripped;
991
- }
992
821
  /**
993
822
  * Converts OCC symbol to Schwab format (space-padded).
994
823
  */
@@ -1009,10 +838,10 @@ class SchwabClient {
1009
838
  return occSymbol;
1010
839
  }
1011
840
  /**
1012
- * Checks if symbol is an option symbol.
841
+ * Checks if symbol is an option symbol (Schwab format allows spaces).
1013
842
  */
1014
- isOptionSymbol(symbol) {
1015
- return OCC_OPTION_PATTERN.test(symbol) || /\d{6}[CP]\d{8}/.test(symbol.replace(/\s+/g, ''));
843
+ isSchwabOptionSymbol(symbol) {
844
+ return SCHWAB_OCC_OPTION_PATTERN.test(symbol) || /\d{6}[CP]\d{8}/.test(symbol.replace(/\s+/g, ''));
1016
845
  }
1017
846
  /**
1018
847
  * Attempts to reconnect with exponential backoff.
@@ -1023,10 +852,8 @@ class SchwabClient {
1023
852
  return;
1024
853
  }
1025
854
  this.reconnectAttempts++;
1026
- const delay = this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
1027
- if (this.verbose) {
1028
- console.log(`[Schwab:WS] Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
1029
- }
855
+ const delay = this.getReconnectDelay();
856
+ this.log(`Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
1030
857
  await this.sleep(delay);
1031
858
  try {
1032
859
  await this.connect();
@@ -1043,41 +870,5 @@ class SchwabClient {
1043
870
  this.ws.send(JSON.stringify(message));
1044
871
  }
1045
872
  }
1046
- /**
1047
- * Emits an event to all listeners.
1048
- */
1049
- emit(event, data) {
1050
- const listeners = this.eventListeners.get(event);
1051
- if (listeners) {
1052
- listeners.forEach(listener => {
1053
- try {
1054
- listener(data);
1055
- }
1056
- catch (error) {
1057
- console.error('Event listener error:', error);
1058
- }
1059
- });
1060
- }
1061
- }
1062
- /**
1063
- * Converts value to number, handling NaN and null.
1064
- */
1065
- toNumber(value) {
1066
- if (value === null || value === undefined)
1067
- return 0;
1068
- if (typeof value === 'number')
1069
- return isNaN(value) ? 0 : value;
1070
- if (typeof value === 'string') {
1071
- const num = parseFloat(value);
1072
- return isNaN(num) ? 0 : num;
1073
- }
1074
- return 0;
1075
- }
1076
- /**
1077
- * Sleep utility.
1078
- */
1079
- sleep(ms) {
1080
- return new Promise(resolve => setTimeout(resolve, ms));
1081
- }
1082
873
  }
1083
874
  exports.SchwabClient = SchwabClient;