@adaptic/utils 0.1.0 → 0.1.2

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.
Files changed (217) hide show
  1. package/README.md +153 -61
  2. package/dist/index.cjs +55882 -11299
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.mjs +55809 -11299
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/test.js +709 -383
  7. package/dist/test.js.map +1 -1
  8. package/dist/types/__tests__/alpaca-functions.test.d.ts +2 -0
  9. package/dist/types/__tests__/alpaca-functions.test.d.ts.map +1 -0
  10. package/dist/types/__tests__/api-endpoints.test.d.ts +2 -0
  11. package/dist/types/__tests__/api-endpoints.test.d.ts.map +1 -0
  12. package/dist/types/__tests__/asset-allocation.test.d.ts +2 -0
  13. package/dist/types/__tests__/asset-allocation.test.d.ts.map +1 -0
  14. package/dist/types/__tests__/auth-validator.test.d.ts +2 -0
  15. package/dist/types/__tests__/auth-validator.test.d.ts.map +1 -0
  16. package/dist/types/__tests__/cache.test.d.ts +2 -0
  17. package/dist/types/__tests__/cache.test.d.ts.map +1 -0
  18. package/dist/types/__tests__/errors.test.d.ts +2 -0
  19. package/dist/types/__tests__/errors.test.d.ts.map +1 -0
  20. package/dist/types/__tests__/financial-regression.test.d.ts +2 -0
  21. package/dist/types/__tests__/financial-regression.test.d.ts.map +1 -0
  22. package/dist/types/__tests__/format-tools.test.d.ts +2 -0
  23. package/dist/types/__tests__/format-tools.test.d.ts.map +1 -0
  24. package/dist/types/__tests__/http-keep-alive.test.d.ts +2 -0
  25. package/dist/types/__tests__/http-keep-alive.test.d.ts.map +1 -0
  26. package/dist/types/__tests__/http-timeout.test.d.ts +2 -0
  27. package/dist/types/__tests__/http-timeout.test.d.ts.map +1 -0
  28. package/dist/types/__tests__/logger.test.d.ts +2 -0
  29. package/dist/types/__tests__/logger.test.d.ts.map +1 -0
  30. package/dist/types/__tests__/market-time.test.d.ts +2 -0
  31. package/dist/types/__tests__/market-time.test.d.ts.map +1 -0
  32. package/dist/types/__tests__/misc-utils.test.d.ts +2 -0
  33. package/dist/types/__tests__/misc-utils.test.d.ts.map +1 -0
  34. package/dist/types/__tests__/paginator.test.d.ts +2 -0
  35. package/dist/types/__tests__/paginator.test.d.ts.map +1 -0
  36. package/dist/types/__tests__/performance-metrics.test.d.ts +2 -0
  37. package/dist/types/__tests__/performance-metrics.test.d.ts.map +1 -0
  38. package/dist/types/__tests__/polygon.test.d.ts +2 -0
  39. package/dist/types/__tests__/polygon.test.d.ts.map +1 -0
  40. package/dist/types/__tests__/price-utils.test.d.ts +2 -0
  41. package/dist/types/__tests__/price-utils.test.d.ts.map +1 -0
  42. package/dist/types/__tests__/property-based-financial.test.d.ts +2 -0
  43. package/dist/types/__tests__/property-based-financial.test.d.ts.map +1 -0
  44. package/dist/types/__tests__/rate-limiter.test.d.ts +2 -0
  45. package/dist/types/__tests__/rate-limiter.test.d.ts.map +1 -0
  46. package/dist/types/__tests__/schema-validation.test.d.ts +2 -0
  47. package/dist/types/__tests__/schema-validation.test.d.ts.map +1 -0
  48. package/dist/types/__tests__/technical-analysis.test.d.ts +2 -0
  49. package/dist/types/__tests__/technical-analysis.test.d.ts.map +1 -0
  50. package/dist/types/__tests__/time-utils.test.d.ts +2 -0
  51. package/dist/types/__tests__/time-utils.test.d.ts.map +1 -0
  52. package/dist/types/adaptic.d.ts +2 -2
  53. package/dist/types/adaptic.d.ts.map +1 -1
  54. package/dist/types/alpaca/client.d.ts +4 -4
  55. package/dist/types/alpaca/client.d.ts.map +1 -1
  56. package/dist/types/alpaca/crypto/data.d.ts +5 -5
  57. package/dist/types/alpaca/crypto/data.d.ts.map +1 -1
  58. package/dist/types/alpaca/crypto/index.d.ts +2 -2
  59. package/dist/types/alpaca/crypto/orders.d.ts +2 -2
  60. package/dist/types/alpaca/crypto/orders.d.ts.map +1 -1
  61. package/dist/types/alpaca/index.d.ts +36 -34
  62. package/dist/types/alpaca/index.d.ts.map +1 -1
  63. package/dist/types/alpaca/legacy/account.d.ts +34 -0
  64. package/dist/types/alpaca/legacy/account.d.ts.map +1 -0
  65. package/dist/types/alpaca/legacy/assets.d.ts +13 -0
  66. package/dist/types/alpaca/legacy/assets.d.ts.map +1 -0
  67. package/dist/types/alpaca/legacy/auth.d.ts +18 -0
  68. package/dist/types/alpaca/legacy/auth.d.ts.map +1 -0
  69. package/dist/types/alpaca/legacy/index.d.ts +15 -0
  70. package/dist/types/alpaca/legacy/index.d.ts.map +1 -0
  71. package/dist/types/alpaca/legacy/market-data.d.ts +32 -0
  72. package/dist/types/alpaca/legacy/market-data.d.ts.map +1 -0
  73. package/dist/types/alpaca/legacy/orders.d.ts +84 -0
  74. package/dist/types/alpaca/legacy/orders.d.ts.map +1 -0
  75. package/dist/types/alpaca/legacy/positions.d.ts +66 -0
  76. package/dist/types/alpaca/legacy/positions.d.ts.map +1 -0
  77. package/dist/types/alpaca/legacy/utils.d.ts +18 -0
  78. package/dist/types/alpaca/legacy/utils.d.ts.map +1 -0
  79. package/dist/types/alpaca/market-data/bars.d.ts +4 -4
  80. package/dist/types/alpaca/market-data/bars.d.ts.map +1 -1
  81. package/dist/types/alpaca/market-data/index.d.ts +8 -8
  82. package/dist/types/alpaca/market-data/news.d.ts +3 -3
  83. package/dist/types/alpaca/market-data/news.d.ts.map +1 -1
  84. package/dist/types/alpaca/market-data/quotes.d.ts +2 -2
  85. package/dist/types/alpaca/market-data/quotes.d.ts.map +1 -1
  86. package/dist/types/alpaca/market-data/trades.d.ts +2 -2
  87. package/dist/types/alpaca/market-data/trades.d.ts.map +1 -1
  88. package/dist/types/alpaca/options/contracts.d.ts +2 -2
  89. package/dist/types/alpaca/options/contracts.d.ts.map +1 -1
  90. package/dist/types/alpaca/options/data.d.ts +5 -5
  91. package/dist/types/alpaca/options/data.d.ts.map +1 -1
  92. package/dist/types/alpaca/options/index.d.ts +12 -12
  93. package/dist/types/alpaca/options/index.d.ts.map +1 -1
  94. package/dist/types/alpaca/options/orders.d.ts +4 -4
  95. package/dist/types/alpaca/options/orders.d.ts.map +1 -1
  96. package/dist/types/alpaca/options/strategies.d.ts +9 -9
  97. package/dist/types/alpaca/options/strategies.d.ts.map +1 -1
  98. package/dist/types/alpaca/streams/base-stream.d.ts +5 -5
  99. package/dist/types/alpaca/streams/base-stream.d.ts.map +1 -1
  100. package/dist/types/alpaca/streams/crypto-stream.d.ts +5 -5
  101. package/dist/types/alpaca/streams/crypto-stream.d.ts.map +1 -1
  102. package/dist/types/alpaca/streams/index.d.ts +6 -6
  103. package/dist/types/alpaca/streams/option-stream.d.ts +5 -5
  104. package/dist/types/alpaca/streams/option-stream.d.ts.map +1 -1
  105. package/dist/types/alpaca/streams/stock-stream.d.ts +5 -5
  106. package/dist/types/alpaca/streams/stock-stream.d.ts.map +1 -1
  107. package/dist/types/alpaca/streams/stream-manager.d.ts +16 -16
  108. package/dist/types/alpaca/streams/stream-manager.d.ts.map +1 -1
  109. package/dist/types/alpaca/streams/trading-stream.d.ts +28 -28
  110. package/dist/types/alpaca/streams/trading-stream.d.ts.map +1 -1
  111. package/dist/types/alpaca/streams.d.ts +2 -2
  112. package/dist/types/alpaca/streams.d.ts.map +1 -1
  113. package/dist/types/alpaca/trading/account.d.ts +2 -2
  114. package/dist/types/alpaca/trading/account.d.ts.map +1 -1
  115. package/dist/types/alpaca/trading/bracket-orders.d.ts +3 -3
  116. package/dist/types/alpaca/trading/bracket-orders.d.ts.map +1 -1
  117. package/dist/types/alpaca/trading/index.d.ts +12 -12
  118. package/dist/types/alpaca/trading/oco-orders.d.ts +2 -2
  119. package/dist/types/alpaca/trading/oco-orders.d.ts.map +1 -1
  120. package/dist/types/alpaca/trading/order-utils.d.ts +9 -9
  121. package/dist/types/alpaca/trading/order-utils.d.ts.map +1 -1
  122. package/dist/types/alpaca/trading/orders.d.ts +2 -2
  123. package/dist/types/alpaca/trading/orders.d.ts.map +1 -1
  124. package/dist/types/alpaca/trading/oto-orders.d.ts +3 -3
  125. package/dist/types/alpaca/trading/oto-orders.d.ts.map +1 -1
  126. package/dist/types/alpaca/trading/positions.d.ts +3 -3
  127. package/dist/types/alpaca/trading/positions.d.ts.map +1 -1
  128. package/dist/types/alpaca/trading/smart-orders.d.ts +12 -12
  129. package/dist/types/alpaca/trading/smart-orders.d.ts.map +1 -1
  130. package/dist/types/alpaca/trading/trailing-stops.d.ts +2 -2
  131. package/dist/types/alpaca/trading/trailing-stops.d.ts.map +1 -1
  132. package/dist/types/alpaca-market-data-api.d.ts +10 -10
  133. package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
  134. package/dist/types/alpaca-trading-api.d.ts +12 -12
  135. package/dist/types/alpaca-trading-api.d.ts.map +1 -1
  136. package/dist/types/alphavantage.d.ts +1 -1
  137. package/dist/types/alphavantage.d.ts.map +1 -1
  138. package/dist/types/asset-allocation-algorithm.d.ts +7 -1
  139. package/dist/types/asset-allocation-algorithm.d.ts.map +1 -1
  140. package/dist/types/cache/stampede-protected-cache.d.ts.map +1 -1
  141. package/dist/types/config/api-endpoints.d.ts +94 -0
  142. package/dist/types/config/api-endpoints.d.ts.map +1 -0
  143. package/dist/types/crypto.d.ts +2 -2
  144. package/dist/types/crypto.d.ts.map +1 -1
  145. package/dist/types/display-manager.d.ts +1 -1
  146. package/dist/types/display-manager.d.ts.map +1 -1
  147. package/dist/types/errors/index.d.ts +130 -0
  148. package/dist/types/errors/index.d.ts.map +1 -0
  149. package/dist/types/examples/asset-allocation-example.d.ts +7 -6
  150. package/dist/types/examples/asset-allocation-example.d.ts.map +1 -1
  151. package/dist/types/examples/rate-limiter-example.d.ts +7 -0
  152. package/dist/types/examples/rate-limiter-example.d.ts.map +1 -0
  153. package/dist/types/format-tools.d.ts.map +1 -1
  154. package/dist/types/http-timeout.d.ts +37 -0
  155. package/dist/types/http-timeout.d.ts.map +1 -0
  156. package/dist/types/index.d.ts +59 -72
  157. package/dist/types/index.d.ts.map +1 -1
  158. package/dist/types/logger.d.ts +56 -0
  159. package/dist/types/logger.d.ts.map +1 -0
  160. package/dist/types/logging.d.ts +1 -1
  161. package/dist/types/logging.d.ts.map +1 -1
  162. package/dist/types/market-hours.d.ts.map +1 -1
  163. package/dist/types/market-time.d.ts +75 -13
  164. package/dist/types/market-time.d.ts.map +1 -1
  165. package/dist/types/metrics-calcs.d.ts.map +1 -1
  166. package/dist/types/misc-utils.d.ts +4 -1
  167. package/dist/types/misc-utils.d.ts.map +1 -1
  168. package/dist/types/performance-metrics.d.ts +4 -4
  169. package/dist/types/performance-metrics.d.ts.map +1 -1
  170. package/dist/types/polygon-indices.d.ts +3 -3
  171. package/dist/types/polygon-indices.d.ts.map +1 -1
  172. package/dist/types/polygon.d.ts +1 -1
  173. package/dist/types/polygon.d.ts.map +1 -1
  174. package/dist/types/price-utils.d.ts.map +1 -1
  175. package/dist/types/rate-limiter.d.ts +171 -0
  176. package/dist/types/rate-limiter.d.ts.map +1 -0
  177. package/dist/types/schemas/alpaca-schemas.d.ts +779 -0
  178. package/dist/types/schemas/alpaca-schemas.d.ts.map +1 -0
  179. package/dist/types/schemas/alphavantage-schemas.d.ts +255 -0
  180. package/dist/types/schemas/alphavantage-schemas.d.ts.map +1 -0
  181. package/dist/types/schemas/index.d.ts +21 -0
  182. package/dist/types/schemas/index.d.ts.map +1 -0
  183. package/dist/types/schemas/polygon-schemas.d.ts +551 -0
  184. package/dist/types/schemas/polygon-schemas.d.ts.map +1 -0
  185. package/dist/types/schemas/validate-response.d.ts +88 -0
  186. package/dist/types/schemas/validate-response.d.ts.map +1 -0
  187. package/dist/types/technical-analysis.d.ts +9 -9
  188. package/dist/types/technical-analysis.d.ts.map +1 -1
  189. package/dist/types/time-utils.d.ts.map +1 -1
  190. package/dist/types/types/adaptic-types.d.ts +1 -1
  191. package/dist/types/types/adaptic-types.d.ts.map +1 -1
  192. package/dist/types/types/alpaca-types.d.ts +172 -98
  193. package/dist/types/types/alpaca-types.d.ts.map +1 -1
  194. package/dist/types/types/alphavantage-types.d.ts +2 -2
  195. package/dist/types/types/asset-allocation-types.d.ts +11 -11
  196. package/dist/types/types/index.d.ts +8 -8
  197. package/dist/types/types/index.d.ts.map +1 -1
  198. package/dist/types/types/logging-types.d.ts +2 -2
  199. package/dist/types/types/logging-types.d.ts.map +1 -1
  200. package/dist/types/types/market-time-types.d.ts +4 -4
  201. package/dist/types/types/market-time-types.d.ts.map +1 -1
  202. package/dist/types/types/metrics-types.d.ts +3 -3
  203. package/dist/types/types/metrics-types.d.ts.map +1 -1
  204. package/dist/types/types/polygon-indices-types.d.ts +6 -6
  205. package/dist/types/types/polygon-types.d.ts +3 -3
  206. package/dist/types/types/ta-types.d.ts +3 -3
  207. package/dist/types/utils/auth-validator.d.ts +27 -0
  208. package/dist/types/utils/auth-validator.d.ts.map +1 -0
  209. package/dist/types/utils/http-keep-alive.d.ts +110 -0
  210. package/dist/types/utils/http-keep-alive.d.ts.map +1 -0
  211. package/dist/types/utils/paginator.d.ts +154 -0
  212. package/dist/types/utils/paginator.d.ts.map +1 -0
  213. package/dist/types/utils/retry.d.ts +78 -0
  214. package/dist/types/utils/retry.d.ts.map +1 -0
  215. package/package.json +22 -5
  216. package/dist/types/alpaca-functions.d.ts +0 -233
  217. package/dist/types/alpaca-functions.d.ts.map +0 -1
package/dist/test.js CHANGED
@@ -493,7 +493,7 @@ createChalk({level: stderrColor ? stderrColor.level : 0});
493
493
 
494
494
  class DisplayManager {
495
495
  static instance;
496
- promptText = '';
496
+ promptText = "";
497
497
  constructor() { }
498
498
  static getInstance() {
499
499
  if (!DisplayManager.instance) {
@@ -513,20 +513,22 @@ class DisplayManager {
513
513
  cursorTo(process.stdout, 0);
514
514
  // Format the timestamp
515
515
  const date = new Date();
516
- const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
516
+ const timestamp = date.toLocaleString("en-US", {
517
+ timeZone: "America/New_York",
518
+ });
517
519
  const account = options?.account;
518
520
  const symbol = options?.symbol;
519
521
  // Build the log message
520
- let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
522
+ let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ""}${account ? ` [${account}] ` : ""}${symbol ? ` [${symbol}] ` : ""}${message}`;
521
523
  // Add color based on type
522
- if (options?.type === 'error') {
524
+ if (options?.type === "error") {
523
525
  logMessage = chalk.red(logMessage);
524
526
  }
525
- else if (options?.type === 'warn') {
527
+ else if (options?.type === "warn") {
526
528
  logMessage = chalk.yellow(logMessage);
527
529
  }
528
530
  // Write the log message
529
- process.stdout.write(logMessage + '\n');
531
+ process.stdout.write(logMessage + "\n");
530
532
  // Log to file
531
533
  if (symbol) {
532
534
  // Log to symbol-specific file if symbol is provided
@@ -545,20 +547,20 @@ class DisplayManager {
545
547
  writeSymbolLog(symbol, date, logMessage, options) {
546
548
  try {
547
549
  // Create logs directory if it doesn't exist
548
- if (!fs.existsSync('logs')) {
549
- fs.mkdirSync('logs', { recursive: true });
550
+ if (!fs.existsSync("logs")) {
551
+ fs.mkdirSync("logs", { recursive: true });
550
552
  }
551
553
  // Format date for filename: YYYY-MM-DD
552
554
  const year = date.getFullYear();
553
- const month = String(date.getMonth() + 1).padStart(2, '0');
554
- const day = String(date.getDate()).padStart(2, '0');
555
+ const month = String(date.getMonth() + 1).padStart(2, "0");
556
+ const day = String(date.getDate()).padStart(2, "0");
555
557
  // Create filename: SYM-YYYY-MM-DD.log
556
558
  const filename = `${symbol}-${year}-${month}-${day}.log`;
557
- const filePath = path.join('logs', filename);
559
+ const filePath = path.join("logs", filename);
558
560
  // Strip ANSI color codes from log message
559
- const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, '');
561
+ const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, "");
560
562
  // Write to file (append if exists, create if not)
561
- fs.appendFileSync(filePath, plainLogMessage + '\n');
563
+ fs.appendFileSync(filePath, plainLogMessage + "\n");
562
564
  }
563
565
  catch (error) {
564
566
  // Only log to console - don't try to log to file again to avoid potential infinite loop
@@ -571,21 +573,21 @@ class DisplayManager {
571
573
  writeGenericLog(date, logMessage, options) {
572
574
  try {
573
575
  // Create logs directory if it doesn't exist
574
- if (!fs.existsSync('logs')) {
575
- fs.mkdirSync('logs', { recursive: true });
576
+ if (!fs.existsSync("logs")) {
577
+ fs.mkdirSync("logs", { recursive: true });
576
578
  }
577
579
  // Format date for filename: YYYY-MM-DD
578
580
  const year = date.getFullYear();
579
- const month = String(date.getMonth() + 1).padStart(2, '0');
580
- const day = String(date.getDate()).padStart(2, '0');
581
+ const month = String(date.getMonth() + 1).padStart(2, "0");
582
+ const day = String(date.getDate()).padStart(2, "0");
581
583
  // Create filename: system-YYYY-MM-DD.log
582
- const source = options?.source?.toLowerCase().replace(/\s+/g, '-') || 'system';
584
+ const source = options?.source?.toLowerCase().replace(/\s+/g, "-") || "system";
583
585
  const filename = `${source}-${year}-${month}-${day}.log`;
584
- const filePath = path.join('logs', filename);
586
+ const filePath = path.join("logs", filename);
585
587
  // Strip ANSI color codes from log message
586
- const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, '');
588
+ const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, "");
587
589
  // Write to file (append if exists, create if not)
588
- fs.appendFileSync(filePath, plainLogMessage + '\n');
590
+ fs.appendFileSync(filePath, plainLogMessage + "\n");
589
591
  }
590
592
  catch (error) {
591
593
  // Only log to console - don't try to log to file again to avoid potential infinite loop
@@ -613,112 +615,206 @@ class DisplayManager {
613
615
  * @param options.symbol The trading symbol associated with this log.
614
616
  * @param options.logToFile Force logging to a file even when no symbol is provided.
615
617
  */
616
- function log$2(message, options = { source: 'Server', type: 'info' }) {
618
+ function log$2(message, options = { source: "Server", type: "info" }) {
617
619
  const displayManager = DisplayManager.getInstance();
618
620
  displayManager.log(message, options);
619
621
  }
620
622
 
623
+ /**
624
+ * Centralized API Endpoints Configuration
625
+ *
626
+ * This file defines all Alpaca API base URLs to ensure consistency
627
+ * across the codebase and make updates easier.
628
+ *
629
+ * API Version Guidelines:
630
+ * - Trading API: v2 (stable, production-ready)
631
+ * - Market Data (stocks): v2 (stable)
632
+ * - Market Data (crypto): v1beta3 (latest beta)
633
+ * - Market Data (options): v1beta1 (latest beta)
634
+ * - News API: v1beta1 (latest beta)
635
+ */
636
+ /**
637
+ * Trading API base URLs (v2)
638
+ * Used for orders, positions, account management
639
+ */
640
+ const TRADING_API = {
641
+ PAPER: "https://paper-api.alpaca.markets/v2",
642
+ LIVE: "https://api.alpaca.markets/v2",
643
+ };
644
+ /**
645
+ * Get trading API base URL for account type
646
+ */
647
+ function getTradingApiUrl(accountType) {
648
+ return TRADING_API[accountType];
649
+ }
650
+ /**
651
+ * Market Data API base URLs
652
+ */
653
+ const MARKET_DATA_API = {
654
+ /** Stock market data (v2) - bars, quotes, trades */
655
+ STOCKS: "https://data.alpaca.markets/v2",
656
+ /** Options market data (v1beta1) */
657
+ OPTIONS: "https://data.alpaca.markets/v1beta1"};
658
+ /**
659
+ * WebSocket stream URLs
660
+ */
661
+ const WEBSOCKET_STREAMS = {
662
+ /** Stock market data stream (v2) */
663
+ STOCKS: {
664
+ PRODUCTION: "wss://stream.data.alpaca.markets/v2/sip",
665
+ TEST: "wss://stream.data.alpaca.markets/v2/test",
666
+ },
667
+ /** Options market data stream (v1beta3) */
668
+ OPTIONS: {
669
+ PRODUCTION: "wss://stream.data.alpaca.markets/v1beta3/options",
670
+ SANDBOX: "wss://stream.data.sandbox.alpaca.markets/v1beta3/options",
671
+ },
672
+ /** Crypto market data stream (v1beta3) */
673
+ CRYPTO: {
674
+ PRODUCTION: "wss://stream.data.alpaca.markets/v1beta3/crypto/us",
675
+ SANDBOX: "wss://stream.data.sandbox.alpaca.markets/v1beta3/crypto/us",
676
+ },
677
+ };
678
+ /**
679
+ * Get stock stream WebSocket URL
680
+ */
681
+ function getStockStreamUrl(mode = "PRODUCTION") {
682
+ return WEBSOCKET_STREAMS.STOCKS[mode];
683
+ }
684
+ /**
685
+ * Get options stream WebSocket URL
686
+ */
687
+ function getOptionsStreamUrl(mode = "PRODUCTION") {
688
+ return WEBSOCKET_STREAMS.OPTIONS[mode];
689
+ }
690
+ /**
691
+ * Get crypto stream WebSocket URL
692
+ */
693
+ function getCryptoStreamUrl(mode = "PRODUCTION") {
694
+ return WEBSOCKET_STREAMS.CRYPTO[mode];
695
+ }
696
+
621
697
  // market-hours.ts
622
698
  const marketHolidays = {
623
699
  2024: {
624
- 'New Year\'s Day': { date: '2024-01-01' },
625
- 'Martin Luther King, Jr. Day': { date: '2024-01-15' },
626
- 'Washington\'s Birthday': { date: '2024-02-19' },
627
- 'Good Friday': { date: '2024-03-29' },
628
- 'Memorial Day': { date: '2024-05-27' },
629
- 'Juneteenth National Independence Day': { date: '2024-06-19' },
630
- 'Independence Day': { date: '2024-07-04' },
631
- 'Labor Day': { date: '2024-09-02' },
632
- 'Thanksgiving Day': { date: '2024-11-28' },
633
- 'Christmas Day': { date: '2024-12-25' }
700
+ "New Year's Day": { date: "2024-01-01" },
701
+ "Martin Luther King, Jr. Day": { date: "2024-01-15" },
702
+ "Washington's Birthday": { date: "2024-02-19" },
703
+ "Good Friday": { date: "2024-03-29" },
704
+ "Memorial Day": { date: "2024-05-27" },
705
+ "Juneteenth National Independence Day": { date: "2024-06-19" },
706
+ "Independence Day": { date: "2024-07-04" },
707
+ "Labor Day": { date: "2024-09-02" },
708
+ "Thanksgiving Day": { date: "2024-11-28" },
709
+ "Christmas Day": { date: "2024-12-25" },
634
710
  },
635
711
  2025: {
636
- 'New Year\'s Day': { date: '2025-01-01' },
637
- 'Jimmy Carter Memorial Day': { date: '2025-01-09' },
638
- 'Martin Luther King, Jr. Day': { date: '2025-01-20' },
639
- 'Washington\'s Birthday': { date: '2025-02-17' },
640
- 'Good Friday': { date: '2025-04-18' },
641
- 'Memorial Day': { date: '2025-05-26' },
642
- 'Juneteenth National Independence Day': { date: '2025-06-19' },
643
- 'Independence Day': { date: '2025-07-04' },
644
- 'Labor Day': { date: '2025-09-01' },
645
- 'Thanksgiving Day': { date: '2025-11-27' },
646
- 'Christmas Day': { date: '2025-12-25' }
712
+ "New Year's Day": { date: "2025-01-01" },
713
+ "Jimmy Carter Memorial Day": { date: "2025-01-09" },
714
+ "Martin Luther King, Jr. Day": { date: "2025-01-20" },
715
+ "Washington's Birthday": { date: "2025-02-17" },
716
+ "Good Friday": { date: "2025-04-18" },
717
+ "Memorial Day": { date: "2025-05-26" },
718
+ "Juneteenth National Independence Day": { date: "2025-06-19" },
719
+ "Independence Day": { date: "2025-07-04" },
720
+ "Labor Day": { date: "2025-09-01" },
721
+ "Thanksgiving Day": { date: "2025-11-27" },
722
+ "Christmas Day": { date: "2025-12-25" },
647
723
  },
648
724
  2026: {
649
- 'New Year\'s Day': { date: '2026-01-01' },
650
- 'Martin Luther King, Jr. Day': { date: '2026-01-19' },
651
- 'Washington\'s Birthday': { date: '2026-02-16' },
652
- 'Good Friday': { date: '2026-04-03' },
653
- 'Memorial Day': { date: '2026-05-25' },
654
- 'Juneteenth National Independence Day': { date: '2026-06-19' },
655
- 'Independence Day': { date: '2026-07-03' },
656
- 'Labor Day': { date: '2026-09-07' },
657
- 'Thanksgiving Day': { date: '2026-11-26' },
658
- 'Christmas Day': { date: '2026-12-25' }
659
- }
725
+ "New Year's Day": { date: "2026-01-01" },
726
+ "Martin Luther King, Jr. Day": { date: "2026-01-19" },
727
+ "Washington's Birthday": { date: "2026-02-16" },
728
+ "Good Friday": { date: "2026-04-03" },
729
+ "Memorial Day": { date: "2026-05-25" },
730
+ "Juneteenth National Independence Day": { date: "2026-06-19" },
731
+ "Independence Day": { date: "2026-07-03" },
732
+ "Labor Day": { date: "2026-09-07" },
733
+ "Thanksgiving Day": { date: "2026-11-26" },
734
+ "Christmas Day": { date: "2026-12-25" },
735
+ },
736
+ 2027: {
737
+ "New Year's Day": { date: "2027-01-01" },
738
+ "Martin Luther King, Jr. Day": { date: "2027-01-18" },
739
+ "Washington's Birthday": { date: "2027-02-15" },
740
+ "Good Friday": { date: "2027-03-26" },
741
+ "Memorial Day": { date: "2027-05-31" },
742
+ "Juneteenth National Independence Day": { date: "2027-06-18" },
743
+ "Independence Day": { date: "2027-07-05" },
744
+ "Labor Day": { date: "2027-09-06" },
745
+ "Thanksgiving Day": { date: "2027-11-25" },
746
+ "Christmas Day": { date: "2027-12-24" },
747
+ },
660
748
  };
661
749
  const marketEarlyCloses = {
662
750
  2024: {
663
- '2024-07-03': {
664
- date: '2024-07-03',
665
- time: '13:00',
666
- optionsTime: '13:15',
667
- notes: 'Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
751
+ "2024-07-03": {
752
+ date: "2024-07-03",
753
+ time: "13:00",
754
+ optionsTime: "13:15",
755
+ notes: "Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
668
756
  },
669
- '2024-11-29': {
670
- date: '2024-11-29',
671
- time: '13:00',
672
- optionsTime: '13:15',
673
- notes: 'Market closes early on Friday, November 29, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
757
+ "2024-11-29": {
758
+ date: "2024-11-29",
759
+ time: "13:00",
760
+ optionsTime: "13:15",
761
+ notes: "Market closes early on Friday, November 29, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
762
+ },
763
+ "2024-12-24": {
764
+ date: "2024-12-24",
765
+ time: "13:00",
766
+ optionsTime: "13:15",
767
+ notes: "Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
674
768
  },
675
- '2024-12-24': {
676
- date: '2024-12-24',
677
- time: '13:00',
678
- optionsTime: '13:15',
679
- notes: 'Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
680
- }
681
769
  },
682
770
  2025: {
683
- '2025-07-03': {
684
- date: '2025-07-03',
685
- time: '13:00',
686
- optionsTime: '13:15',
687
- notes: 'Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
771
+ "2025-07-03": {
772
+ date: "2025-07-03",
773
+ time: "13:00",
774
+ optionsTime: "13:15",
775
+ notes: "Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
688
776
  },
689
- '2025-11-28': {
690
- date: '2025-11-28',
691
- time: '13:00',
692
- optionsTime: '13:15',
693
- notes: 'Market closes early on Friday, November 28, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
777
+ "2025-11-28": {
778
+ date: "2025-11-28",
779
+ time: "13:00",
780
+ optionsTime: "13:15",
781
+ notes: "Market closes early on Friday, November 28, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
782
+ },
783
+ "2025-12-24": {
784
+ date: "2025-12-24",
785
+ time: "13:00",
786
+ optionsTime: "13:15",
787
+ notes: "Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
694
788
  },
695
- '2025-12-24': {
696
- date: '2025-12-24',
697
- time: '13:00',
698
- optionsTime: '13:15',
699
- notes: 'Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
700
- }
701
789
  },
702
790
  2026: {
703
- '2026-07-02': {
704
- date: '2026-07-02',
705
- time: '13:00',
706
- optionsTime: '13:15',
707
- notes: 'Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
791
+ "2026-07-02": {
792
+ date: "2026-07-02",
793
+ time: "13:00",
794
+ optionsTime: "13:15",
795
+ notes: "Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
708
796
  },
709
- '2026-11-27': {
710
- date: '2026-11-27',
711
- time: '13:00',
712
- optionsTime: '13:15',
713
- notes: 'Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
797
+ "2026-11-27": {
798
+ date: "2026-11-27",
799
+ time: "13:00",
800
+ optionsTime: "13:15",
801
+ notes: "Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
714
802
  },
715
- '2026-12-24': {
716
- date: '2026-12-24',
717
- time: '13:00',
718
- optionsTime: '13:15',
719
- notes: 'Market closes early on Thursday, December 24, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
720
- }
721
- }
803
+ "2026-12-24": {
804
+ date: "2026-12-24",
805
+ time: "13:00",
806
+ optionsTime: "13:15",
807
+ notes: "Market closes early on Thursday, December 24, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
808
+ },
809
+ },
810
+ 2027: {
811
+ "2027-11-26": {
812
+ date: "2027-11-26",
813
+ time: "13:00",
814
+ optionsTime: "13:15",
815
+ notes: "Market closes early on Friday, November 26, 2027 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE National, and NYSE Texas late trading sessions will close at 5:00 p.m. Eastern Time.",
816
+ },
817
+ },
722
818
  };
723
819
 
724
820
  // market-time.ts
@@ -731,9 +827,15 @@ const marketEarlyCloses = {
731
827
  * Early extended market hours are 1:00pm-5:00pm on early close days
732
828
  */
733
829
  const MARKET_TIMES = {
734
- TIMEZONE: 'America/New_York',
735
- REGULAR: { START: { HOUR: 9, MINUTE: 30}, END: { HOUR: 16, MINUTE: 0} },
736
- EXTENDED: { START: { HOUR: 4, MINUTE: 0}, END: { HOUR: 20, MINUTE: 0} },
830
+ TIMEZONE: "America/New_York",
831
+ REGULAR: {
832
+ START: { HOUR: 9, MINUTE: 30},
833
+ END: { HOUR: 16, MINUTE: 0},
834
+ },
835
+ EXTENDED: {
836
+ START: { HOUR: 4, MINUTE: 0},
837
+ END: { HOUR: 20, MINUTE: 0},
838
+ },
737
839
  };
738
840
  /**
739
841
  * Utility class for handling market time-related operations
@@ -746,7 +848,7 @@ class MarketTimeUtil {
746
848
  * @param {string} [timezone='America/New_York'] - The timezone to use for market time calculations
747
849
  * @param {IntradayReporting} [intradayReporting='market_hours'] - The intraday reporting mode
748
850
  */
749
- constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting = 'market_hours') {
851
+ constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting = "market_hours") {
750
852
  this.validateTimezone(timezone);
751
853
  this.timezone = timezone;
752
854
  this.intradayReporting = intradayReporting;
@@ -765,25 +867,37 @@ class MarketTimeUtil {
765
867
  throw new Error(`Invalid timezone: ${timezone}`);
766
868
  }
767
869
  }
768
- formatDate(date, outputFormat = 'iso') {
870
+ formatDate(date, outputFormat = "iso") {
769
871
  switch (outputFormat) {
770
- case 'unix-seconds':
872
+ case "unix-seconds":
771
873
  return Math.floor(date.getTime() / 1000);
772
- case 'unix-ms':
874
+ case "unix-ms":
773
875
  return date.getTime();
774
- case 'iso':
876
+ case "iso":
775
877
  default:
776
878
  // return with timezone offset
777
879
  return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
778
880
  }
779
881
  }
780
- isWeekend(date) {
781
- const day = date.getDay();
882
+ /**
883
+ * Checks if a NY-zoned date falls on a weekend.
884
+ * Expects a date already converted to market timezone via toZonedTime.
885
+ * @param nyDate - Date in market timezone representation
886
+ * @returns true if the date is Saturday or Sunday
887
+ */
888
+ isWeekendZoned(nyDate) {
889
+ const day = nyDate.getDay();
782
890
  return day === 0 || day === 6;
783
891
  }
784
- isHoliday(date) {
785
- const formattedDate = format(date, 'yyyy-MM-dd');
786
- const yearHolidays = marketHolidays[date.getFullYear()];
892
+ /**
893
+ * Checks if a NY-zoned date falls on a market holiday.
894
+ * Expects a date already converted to market timezone via toZonedTime.
895
+ * @param nyDate - Date in market timezone representation
896
+ * @returns true if the date is a holiday
897
+ */
898
+ isHolidayZoned(nyDate) {
899
+ const formattedDate = format(nyDate, "yyyy-MM-dd");
900
+ const yearHolidays = marketHolidays[nyDate.getFullYear()];
787
901
  for (const holiday in yearHolidays) {
788
902
  if (yearHolidays[holiday].date === formattedDate) {
789
903
  return true;
@@ -791,50 +905,98 @@ class MarketTimeUtil {
791
905
  }
792
906
  return false;
793
907
  }
794
- isEarlyCloseDay(date) {
795
- const formattedDate = format(date, 'yyyy-MM-dd');
796
- const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
908
+ /**
909
+ * Checks if a NY-zoned date is an early close day.
910
+ * Expects a date already converted to market timezone via toZonedTime.
911
+ * @param nyDate - Date in market timezone representation
912
+ * @returns true if the date is an early close day
913
+ */
914
+ isEarlyCloseDayZoned(nyDate) {
915
+ const formattedDate = format(nyDate, "yyyy-MM-dd");
916
+ const yearEarlyCloses = marketEarlyCloses[nyDate.getFullYear()];
797
917
  return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
798
918
  }
799
919
  /**
800
- * Get the early close time for a given date
801
- * @param date - The date to get the early close time for
802
- * @returns The early close time in minutes from midnight, or null if there is no early close
920
+ * Gets the early close time for a NY-zoned date.
921
+ * Expects a date already converted to market timezone via toZonedTime.
922
+ * @param nyDate - Date in market timezone representation
923
+ * @returns The early close time in minutes from midnight, or null if not an early close day
803
924
  */
804
- getEarlyCloseTime(date) {
805
- const formattedDate = format(date, 'yyyy-MM-dd');
806
- const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
925
+ getEarlyCloseTimeZoned(nyDate) {
926
+ const formattedDate = format(nyDate, "yyyy-MM-dd");
927
+ const yearEarlyCloses = marketEarlyCloses[nyDate.getFullYear()];
807
928
  if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
808
- const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(':').map(Number);
929
+ const [hours, minutes] = yearEarlyCloses[formattedDate].time
930
+ .split(":")
931
+ .map(Number);
809
932
  return hours * 60 + minutes;
810
933
  }
811
934
  return null;
812
935
  }
813
936
  /**
814
- * Check if a given date is a market day
815
- * @param date - The date to check
937
+ * Checks if a NY-zoned date is a market day (not weekend, not holiday).
938
+ * Expects a date already converted to market timezone via toZonedTime.
939
+ * @param nyDate - Date in market timezone representation
940
+ * @returns true if the date is a market day
941
+ */
942
+ isMarketDayZoned(nyDate) {
943
+ return !this.isWeekendZoned(nyDate) && !this.isHolidayZoned(nyDate);
944
+ }
945
+ /**
946
+ * Check if a given date is an early close day.
947
+ * Handles timezone conversion from any input date.
948
+ * @param date - The date to check (any timezone)
949
+ * @returns true if the date is an early close day
950
+ */
951
+ isEarlyCloseDay(date) {
952
+ const nyDate = toZonedTime(date, this.timezone);
953
+ return this.isEarlyCloseDayZoned(nyDate);
954
+ }
955
+ /**
956
+ * Get the early close time for a given date.
957
+ * Handles timezone conversion from any input date.
958
+ * @param date - The date to check (any timezone)
959
+ * @returns The early close time in minutes from midnight, or null if there is no early close
960
+ */
961
+ getEarlyCloseTime(date) {
962
+ const nyDate = toZonedTime(date, this.timezone);
963
+ return this.getEarlyCloseTimeZoned(nyDate);
964
+ }
965
+ /**
966
+ * Check if a given date is a market day.
967
+ * Handles timezone conversion from any input date.
968
+ * @param date - The date to check (any timezone)
816
969
  * @returns true if the date is a market day, false otherwise
817
970
  */
818
971
  isMarketDay(date) {
819
- const isWeekendDay = this.isWeekend(date);
820
- const isHolidayDay = this.isHoliday(date);
821
- const returner = !isWeekendDay && !isHolidayDay;
822
- return returner;
972
+ const nyDate = toZonedTime(date, this.timezone);
973
+ return this.isMarketDayZoned(nyDate);
823
974
  }
824
975
  /**
825
- * Check if a given date is within market hours
826
- * @param date - The date to check
976
+ * Check if a given date is within market hours.
977
+ * Handles timezone conversion from any input date.
978
+ * @param date - The date to check (any timezone)
827
979
  * @returns true if the date is within market hours, false otherwise
828
980
  */
829
981
  isWithinMarketHours(date) {
830
- // Check for holidays first
831
- if (this.isHoliday(date)) {
982
+ const nyDate = toZonedTime(date, this.timezone);
983
+ return this.isWithinMarketHoursZoned(nyDate);
984
+ }
985
+ /**
986
+ * Check if a NY-zoned date is within market hours.
987
+ * Expects a date already converted to market timezone via toZonedTime.
988
+ * @param nyDate - Date in market timezone representation
989
+ * @returns true if the date is within market hours, false otherwise
990
+ */
991
+ isWithinMarketHoursZoned(nyDate) {
992
+ // Check for weekends and holidays first
993
+ if (this.isWeekendZoned(nyDate) || this.isHolidayZoned(nyDate)) {
832
994
  return false;
833
995
  }
834
- const timeInMinutes = date.getHours() * 60 + date.getMinutes();
996
+ const timeInMinutes = nyDate.getHours() * 60 + nyDate.getMinutes();
835
997
  // Check for early closure
836
- if (this.isEarlyCloseDay(date)) {
837
- const earlyCloseMinutes = this.getEarlyCloseTime(date);
998
+ if (this.isEarlyCloseDayZoned(nyDate)) {
999
+ const earlyCloseMinutes = this.getEarlyCloseTimeZoned(nyDate);
838
1000
  if (earlyCloseMinutes !== null && timeInMinutes > earlyCloseMinutes) {
839
1001
  return false;
840
1002
  }
@@ -842,38 +1004,50 @@ class MarketTimeUtil {
842
1004
  // Regular market hours logic
843
1005
  let returner;
844
1006
  switch (this.intradayReporting) {
845
- case 'extended_hours': {
846
- const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 + MARKET_TIMES.EXTENDED.START.MINUTE;
847
- const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
1007
+ case "extended_hours": {
1008
+ const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 +
1009
+ MARKET_TIMES.EXTENDED.START.MINUTE;
1010
+ const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 +
1011
+ MARKET_TIMES.EXTENDED.END.MINUTE;
848
1012
  // Comprehensive handling of times crossing midnight
849
- const adjustedDate = timeInMinutes < extendedStartMinutes ? sub(date, { days: 1 }) : date;
850
- const adjustedTimeInMinutes = adjustedDate.getHours() * 60 + adjustedDate.getMinutes();
851
- returner = adjustedTimeInMinutes >= extendedStartMinutes && adjustedTimeInMinutes <= extendedEndMinutes;
1013
+ const adjustedNyDate = timeInMinutes < extendedStartMinutes
1014
+ ? sub(nyDate, { days: 1 })
1015
+ : nyDate;
1016
+ const adjustedTimeInMinutes = adjustedNyDate.getHours() * 60 + adjustedNyDate.getMinutes();
1017
+ returner =
1018
+ adjustedTimeInMinutes >= extendedStartMinutes &&
1019
+ adjustedTimeInMinutes <= extendedEndMinutes;
852
1020
  break;
853
1021
  }
854
- case 'continuous':
1022
+ case "continuous":
855
1023
  returner = true;
856
1024
  break;
857
1025
  default: {
858
1026
  // market_hours
859
- const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
1027
+ const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 +
1028
+ MARKET_TIMES.REGULAR.START.MINUTE;
860
1029
  const regularEndMinutes = MARKET_TIMES.REGULAR.END.HOUR * 60 + MARKET_TIMES.REGULAR.END.MINUTE;
861
- returner = timeInMinutes >= regularStartMinutes && timeInMinutes <= regularEndMinutes;
1030
+ returner =
1031
+ timeInMinutes >= regularStartMinutes &&
1032
+ timeInMinutes <= regularEndMinutes;
862
1033
  break;
863
1034
  }
864
1035
  }
865
1036
  return returner;
866
1037
  }
867
1038
  /**
868
- * Check if a given date is before market hours
869
- * @param date - The date to check
1039
+ * Check if a NY-zoned date is before market hours.
1040
+ * Expects a date already converted to market timezone via toZonedTime.
1041
+ * @param nyDate - Date in market timezone representation
870
1042
  * @returns true if the date is before market hours, false otherwise
871
1043
  */
872
- isBeforeMarketHours(date) {
873
- const timeInMinutes = date.getHours() * 60 + date.getMinutes();
874
- const startMinutes = this.intradayReporting === 'extended_hours'
875
- ? MARKET_TIMES.EXTENDED.START.HOUR * 60 + MARKET_TIMES.EXTENDED.START.MINUTE
876
- : MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
1044
+ isBeforeMarketHoursZoned(nyDate) {
1045
+ const timeInMinutes = nyDate.getHours() * 60 + nyDate.getMinutes();
1046
+ const startMinutes = this.intradayReporting === "extended_hours"
1047
+ ? MARKET_TIMES.EXTENDED.START.HOUR * 60 +
1048
+ MARKET_TIMES.EXTENDED.START.MINUTE
1049
+ : MARKET_TIMES.REGULAR.START.HOUR * 60 +
1050
+ MARKET_TIMES.REGULAR.START.MINUTE;
877
1051
  return timeInMinutes < startMinutes;
878
1052
  }
879
1053
  /**
@@ -883,7 +1057,7 @@ class MarketTimeUtil {
883
1057
  */
884
1058
  getLastTradingDate(currentDate = new Date()) {
885
1059
  const nowET = toZonedTime(currentDate, this.timezone);
886
- const isMarketDayToday = this.isMarketDay(nowET);
1060
+ const isMarketDayToday = this.isMarketDayZoned(nowET);
887
1061
  const currentMinutes = nowET.getHours() * 60 + nowET.getMinutes();
888
1062
  const marketOpenMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
889
1063
  if (isMarketDayToday && currentMinutes >= marketOpenMinutes) {
@@ -893,7 +1067,7 @@ class MarketTimeUtil {
893
1067
  else {
894
1068
  // Before market open, or not a market day, return previous trading day
895
1069
  let lastTradingDate = sub(nowET, { days: 1 });
896
- while (!this.isMarketDay(lastTradingDate)) {
1070
+ while (!this.isMarketDayZoned(lastTradingDate)) {
897
1071
  lastTradingDate = sub(lastTradingDate, { days: 1 });
898
1072
  }
899
1073
  return lastTradingDate;
@@ -901,7 +1075,7 @@ class MarketTimeUtil {
901
1075
  }
902
1076
  getLastMarketDay(date) {
903
1077
  let currentDate = sub(date, { days: 1 });
904
- while (!this.isMarketDay(currentDate)) {
1078
+ while (!this.isMarketDayZoned(currentDate)) {
905
1079
  currentDate = sub(currentDate, { days: 1 });
906
1080
  }
907
1081
  return currentDate;
@@ -910,7 +1084,7 @@ class MarketTimeUtil {
910
1084
  const nowET = toZonedTime(currentDate, this.timezone);
911
1085
  // If today is a market day and we're after extended hours close
912
1086
  // then return today since it's a completed trading day
913
- if (this.isMarketDay(nowET)) {
1087
+ if (this.isMarketDayZoned(nowET)) {
914
1088
  const timeInMinutes = nowET.getHours() * 60 + nowET.getMinutes();
915
1089
  const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
916
1090
  // Check if we're after market close (including extended hours)
@@ -934,18 +1108,33 @@ class MarketTimeUtil {
934
1108
  * @property {string} yyyymmdd - The date in YYYY-MM-DD format
935
1109
  * @property {string} dateISOString - Full ISO date string
936
1110
  */
937
- getNextMarketDay(date) {
938
- let currentDate = add(date, { days: 1 });
939
- while (!this.isMarketDay(currentDate)) {
1111
+ /**
1112
+ * Gets the next market day from a date already in market timezone.
1113
+ * @param nyDate - Date in market timezone representation
1114
+ * @returns The next market day in market timezone representation
1115
+ */
1116
+ getNextMarketDayZoned(nyDate) {
1117
+ let currentDate = add(nyDate, { days: 1 });
1118
+ while (!this.isMarketDayZoned(currentDate)) {
940
1119
  currentDate = add(currentDate, { days: 1 });
941
1120
  }
942
1121
  return currentDate;
943
1122
  }
1123
+ /**
1124
+ * Gets the next market day from a reference date.
1125
+ * Handles timezone conversion from any input date.
1126
+ * @param date - The reference date (any timezone)
1127
+ * @returns The next market day as a Date (note: internally represented in market timezone)
1128
+ */
1129
+ getNextMarketDay(date) {
1130
+ const nyDate = toZonedTime(date, this.timezone);
1131
+ return this.getNextMarketDayZoned(nyDate);
1132
+ }
944
1133
  getDayBoundaries(date) {
945
1134
  let start;
946
1135
  let end;
947
1136
  switch (this.intradayReporting) {
948
- case 'extended_hours': {
1137
+ case "extended_hours": {
949
1138
  start = set(date, {
950
1139
  hours: MARKET_TIMES.EXTENDED.START.HOUR,
951
1140
  minutes: MARKET_TIMES.EXTENDED.START.MINUTE,
@@ -960,7 +1149,7 @@ class MarketTimeUtil {
960
1149
  });
961
1150
  break;
962
1151
  }
963
- case 'continuous': {
1152
+ case "continuous": {
964
1153
  start = startOfDay(date);
965
1154
  end = endOfDay(date);
966
1155
  break;
@@ -973,9 +1162,9 @@ class MarketTimeUtil {
973
1162
  seconds: 0,
974
1163
  milliseconds: 0,
975
1164
  });
976
- // Check for early close
977
- if (this.isEarlyCloseDay(date)) {
978
- const earlyCloseMinutes = this.getEarlyCloseTime(date);
1165
+ // Check for early close (date is already zoned)
1166
+ if (this.isEarlyCloseDayZoned(date)) {
1167
+ const earlyCloseMinutes = this.getEarlyCloseTimeZoned(date);
979
1168
  if (earlyCloseMinutes !== null) {
980
1169
  const earlyCloseHours = Math.floor(earlyCloseMinutes / 60);
981
1170
  const earlyCloseMinutesRemainder = earlyCloseMinutes % 60;
@@ -1002,44 +1191,44 @@ class MarketTimeUtil {
1002
1191
  calculatePeriodStartDate(endDate, period) {
1003
1192
  let startDate;
1004
1193
  switch (period) {
1005
- case 'YTD':
1194
+ case "YTD":
1006
1195
  startDate = set(endDate, { month: 0, date: 1 });
1007
1196
  break;
1008
- case '1D':
1197
+ case "1D":
1009
1198
  startDate = this.getLastMarketDay(endDate);
1010
1199
  break;
1011
- case '3D':
1200
+ case "3D":
1012
1201
  startDate = sub(endDate, { days: 3 });
1013
1202
  break;
1014
- case '1W':
1203
+ case "1W":
1015
1204
  startDate = sub(endDate, { weeks: 1 });
1016
1205
  break;
1017
- case '2W':
1206
+ case "2W":
1018
1207
  startDate = sub(endDate, { weeks: 2 });
1019
1208
  break;
1020
- case '1M':
1209
+ case "1M":
1021
1210
  startDate = sub(endDate, { months: 1 });
1022
1211
  break;
1023
- case '3M':
1212
+ case "3M":
1024
1213
  startDate = sub(endDate, { months: 3 });
1025
1214
  break;
1026
- case '6M':
1215
+ case "6M":
1027
1216
  startDate = sub(endDate, { months: 6 });
1028
1217
  break;
1029
- case '1Y':
1218
+ case "1Y":
1030
1219
  startDate = sub(endDate, { years: 1 });
1031
1220
  break;
1032
1221
  default:
1033
1222
  throw new Error(`Invalid period: ${period}`);
1034
1223
  }
1035
- while (!this.isMarketDay(startDate)) {
1036
- startDate = this.getNextMarketDay(startDate);
1224
+ while (!this.isMarketDayZoned(startDate)) {
1225
+ startDate = this.getNextMarketDayZoned(startDate);
1037
1226
  }
1038
1227
  return startDate;
1039
1228
  }
1040
- getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat = 'iso', }) {
1229
+ getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat = "iso", }) {
1041
1230
  if (!period) {
1042
- throw new Error('Period is required');
1231
+ throw new Error("Period is required");
1043
1232
  }
1044
1233
  if (intraday_reporting) {
1045
1234
  this.intradayReporting = intraday_reporting;
@@ -1048,9 +1237,9 @@ class MarketTimeUtil {
1048
1237
  const zonedEndDate = toZonedTime(end, this.timezone);
1049
1238
  let startDate;
1050
1239
  let endDate;
1051
- const isCurrentMarketDay = this.isMarketDay(zonedEndDate);
1052
- const isWithinHours = this.isWithinMarketHours(zonedEndDate);
1053
- const isBeforeHours = this.isBeforeMarketHours(zonedEndDate);
1240
+ const isCurrentMarketDay = this.isMarketDayZoned(zonedEndDate);
1241
+ const isWithinHours = this.isWithinMarketHoursZoned(zonedEndDate);
1242
+ const isBeforeHours = this.isBeforeMarketHoursZoned(zonedEndDate);
1054
1243
  // First determine the end date based on current market conditions
1055
1244
  if (isCurrentMarketDay) {
1056
1245
  if (isBeforeHours) {
@@ -1084,7 +1273,7 @@ class MarketTimeUtil {
1084
1273
  const utcEnd = fromZonedTime(endDate, this.timezone);
1085
1274
  // Ensure start is not after end
1086
1275
  if (isBefore(utcEnd, utcStart)) {
1087
- throw new Error('Start date cannot be after end date');
1276
+ throw new Error("Start date cannot be after end date");
1088
1277
  }
1089
1278
  return {
1090
1279
  start: this.formatDate(utcStart, outputFormat),
@@ -1095,7 +1284,7 @@ class MarketTimeUtil {
1095
1284
  const { date = new Date() } = options;
1096
1285
  const zonedDate = toZonedTime(date, this.timezone);
1097
1286
  // Check if market is closed for the day
1098
- if (this.isWeekend(zonedDate) || this.isHoliday(zonedDate)) {
1287
+ if (this.isWeekendZoned(zonedDate) || this.isHolidayZoned(zonedDate)) {
1099
1288
  return {
1100
1289
  marketOpen: false,
1101
1290
  open: null,
@@ -1109,10 +1298,10 @@ class MarketTimeUtil {
1109
1298
  let regularCloseTime = MARKET_TIMES.REGULAR.END;
1110
1299
  const extendedOpenTime = MARKET_TIMES.EXTENDED.START;
1111
1300
  let extendedCloseTime = MARKET_TIMES.EXTENDED.END;
1112
- // Check for early close
1113
- const isEarlyClose = this.isEarlyCloseDay(zonedDate);
1301
+ // Check for early close (zonedDate is already in market timezone)
1302
+ const isEarlyClose = this.isEarlyCloseDayZoned(zonedDate);
1114
1303
  if (isEarlyClose) {
1115
- const earlyCloseMinutes = this.getEarlyCloseTime(zonedDate);
1304
+ const earlyCloseMinutes = this.getEarlyCloseTimeZoned(zonedDate);
1116
1305
  if (earlyCloseMinutes !== null) {
1117
1306
  // For regular hours, use the early close time
1118
1307
  regularCloseTime = {
@@ -1128,10 +1317,22 @@ class MarketTimeUtil {
1128
1317
  };
1129
1318
  }
1130
1319
  }
1131
- const open = fromZonedTime(set(dayStart, { hours: regularOpenTime.HOUR, minutes: regularOpenTime.MINUTE }), this.timezone);
1132
- const close = fromZonedTime(set(dayStart, { hours: regularCloseTime.HOUR, minutes: regularCloseTime.MINUTE }), this.timezone);
1133
- const openExt = fromZonedTime(set(dayStart, { hours: extendedOpenTime.HOUR, minutes: extendedOpenTime.MINUTE }), this.timezone);
1134
- const closeExt = fromZonedTime(set(dayStart, { hours: extendedCloseTime.HOUR, minutes: extendedCloseTime.MINUTE }), this.timezone);
1320
+ const open = fromZonedTime(set(dayStart, {
1321
+ hours: regularOpenTime.HOUR,
1322
+ minutes: regularOpenTime.MINUTE,
1323
+ }), this.timezone);
1324
+ const close = fromZonedTime(set(dayStart, {
1325
+ hours: regularCloseTime.HOUR,
1326
+ minutes: regularCloseTime.MINUTE,
1327
+ }), this.timezone);
1328
+ const openExt = fromZonedTime(set(dayStart, {
1329
+ hours: extendedOpenTime.HOUR,
1330
+ minutes: extendedOpenTime.MINUTE,
1331
+ }), this.timezone);
1332
+ const closeExt = fromZonedTime(set(dayStart, {
1333
+ hours: extendedCloseTime.HOUR,
1334
+ minutes: extendedCloseTime.MINUTE,
1335
+ }), this.timezone);
1135
1336
  return {
1136
1337
  marketOpen: true,
1137
1338
  open,
@@ -1154,7 +1355,7 @@ function getLastFullTradingDate(currentDate = new Date()) {
1154
1355
  // Format the date in NY timezone to ensure consistency
1155
1356
  return {
1156
1357
  date,
1157
- YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE, 'yyyy-MM-dd'),
1358
+ YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE, "yyyy-MM-dd"),
1158
1359
  };
1159
1360
  }
1160
1361
 
@@ -6128,13 +6329,63 @@ function requireWebsocketServer () {
6128
6329
 
6129
6330
  requireWebsocketServer();
6130
6331
 
6131
- const log$1 = (message, options = { type: 'info' }) => {
6132
- log$2(message, { ...options, source: 'AlpacaMarketDataAPI' });
6332
+ /**
6333
+ * API credential validation utilities
6334
+ * Provides fast, synchronous validation of API credentials before making requests
6335
+ */
6336
+ /**
6337
+ * Validates Alpaca API credentials
6338
+ * @param auth - Authentication object containing API key and secret
6339
+ * @throws {Error} If credentials are invalid or missing
6340
+ */
6341
+ function validateAlpacaCredentials(auth) {
6342
+ if (!auth.apiKey ||
6343
+ typeof auth.apiKey !== "string" ||
6344
+ auth.apiKey.trim().length === 0) {
6345
+ throw new Error("Invalid Alpaca API key: must be a non-empty string");
6346
+ }
6347
+ if (!auth.apiSecret ||
6348
+ typeof auth.apiSecret !== "string" ||
6349
+ auth.apiSecret.trim().length === 0) {
6350
+ throw new Error("Invalid Alpaca API secret: must be a non-empty string");
6351
+ }
6352
+ // Alpaca keys are typically 20+ characters
6353
+ if (auth.apiKey.length < 10) {
6354
+ throw new Error("Alpaca API key appears to be too short");
6355
+ }
6356
+ }
6357
+
6358
+ /**
6359
+ * HTTP request timeout utilities
6360
+ * Provides configurable timeout handling for external API calls
6361
+ */
6362
+ /**
6363
+ * Default timeout values for different external APIs (in milliseconds)
6364
+ * Can be overridden via environment variables
6365
+ */
6366
+ const DEFAULT_TIMEOUTS = {
6367
+ ALPACA_API: parseInt(process.env.ALPACA_API_TIMEOUT || "30000", 10),
6368
+ POLYGON_API: parseInt(process.env.POLYGON_API_TIMEOUT || "30000", 10),
6369
+ ALPHA_VANTAGE: parseInt(process.env.ALPHA_VANTAGE_API_TIMEOUT || "30000", 10),
6370
+ GENERAL: parseInt(process.env.HTTP_TIMEOUT || "30000", 10),
6371
+ };
6372
+ /**
6373
+ * Creates an AbortSignal that times out after the specified duration
6374
+ * Compatible with fetch API
6375
+ * @param ms - Timeout duration in milliseconds
6376
+ * @returns AbortSignal that will abort after the specified duration
6377
+ */
6378
+ function createTimeoutSignal(ms) {
6379
+ return AbortSignal.timeout(ms);
6380
+ }
6381
+
6382
+ const log$1 = (message, options = { type: "info" }) => {
6383
+ log$2(message, { ...options, source: "AlpacaMarketDataAPI" });
6133
6384
  };
6134
6385
  // Default settings for market data API
6135
- const DEFAULT_ADJUSTMENT = 'all';
6136
- const DEFAULT_FEED = 'sip';
6137
- const DEFAULT_CURRENCY = 'USD';
6386
+ const DEFAULT_ADJUSTMENT = "all";
6387
+ const DEFAULT_FEED = "sip";
6388
+ const DEFAULT_CURRENCY = "USD";
6138
6389
  /**
6139
6390
  * Singleton class for interacting with Alpaca Market Data API
6140
6391
  * Provides methods for fetching historical bars, latest bars, last trades, latest trades, latest quotes, and latest quote for a single symbol
@@ -6145,56 +6396,79 @@ class AlpacaMarketDataAPI extends EventEmitter {
6145
6396
  dataURL;
6146
6397
  apiURL;
6147
6398
  v1beta1url;
6148
- stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip'; // production values
6149
- optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // production values
6150
- cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us'; // production values
6399
+ stockStreamUrl = getStockStreamUrl("PRODUCTION"); // production values
6400
+ optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // production values
6401
+ cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // production values
6151
6402
  stockWs = null;
6152
6403
  optionWs = null;
6153
6404
  cryptoWs = null;
6154
- stockSubscriptions = { trades: [], quotes: [], bars: [] };
6155
- optionSubscriptions = { trades: [], quotes: [], bars: [] };
6156
- cryptoSubscriptions = { trades: [], quotes: [], bars: [] };
6157
- setMode(mode = 'production') {
6158
- if (mode === 'sandbox') { // sandbox mode
6159
- this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
6160
- this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
6161
- this.cryptoStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/crypto/us';
6405
+ stockSubscriptions = {
6406
+ trades: [],
6407
+ quotes: [],
6408
+ bars: [],
6409
+ };
6410
+ optionSubscriptions = {
6411
+ trades: [],
6412
+ quotes: [],
6413
+ bars: [],
6414
+ };
6415
+ cryptoSubscriptions = {
6416
+ trades: [],
6417
+ quotes: [],
6418
+ bars: [],
6419
+ };
6420
+ setMode(mode = "production") {
6421
+ if (mode === "sandbox") {
6422
+ // sandbox mode
6423
+ this.stockStreamUrl = WEBSOCKET_STREAMS.STOCKS.PRODUCTION; // sandbox uses production for stocks
6424
+ this.optionStreamUrl = getOptionsStreamUrl("SANDBOX");
6425
+ this.cryptoStreamUrl = getCryptoStreamUrl("SANDBOX");
6162
6426
  }
6163
- else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
6164
- this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
6165
- this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // there's no test mode for options
6166
- this.cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us'; // there's no test mode for crypto
6427
+ else if (mode === "test") {
6428
+ // test mode, can only use ticker FAKEPACA
6429
+ this.stockStreamUrl = getStockStreamUrl("TEST");
6430
+ this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // there's no test mode for options
6431
+ this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // there's no test mode for crypto
6167
6432
  }
6168
- else { // production
6169
- this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
6170
- this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
6171
- this.cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us';
6433
+ else {
6434
+ // production
6435
+ this.stockStreamUrl = getStockStreamUrl("PRODUCTION");
6436
+ this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION");
6437
+ this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION");
6172
6438
  }
6173
6439
  }
6174
6440
  getMode() {
6175
- if (this.stockStreamUrl.includes('sandbox')) {
6176
- return 'sandbox';
6441
+ if (this.stockStreamUrl.includes("sandbox")) {
6442
+ return "sandbox";
6177
6443
  }
6178
- else if (this.stockStreamUrl.includes('test')) {
6179
- return 'test';
6444
+ else if (this.stockStreamUrl.includes("test")) {
6445
+ return "test";
6180
6446
  }
6181
6447
  else {
6182
- return 'production';
6448
+ return "production";
6183
6449
  }
6184
6450
  }
6185
6451
  constructor() {
6186
6452
  super();
6187
- this.dataURL = 'https://data.alpaca.markets/v2';
6453
+ // Validate credentials from environment variables before initializing
6454
+ const apiKey = process.env.ALPACA_API_KEY;
6455
+ const apiSecret = process.env.ALPACA_SECRET_KEY;
6456
+ validateAlpacaCredentials({
6457
+ apiKey,
6458
+ apiSecret,
6459
+ isPaper: process.env.ALPACA_ACCOUNT_TYPE === "PAPER",
6460
+ });
6461
+ this.dataURL = MARKET_DATA_API.STOCKS;
6188
6462
  this.apiURL =
6189
- process.env.ALPACA_ACCOUNT_TYPE === 'PAPER'
6190
- ? 'https://paper-api.alpaca.markets/v2'
6191
- : 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
6192
- this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
6193
- this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
6463
+ process.env.ALPACA_ACCOUNT_TYPE === "PAPER"
6464
+ ? getTradingApiUrl("PAPER")
6465
+ : getTradingApiUrl("LIVE"); // used by some, e.g. getAssets
6466
+ this.v1beta1url = MARKET_DATA_API.OPTIONS; // used for options endpoints
6467
+ this.setMode("production"); // sets stockStreamUrl and optionStreamUrl
6194
6468
  this.headers = {
6195
- 'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
6196
- 'APCA-API-SECRET-KEY': process.env.ALPACA_SECRET_KEY,
6197
- 'Content-Type': 'application/json',
6469
+ "APCA-API-KEY-ID": apiKey,
6470
+ "APCA-API-SECRET-KEY": apiSecret,
6471
+ "Content-Type": "application/json",
6198
6472
  };
6199
6473
  }
6200
6474
  static getInstance() {
@@ -6211,73 +6485,75 @@ class AlpacaMarketDataAPI extends EventEmitter {
6211
6485
  }
6212
6486
  connect(streamType) {
6213
6487
  let url;
6214
- if (streamType === 'stock') {
6488
+ if (streamType === "stock") {
6215
6489
  url = this.stockStreamUrl;
6216
6490
  }
6217
- else if (streamType === 'option') {
6491
+ else if (streamType === "option") {
6218
6492
  url = this.optionStreamUrl;
6219
6493
  }
6220
6494
  else {
6221
6495
  url = this.cryptoStreamUrl;
6222
6496
  }
6223
6497
  const ws = new WebSocket(url);
6224
- if (streamType === 'stock') {
6498
+ if (streamType === "stock") {
6225
6499
  this.stockWs = ws;
6226
6500
  }
6227
- else if (streamType === 'option') {
6501
+ else if (streamType === "option") {
6228
6502
  this.optionWs = ws;
6229
6503
  }
6230
6504
  else {
6231
6505
  this.cryptoWs = ws;
6232
6506
  }
6233
- ws.on('open', () => {
6234
- log$1(`${streamType} stream connected`, { type: 'info' });
6507
+ ws.on("open", () => {
6508
+ log$1(`${streamType} stream connected`, { type: "info" });
6235
6509
  const authMessage = {
6236
- action: 'auth',
6510
+ action: "auth",
6237
6511
  key: process.env.ALPACA_API_KEY,
6238
6512
  secret: process.env.ALPACA_SECRET_KEY,
6239
6513
  };
6240
6514
  ws.send(JSON.stringify(authMessage));
6241
6515
  });
6242
- ws.on('message', (data) => {
6516
+ ws.on("message", (data) => {
6243
6517
  const rawData = data.toString();
6244
6518
  let messages;
6245
6519
  try {
6246
6520
  messages = JSON.parse(rawData);
6247
6521
  }
6248
6522
  catch (e) {
6249
- log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type: 'error' });
6523
+ log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type: "error" });
6250
6524
  return;
6251
6525
  }
6252
6526
  for (const message of messages) {
6253
- if (message.T === 'success' && message.msg === 'authenticated') {
6254
- log$1(`${streamType} stream authenticated`, { type: 'info' });
6527
+ if (message.T === "success" && message.msg === "authenticated") {
6528
+ log$1(`${streamType} stream authenticated`, { type: "info" });
6255
6529
  this.sendSubscription(streamType);
6256
6530
  }
6257
- else if (message.T === 'success' && message.msg === 'connected') {
6258
- log$1(`${streamType} stream connected message received`, { type: 'debug' });
6531
+ else if (message.T === "success" && message.msg === "connected") {
6532
+ log$1(`${streamType} stream connected message received`, {
6533
+ type: "debug",
6534
+ });
6259
6535
  }
6260
- else if (message.T === 'subscription') {
6261
- log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type: 'info' });
6536
+ else if (message.T === "subscription") {
6537
+ log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type: "info" });
6262
6538
  }
6263
- else if (message.T === 'error') {
6264
- log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: 'error' });
6539
+ else if (message.T === "error") {
6540
+ log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: "error" });
6265
6541
  }
6266
6542
  else if (message.S) {
6267
6543
  super.emit(`${streamType}-${message.T}`, message);
6268
6544
  super.emit(`${streamType}-data`, message);
6269
6545
  }
6270
6546
  else {
6271
- log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: 'debug' });
6547
+ log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: "debug" });
6272
6548
  }
6273
6549
  }
6274
6550
  });
6275
- ws.on('close', () => {
6276
- log$1(`${streamType} stream disconnected`, { type: 'warn' });
6277
- if (streamType === 'stock') {
6551
+ ws.on("close", () => {
6552
+ log$1(`${streamType} stream disconnected`, { type: "warn" });
6553
+ if (streamType === "stock") {
6278
6554
  this.stockWs = null;
6279
6555
  }
6280
- else if (streamType === 'option') {
6556
+ else if (streamType === "option") {
6281
6557
  this.optionWs = null;
6282
6558
  }
6283
6559
  else {
@@ -6285,18 +6561,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6285
6561
  }
6286
6562
  // Optional: implement reconnect logic
6287
6563
  });
6288
- ws.on('error', (error) => {
6289
- log$1(`${streamType} stream error: ${error.message}`, { type: 'error' });
6564
+ ws.on("error", (error) => {
6565
+ log$1(`${streamType} stream error: ${error.message}`, { type: "error" });
6290
6566
  });
6291
6567
  }
6292
6568
  sendSubscription(streamType) {
6293
6569
  let ws;
6294
6570
  let subscriptions;
6295
- if (streamType === 'stock') {
6571
+ if (streamType === "stock") {
6296
6572
  ws = this.stockWs;
6297
6573
  subscriptions = this.stockSubscriptions;
6298
6574
  }
6299
- else if (streamType === 'option') {
6575
+ else if (streamType === "option") {
6300
6576
  ws = this.optionWs;
6301
6577
  subscriptions = this.optionSubscriptions;
6302
6578
  }
@@ -6305,7 +6581,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6305
6581
  subscriptions = this.cryptoSubscriptions;
6306
6582
  }
6307
6583
  log$1(`sendSubscription called for ${streamType} (wsReady=${ws?.readyState === WebSocket.OPEN}, trades=${subscriptions.trades?.length || 0}, quotes=${subscriptions.quotes?.length || 0}, bars=${subscriptions.bars?.length || 0})`, {
6308
- type: 'debug',
6584
+ type: "debug",
6309
6585
  });
6310
6586
  if (ws && ws.readyState === WebSocket.OPEN) {
6311
6587
  const subMessagePayload = {};
@@ -6320,34 +6596,40 @@ class AlpacaMarketDataAPI extends EventEmitter {
6320
6596
  }
6321
6597
  if (Object.keys(subMessagePayload).length > 0) {
6322
6598
  const subMessage = {
6323
- action: 'subscribe',
6599
+ action: "subscribe",
6324
6600
  ...subMessagePayload,
6325
6601
  };
6326
6602
  const messageJson = JSON.stringify(subMessage);
6327
- log$1(`Sending ${streamType} subscription: ${messageJson}`, { type: 'info' });
6603
+ log$1(`Sending ${streamType} subscription: ${messageJson}`, {
6604
+ type: "info",
6605
+ });
6328
6606
  ws.send(messageJson);
6329
6607
  }
6330
6608
  else {
6331
- log$1(`No ${streamType} subscriptions to send (all arrays empty)`, { type: 'debug' });
6609
+ log$1(`No ${streamType} subscriptions to send (all arrays empty)`, {
6610
+ type: "debug",
6611
+ });
6332
6612
  }
6333
6613
  }
6334
6614
  else {
6335
- log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, { type: 'warn' });
6615
+ log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, {
6616
+ type: "warn",
6617
+ });
6336
6618
  }
6337
6619
  }
6338
6620
  connectStockStream() {
6339
6621
  if (!this.stockWs) {
6340
- this.connect('stock');
6622
+ this.connect("stock");
6341
6623
  }
6342
6624
  }
6343
6625
  connectOptionStream() {
6344
6626
  if (!this.optionWs) {
6345
- this.connect('option');
6627
+ this.connect("option");
6346
6628
  }
6347
6629
  }
6348
6630
  connectCryptoStream() {
6349
6631
  if (!this.cryptoWs) {
6350
- this.connect('crypto');
6632
+ this.connect("crypto");
6351
6633
  }
6352
6634
  }
6353
6635
  disconnectStockStream() {
@@ -6371,22 +6653,22 @@ class AlpacaMarketDataAPI extends EventEmitter {
6371
6653
  * @returns True if the stream is connected
6372
6654
  */
6373
6655
  isStreamConnected(streamType) {
6374
- if (streamType === 'stock') {
6375
- return this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN;
6656
+ if (streamType === "stock") {
6657
+ return (this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN);
6376
6658
  }
6377
- else if (streamType === 'option') {
6378
- return this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN;
6659
+ else if (streamType === "option") {
6660
+ return (this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN);
6379
6661
  }
6380
6662
  else {
6381
- return this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN;
6663
+ return (this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN);
6382
6664
  }
6383
6665
  }
6384
6666
  subscribe(streamType, subscriptions) {
6385
6667
  let currentSubscriptions;
6386
- if (streamType === 'stock') {
6668
+ if (streamType === "stock") {
6387
6669
  currentSubscriptions = this.stockSubscriptions;
6388
6670
  }
6389
- else if (streamType === 'option') {
6671
+ else if (streamType === "option") {
6390
6672
  currentSubscriptions = this.optionSubscriptions;
6391
6673
  }
6392
6674
  else {
@@ -6394,17 +6676,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
6394
6676
  }
6395
6677
  Object.entries(subscriptions).forEach(([key, value]) => {
6396
6678
  if (value) {
6397
- currentSubscriptions[key] = [...new Set([...(currentSubscriptions[key] || []), ...value])];
6679
+ currentSubscriptions[key] = [
6680
+ ...new Set([...(currentSubscriptions[key] || []), ...value]),
6681
+ ];
6398
6682
  }
6399
6683
  });
6400
6684
  this.sendSubscription(streamType);
6401
6685
  }
6402
6686
  unsubscribe(streamType, subscriptions) {
6403
6687
  let currentSubscriptions;
6404
- if (streamType === 'stock') {
6688
+ if (streamType === "stock") {
6405
6689
  currentSubscriptions = this.stockSubscriptions;
6406
6690
  }
6407
- else if (streamType === 'option') {
6691
+ else if (streamType === "option") {
6408
6692
  currentSubscriptions = this.optionSubscriptions;
6409
6693
  }
6410
6694
  else {
@@ -6412,18 +6696,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6412
6696
  }
6413
6697
  Object.entries(subscriptions).forEach(([key, value]) => {
6414
6698
  if (value) {
6415
- currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
6699
+ currentSubscriptions[key] = (currentSubscriptions[key] || []).filter((s) => !value.includes(s));
6416
6700
  }
6417
6701
  });
6418
6702
  const unsubMessage = {
6419
- action: 'unsubscribe',
6703
+ action: "unsubscribe",
6420
6704
  ...subscriptions,
6421
6705
  };
6422
6706
  let ws;
6423
- if (streamType === 'stock') {
6707
+ if (streamType === "stock") {
6424
6708
  ws = this.stockWs;
6425
6709
  }
6426
- else if (streamType === 'option') {
6710
+ else if (streamType === "option") {
6427
6711
  ws = this.optionWs;
6428
6712
  }
6429
6713
  else {
@@ -6433,16 +6717,20 @@ class AlpacaMarketDataAPI extends EventEmitter {
6433
6717
  ws.send(JSON.stringify(unsubMessage));
6434
6718
  }
6435
6719
  }
6436
- async makeRequest(endpoint, method = 'GET', params, baseUrlName = 'data') {
6437
- const baseUrl = baseUrlName === 'data' ? this.dataURL : baseUrlName === 'api' ? this.apiURL : this.v1beta1url;
6720
+ async makeRequest(endpoint, method = "GET", params, baseUrlName = "data") {
6721
+ const baseUrl = baseUrlName === "data"
6722
+ ? this.dataURL
6723
+ : baseUrlName === "api"
6724
+ ? this.apiURL
6725
+ : this.v1beta1url;
6438
6726
  const url = new URL(`${baseUrl}${endpoint}`);
6439
6727
  try {
6440
6728
  if (params) {
6441
6729
  Object.entries(params).forEach(([key, value]) => {
6442
6730
  if (Array.isArray(value)) {
6443
- url.searchParams.append(key, value.join(','));
6731
+ url.searchParams.append(key, value.join(","));
6444
6732
  }
6445
- else if (value !== undefined) {
6733
+ else if (value !== undefined && value !== null) {
6446
6734
  url.searchParams.append(key, value.toString());
6447
6735
  }
6448
6736
  });
@@ -6450,10 +6738,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
6450
6738
  const response = await fetch(url.toString(), {
6451
6739
  method,
6452
6740
  headers: this.headers,
6741
+ signal: createTimeoutSignal(DEFAULT_TIMEOUTS.ALPACA_API),
6453
6742
  });
6454
6743
  if (!response.ok) {
6455
6744
  const errorText = await response.text();
6456
- log$1(`Market Data API error (${response.status}): ${errorText}`, { type: 'error' });
6745
+ log$1(`Market Data API error (${response.status}): ${errorText}`, {
6746
+ type: "error",
6747
+ });
6457
6748
  throw new Error(`Market Data API error (${response.status}): ${errorText}`);
6458
6749
  }
6459
6750
  const data = await response.json();
@@ -6461,9 +6752,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
6461
6752
  }
6462
6753
  catch (err) {
6463
6754
  const error = err;
6464
- log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: 'error' });
6755
+ log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: "error" });
6465
6756
  if (error instanceof TypeError) {
6466
- log$1(`Network error details: ${error.stack}`, { type: 'error' });
6757
+ log$1(`Network error details: ${error.stack}`, { type: "error" });
6467
6758
  }
6468
6759
  throw error;
6469
6760
  }
@@ -6476,19 +6767,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
6476
6767
  */
6477
6768
  async getHistoricalBars(params) {
6478
6769
  const symbols = params.symbols;
6479
- const symbolsStr = symbols.join(',');
6770
+ const symbolsStr = symbols.join(",");
6480
6771
  let allBars = {};
6481
6772
  let pageToken = null;
6482
6773
  let hasMorePages = true;
6483
6774
  let totalBarsCount = 0;
6484
6775
  let pageCount = 0;
6485
- let currency = '';
6776
+ let currency = "";
6486
6777
  // Initialize bar arrays for each symbol
6487
- symbols.forEach(symbol => {
6778
+ symbols.forEach((symbol) => {
6488
6779
  allBars[symbol] = [];
6489
6780
  });
6490
- log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
6491
- type: 'info'
6781
+ log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`, {
6782
+ type: "info",
6492
6783
  });
6493
6784
  while (hasMorePages) {
6494
6785
  pageCount++;
@@ -6498,9 +6789,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6498
6789
  feed: DEFAULT_FEED,
6499
6790
  ...(pageToken && { page_token: pageToken }),
6500
6791
  };
6501
- const response = await this.makeRequest('/stocks/bars', 'GET', requestParams);
6792
+ const response = await this.makeRequest("/stocks/bars", "GET", requestParams);
6502
6793
  if (!response.bars) {
6503
- log$1(`No bars data found in response for ${symbolsStr}`, { type: 'warn' });
6794
+ log$1(`No bars data found in response for ${symbolsStr}`, {
6795
+ type: "warn",
6796
+ });
6504
6797
  break;
6505
6798
  }
6506
6799
  // Track currency from first response
@@ -6516,7 +6809,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6516
6809
  allBars[symbol] = [...allBars[symbol], ...bars];
6517
6810
  pageBarsCount += bars.length;
6518
6811
  // Track date range for this page
6519
- bars.forEach(bar => {
6812
+ bars.forEach((bar) => {
6520
6813
  const barDate = new Date(bar.t);
6521
6814
  if (!earliestTimestamp || barDate < earliestTimestamp) {
6522
6815
  earliestTimestamp = barDate;
@@ -6532,21 +6825,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
6532
6825
  hasMorePages = !!pageToken;
6533
6826
  // Enhanced logging with date range and progress info
6534
6827
  const dateRangeStr = earliestTimestamp && latestTimestamp
6535
- ? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
6536
- : 'unknown range';
6537
- log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
6538
- type: 'info'
6828
+ ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
6829
+ : "unknown range";
6830
+ log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
6831
+ type: "info",
6539
6832
  });
6540
6833
  // Prevent infinite loops
6541
6834
  if (pageCount > 1000) {
6542
- log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
6835
+ log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
6543
6836
  break;
6544
6837
  }
6545
6838
  }
6546
6839
  // Final summary
6547
- const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
6840
+ const symbolCounts = Object.entries(allBars)
6841
+ .map(([symbol, bars]) => `${symbol}: ${bars.length}`)
6842
+ .join(", ");
6548
6843
  log$1(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
6549
- type: 'info'
6844
+ type: "info",
6550
6845
  });
6551
6846
  return {
6552
6847
  bars: allBars,
@@ -6562,7 +6857,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6562
6857
 
6563
6858
  */
6564
6859
  async getLatestBars(symbols, currency) {
6565
- return this.makeRequest('/stocks/bars/latest', 'GET', {
6860
+ return this.makeRequest("/stocks/bars/latest", "GET", {
6566
6861
  symbols,
6567
6862
  feed: DEFAULT_FEED,
6568
6863
  currency: currency || DEFAULT_CURRENCY,
@@ -6574,7 +6869,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6574
6869
  * @returns Last trade details including price, size, exchange, and conditions
6575
6870
  */
6576
6871
  async getLastTrade(symbol) {
6577
- return this.makeRequest(`/v1/last/stocks/${symbol}`, 'GET');
6872
+ return this.makeRequest(`/v1/last/stocks/${symbol}`, "GET");
6578
6873
  }
6579
6874
  /**
6580
6875
  * Get the most recent trades for requested symbols
@@ -6585,7 +6880,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6585
6880
 
6586
6881
  */
6587
6882
  async getLatestTrades(symbols, feed, currency) {
6588
- return this.makeRequest('/stocks/trades/latest', 'GET', {
6883
+ return this.makeRequest("/stocks/trades/latest", "GET", {
6589
6884
  symbols,
6590
6885
  feed: feed || DEFAULT_FEED,
6591
6886
  currency: currency || DEFAULT_CURRENCY,
@@ -6601,13 +6896,15 @@ class AlpacaMarketDataAPI extends EventEmitter {
6601
6896
  async getLatestQuotes(symbols, feed, currency) {
6602
6897
  // Return empty response if symbols array is empty to avoid API error
6603
6898
  if (!symbols || symbols.length === 0) {
6604
- log$1('No symbols provided to getLatestQuotes, returning empty response', { type: 'warn' });
6899
+ log$1("No symbols provided to getLatestQuotes, returning empty response", {
6900
+ type: "warn",
6901
+ });
6605
6902
  return {
6606
6903
  quotes: {},
6607
6904
  currency: currency || DEFAULT_CURRENCY,
6608
6905
  };
6609
6906
  }
6610
- return this.makeRequest('/stocks/quotes/latest', 'GET', {
6907
+ return this.makeRequest("/stocks/quotes/latest", "GET", {
6611
6908
  symbols,
6612
6909
  feed: feed || DEFAULT_FEED,
6613
6910
  currency: currency || DEFAULT_CURRENCY,
@@ -6621,7 +6918,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6621
6918
  * @returns Latest quote data with symbol and currency information
6622
6919
  */
6623
6920
  async getLatestQuote(symbol, feed, currency) {
6624
- return this.makeRequest(`/stocks/${symbol}/quotes/latest`, 'GET', {
6921
+ return this.makeRequest(`/stocks/${symbol}/quotes/latest`, "GET", {
6625
6922
  feed: feed || DEFAULT_FEED,
6626
6923
  currency,
6627
6924
  });
@@ -6637,13 +6934,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
6637
6934
  const prevMarketDate = getLastFullTradingDate(date);
6638
6935
  const response = await this.getHistoricalBars({
6639
6936
  symbols: [symbol],
6640
- timeframe: '1Day',
6937
+ timeframe: "1Day",
6641
6938
  start: prevMarketDate.date.toISOString(),
6642
6939
  end: prevMarketDate.date.toISOString(),
6643
6940
  limit: 1,
6644
6941
  });
6645
6942
  if (!response.bars[symbol] || response.bars[symbol].length === 0) {
6646
- log$1(`No previous close data available for ${symbol}`, { type: 'error', symbol });
6943
+ log$1(`No previous close data available for ${symbol}`, {
6944
+ type: "error",
6945
+ symbol,
6946
+ });
6647
6947
  return null;
6648
6948
  }
6649
6949
  return response.bars[symbol][0];
@@ -6658,7 +6958,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6658
6958
  async getHourlyPrices(symbol, start, end) {
6659
6959
  const response = await this.getHistoricalBars({
6660
6960
  symbols: [symbol],
6661
- timeframe: '1Hour',
6961
+ timeframe: "1Hour",
6662
6962
  start: new Date(start).toISOString(),
6663
6963
  end: new Date(end).toISOString(),
6664
6964
  limit: 96, // Last 96 hours (4 days)
@@ -6675,7 +6975,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6675
6975
  async getHalfHourlyPrices(symbol, start, end) {
6676
6976
  const response = await this.getHistoricalBars({
6677
6977
  symbols: [symbol],
6678
- timeframe: '30Min',
6978
+ timeframe: "30Min",
6679
6979
  start: new Date(start).toISOString(),
6680
6980
  end: new Date(end).toISOString(),
6681
6981
  limit: 16 * 2 * 4, // last 4 days, 16 hours per day, 2 bars per hour
@@ -6692,7 +6992,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6692
6992
  async getDailyPrices(symbol, start, end) {
6693
6993
  const response = await this.getHistoricalBars({
6694
6994
  symbols: [symbol],
6695
- timeframe: '1Day',
6995
+ timeframe: "1Day",
6696
6996
  start: new Date(start).toISOString(),
6697
6997
  end: new Date(end).toISOString(),
6698
6998
  limit: 100, // Last 100 days
@@ -6724,7 +7024,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6724
7024
  */
6725
7025
  static analyzeBars(bars) {
6726
7026
  if (!bars || bars.length === 0) {
6727
- return 'No price data available';
7027
+ return "No price data available";
6728
7028
  }
6729
7029
  const firstBar = bars[0];
6730
7030
  const lastBar = bars[bars.length - 1];
@@ -6749,7 +7049,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6749
7049
  */
6750
7050
  async getAssets(params) {
6751
7051
  // Endpoint: GET /v2/assets
6752
- return this.makeRequest('/assets', 'GET', params, 'api'); // use apiURL
7052
+ return this.makeRequest("/assets", "GET", params, "api"); // use apiURL
6753
7053
  }
6754
7054
  /**
6755
7055
  * Get a single asset by symbol or asset_id
@@ -6759,7 +7059,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6759
7059
  */
6760
7060
  async getAsset(symbolOrAssetId) {
6761
7061
  // Endpoint: GET /v2/assets/{symbol_or_asset_id}
6762
- return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, 'GET', undefined, 'api');
7062
+ return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, "GET", undefined, "api");
6763
7063
  }
6764
7064
  // ===== OPTIONS MARKET DATA METHODS =====
6765
7065
  /**
@@ -6771,7 +7071,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6771
7071
  */
6772
7072
  async getOptionsChain(params) {
6773
7073
  const { underlying_symbol, ...queryParams } = params;
6774
- return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, 'GET', queryParams, 'v1beta1');
7074
+ return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, "GET", queryParams, "v1beta1");
6775
7075
  }
6776
7076
  /**
6777
7077
  * Get the most recent trades for requested option contract symbols
@@ -6783,7 +7083,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6783
7083
  async getLatestOptionsTrades(params) {
6784
7084
  // Remove limit and page_token as they're not supported by this endpoint
6785
7085
  const { limit, page_token, ...requestParams } = params;
6786
- return this.makeRequest('/options/trades/latest', 'GET', requestParams, 'v1beta1');
7086
+ return this.makeRequest("/options/trades/latest", "GET", requestParams, "v1beta1");
6787
7087
  }
6788
7088
  /**
6789
7089
  * Get the most recent quotes for requested option contract symbols
@@ -6795,7 +7095,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6795
7095
  async getLatestOptionsQuotes(params) {
6796
7096
  // Remove limit and page_token as they're not supported by this endpoint
6797
7097
  const { limit, page_token, ...requestParams } = params;
6798
- return this.makeRequest('/options/quotes/latest', 'GET', requestParams, 'v1beta1');
7098
+ return this.makeRequest("/options/quotes/latest", "GET", requestParams, "v1beta1");
6799
7099
  }
6800
7100
  /**
6801
7101
  * Get historical OHLCV bars for option contract symbols
@@ -6807,18 +7107,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6807
7107
  */
6808
7108
  async getHistoricalOptionsBars(params) {
6809
7109
  const symbols = params.symbols;
6810
- const symbolsStr = symbols.join(',');
7110
+ const symbolsStr = symbols.join(",");
6811
7111
  let allBars = {};
6812
7112
  let pageToken = null;
6813
7113
  let hasMorePages = true;
6814
7114
  let totalBarsCount = 0;
6815
7115
  let pageCount = 0;
6816
7116
  // Initialize bar arrays for each symbol
6817
- symbols.forEach(symbol => {
7117
+ symbols.forEach((symbol) => {
6818
7118
  allBars[symbol] = [];
6819
7119
  });
6820
- log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
6821
- type: 'info'
7120
+ log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`, {
7121
+ type: "info",
6822
7122
  });
6823
7123
  while (hasMorePages) {
6824
7124
  pageCount++;
@@ -6826,9 +7126,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6826
7126
  ...params,
6827
7127
  ...(pageToken && { page_token: pageToken }),
6828
7128
  };
6829
- const response = await this.makeRequest('/options/bars', 'GET', requestParams, 'v1beta1');
7129
+ const response = await this.makeRequest("/options/bars", "GET", requestParams, "v1beta1");
6830
7130
  if (!response.bars) {
6831
- log$1(`No options bars data found in response for ${symbolsStr}`, { type: 'warn' });
7131
+ log$1(`No options bars data found in response for ${symbolsStr}`, {
7132
+ type: "warn",
7133
+ });
6832
7134
  break;
6833
7135
  }
6834
7136
  // Combine bars for each symbol
@@ -6840,7 +7142,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6840
7142
  allBars[symbol] = [...allBars[symbol], ...bars];
6841
7143
  pageBarsCount += bars.length;
6842
7144
  // Track date range for this page
6843
- bars.forEach(bar => {
7145
+ bars.forEach((bar) => {
6844
7146
  const barDate = new Date(bar.t);
6845
7147
  if (!earliestTimestamp || barDate < earliestTimestamp) {
6846
7148
  earliestTimestamp = barDate;
@@ -6856,21 +7158,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
6856
7158
  hasMorePages = !!pageToken;
6857
7159
  // Enhanced logging with date range and progress info
6858
7160
  const dateRangeStr = earliestTimestamp && latestTimestamp
6859
- ? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
6860
- : 'unknown range';
6861
- log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
6862
- type: 'info'
7161
+ ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
7162
+ : "unknown range";
7163
+ log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
7164
+ type: "info",
6863
7165
  });
6864
7166
  // Prevent infinite loops
6865
7167
  if (pageCount > 1000) {
6866
- log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
7168
+ log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
6867
7169
  break;
6868
7170
  }
6869
7171
  }
6870
7172
  // Final summary
6871
- const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
7173
+ const symbolCounts = Object.entries(allBars)
7174
+ .map(([symbol, bars]) => `${symbol}: ${bars.length}`)
7175
+ .join(", ");
6872
7176
  log$1(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
6873
- type: 'info'
7177
+ type: "info",
6874
7178
  });
6875
7179
  return {
6876
7180
  bars: allBars,
@@ -6887,18 +7191,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6887
7191
  */
6888
7192
  async getHistoricalOptionsTrades(params) {
6889
7193
  const symbols = params.symbols;
6890
- const symbolsStr = symbols.join(',');
7194
+ const symbolsStr = symbols.join(",");
6891
7195
  let allTrades = {};
6892
7196
  let pageToken = null;
6893
7197
  let hasMorePages = true;
6894
7198
  let totalTradesCount = 0;
6895
7199
  let pageCount = 0;
6896
7200
  // Initialize trades arrays for each symbol
6897
- symbols.forEach(symbol => {
7201
+ symbols.forEach((symbol) => {
6898
7202
  allTrades[symbol] = [];
6899
7203
  });
6900
- log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
6901
- type: 'info'
7204
+ log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || "no start"} to ${params.end || "no end"})`, {
7205
+ type: "info",
6902
7206
  });
6903
7207
  while (hasMorePages) {
6904
7208
  pageCount++;
@@ -6906,9 +7210,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6906
7210
  ...params,
6907
7211
  ...(pageToken && { page_token: pageToken }),
6908
7212
  };
6909
- const response = await this.makeRequest('/options/trades', 'GET', requestParams, 'v1beta1');
7213
+ const response = await this.makeRequest("/options/trades", "GET", requestParams, "v1beta1");
6910
7214
  if (!response.trades) {
6911
- log$1(`No options trades data found in response for ${symbolsStr}`, { type: 'warn' });
7215
+ log$1(`No options trades data found in response for ${symbolsStr}`, {
7216
+ type: "warn",
7217
+ });
6912
7218
  break;
6913
7219
  }
6914
7220
  // Combine trades for each symbol
@@ -6920,7 +7226,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6920
7226
  allTrades[symbol] = [...allTrades[symbol], ...trades];
6921
7227
  pageTradesCount += trades.length;
6922
7228
  // Track date range for this page
6923
- trades.forEach(trade => {
7229
+ trades.forEach((trade) => {
6924
7230
  const tradeDate = new Date(trade.t);
6925
7231
  if (!earliestTimestamp || tradeDate < earliestTimestamp) {
6926
7232
  earliestTimestamp = tradeDate;
@@ -6936,21 +7242,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
6936
7242
  hasMorePages = !!pageToken;
6937
7243
  // Enhanced logging with date range and progress info
6938
7244
  const dateRangeStr = earliestTimestamp && latestTimestamp
6939
- ? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
6940
- : 'unknown range';
6941
- log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
6942
- type: 'info'
7245
+ ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
7246
+ : "unknown range";
7247
+ log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
7248
+ type: "info",
6943
7249
  });
6944
7250
  // Prevent infinite loops
6945
7251
  if (pageCount > 1000) {
6946
- log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
7252
+ log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
6947
7253
  break;
6948
7254
  }
6949
7255
  }
6950
7256
  // Final summary
6951
- const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
7257
+ const symbolCounts = Object.entries(allTrades)
7258
+ .map(([symbol, trades]) => `${symbol}: ${trades.length}`)
7259
+ .join(", ");
6952
7260
  log$1(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
6953
- type: 'info'
7261
+ type: "info",
6954
7262
  });
6955
7263
  return {
6956
7264
  trades: allTrades,
@@ -6968,7 +7276,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6968
7276
  async getOptionsSnapshot(params) {
6969
7277
  // Remove limit and page_token as they may not be supported by this endpoint
6970
7278
  const { limit, page_token, ...requestParams } = params;
6971
- return this.makeRequest('/options/snapshots', 'GET', requestParams, 'v1beta1');
7279
+ return this.makeRequest("/options/snapshots", "GET", requestParams, "v1beta1");
6972
7280
  }
6973
7281
  /**
6974
7282
  * Get condition codes for options trades or quotes
@@ -6979,7 +7287,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6979
7287
  * @see https://docs.alpaca.markets/reference/optionmetaconditions
6980
7288
  */
6981
7289
  async getOptionsConditionCodes(tickType) {
6982
- return this.makeRequest(`/options/meta/conditions/${tickType}`, 'GET', undefined, 'v1beta1');
7290
+ return this.makeRequest(`/options/meta/conditions/${tickType}`, "GET", undefined, "v1beta1");
6983
7291
  }
6984
7292
  /**
6985
7293
  * Get exchange codes for options
@@ -6989,7 +7297,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6989
7297
  * @see https://docs.alpaca.markets/reference/optionmetaexchanges
6990
7298
  */
6991
7299
  async getOptionsExchangeCodes() {
6992
- return this.makeRequest('/options/meta/exchanges', 'GET', undefined, 'v1beta1');
7300
+ return this.makeRequest("/options/meta/exchanges", "GET", undefined, "v1beta1");
6993
7301
  }
6994
7302
  /**
6995
7303
  * Analyzes an array of option bars and returns a summary string
@@ -6998,7 +7306,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6998
7306
  */
6999
7307
  static analyzeOptionBars(bars) {
7000
7308
  if (!bars || bars.length === 0) {
7001
- return 'No option price data available';
7309
+ return "No option price data available";
7002
7310
  }
7003
7311
  const firstBar = bars[0];
7004
7312
  const lastBar = bars[bars.length - 1];
@@ -7022,7 +7330,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7022
7330
  */
7023
7331
  static formatOptionGreeks(greeks) {
7024
7332
  if (!greeks) {
7025
- return 'No greeks data available';
7333
+ return "No greeks data available";
7026
7334
  }
7027
7335
  const parts = [];
7028
7336
  if (greeks.delta !== undefined)
@@ -7035,7 +7343,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7035
7343
  parts.push(`Vega: ${greeks.vega.toFixed(4)}`);
7036
7344
  if (greeks.rho !== undefined)
7037
7345
  parts.push(`Rho: ${greeks.rho.toFixed(4)}`);
7038
- return parts.length > 0 ? parts.join(', ') : 'No greeks data available';
7346
+ return parts.length > 0 ? parts.join(", ") : "No greeks data available";
7039
7347
  }
7040
7348
  /**
7041
7349
  * Interprets condition codes using the provided condition codes mapping
@@ -7045,12 +7353,14 @@ class AlpacaMarketDataAPI extends EventEmitter {
7045
7353
  */
7046
7354
  static interpretConditionCodes(conditionCodes, conditionCodesMap) {
7047
7355
  if (!conditionCodes || conditionCodes.length === 0) {
7048
- return 'No conditions';
7356
+ return "No conditions";
7049
7357
  }
7050
7358
  const descriptions = conditionCodes
7051
7359
  .map((code) => conditionCodesMap[code] || `Unknown (${code})`)
7052
7360
  .filter((desc) => desc !== undefined);
7053
- return descriptions.length > 0 ? descriptions.join(', ') : 'No condition descriptions available';
7361
+ return descriptions.length > 0
7362
+ ? descriptions.join(", ")
7363
+ : "No condition descriptions available";
7054
7364
  }
7055
7365
  /**
7056
7366
  * Gets the exchange name from exchange code using the provided exchange codes mapping
@@ -7059,7 +7369,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7059
7369
  * @returns Exchange name or formatted unknown exchange
7060
7370
  */
7061
7371
  static getExchangeName(exchangeCode, exchangeCodesMap) {
7062
- return exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`;
7372
+ return (exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`);
7063
7373
  }
7064
7374
  /**
7065
7375
  * Fetches news articles from Alpaca API for a symbol, paginating through all results.
@@ -7072,7 +7382,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7072
7382
  start: new Date(Date.now() - 24 * 60 * 60 * 1000),
7073
7383
  end: new Date(),
7074
7384
  limit: 10,
7075
- sort: 'desc',
7385
+ sort: "desc",
7076
7386
  include_content: true,
7077
7387
  };
7078
7388
  const mergedParams = { ...defaultParams, ...params };
@@ -7086,38 +7396,52 @@ class AlpacaMarketDataAPI extends EventEmitter {
7086
7396
  if (!content)
7087
7397
  return undefined;
7088
7398
  // Remove excessive whitespace, newlines, and trim
7089
- return content.replace(/\s+/g, ' ').trim();
7399
+ return content.replace(/\s+/g, " ").trim();
7090
7400
  }
7091
7401
  while (hasMorePages) {
7092
7402
  const queryParams = new URLSearchParams({
7093
- ...(mergedParams.start && { start: new Date(mergedParams.start).toISOString() }),
7094
- ...(mergedParams.end && { end: new Date(mergedParams.end).toISOString() }),
7403
+ ...(mergedParams.start && {
7404
+ start: new Date(mergedParams.start).toISOString(),
7405
+ }),
7406
+ ...(mergedParams.end && {
7407
+ end: new Date(mergedParams.end).toISOString(),
7408
+ }),
7095
7409
  ...(symbol && { symbols: symbol }),
7096
- ...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
7410
+ ...(mergedParams.limit && {
7411
+ limit: Math.min(50, maxLimit - fetchedCount).toString(),
7412
+ }),
7097
7413
  ...(mergedParams.sort && { sort: mergedParams.sort }),
7098
- ...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
7414
+ ...(mergedParams.include_content !== undefined
7415
+ ? { include_content: mergedParams.include_content.toString() }
7416
+ : {}),
7099
7417
  ...(pageToken && { page_token: pageToken }),
7100
7418
  });
7101
7419
  const url = `${this.v1beta1url}/news?${queryParams}`;
7102
- log$1(`Fetching news from: ${url}`, { type: 'debug', symbol });
7420
+ log$1(`Fetching news from: ${url}`, { type: "debug", symbol });
7103
7421
  const response = await fetch(url, {
7104
- method: 'GET',
7422
+ method: "GET",
7105
7423
  headers: this.headers,
7106
7424
  });
7107
7425
  if (!response.ok) {
7108
7426
  const errorText = await response.text();
7109
- log$1(`Alpaca news API error (${response.status}): ${errorText}`, { type: 'error', symbol });
7427
+ log$1(`Alpaca news API error (${response.status}): ${errorText}`, {
7428
+ type: "error",
7429
+ symbol,
7430
+ });
7110
7431
  throw new Error(`Alpaca news API error (${response.status}): ${errorText}`);
7111
7432
  }
7112
7433
  const data = await response.json();
7113
7434
  if (!data.news || !Array.isArray(data.news)) {
7114
- log$1(`No news data found in Alpaca response for ${symbol}`, { type: 'warn', symbol });
7435
+ log$1(`No news data found in Alpaca response for ${symbol}`, {
7436
+ type: "warn",
7437
+ symbol,
7438
+ });
7115
7439
  break;
7116
7440
  }
7117
7441
  const transformedNews = data.news.map((article) => ({
7118
7442
  symbols: article.symbols,
7119
7443
  title: article.headline,
7120
- summary: cleanContent(article.summary) ?? '',
7444
+ summary: cleanContent(article.summary) ?? "",
7121
7445
  content: article.content ? cleanContent(article.content) : undefined,
7122
7446
  url: article.url,
7123
7447
  source: article.source,
@@ -7130,7 +7454,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7130
7454
  fetchedCount = newsArticles.length;
7131
7455
  pageToken = data.next_page_token || null;
7132
7456
  hasMorePages = !!pageToken && (!maxLimit || fetchedCount < maxLimit);
7133
- log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: 'debug', symbol });
7457
+ log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: "debug", symbol });
7134
7458
  if (maxLimit && fetchedCount >= maxLimit) {
7135
7459
  newsArticles = newsArticles.slice(0, maxLimit);
7136
7460
  break;
@@ -7161,11 +7485,11 @@ const marketDataAPI = AlpacaMarketDataAPI.getInstance();
7161
7485
  */
7162
7486
  function formatCurrency(value) {
7163
7487
  if (isNaN(value)) {
7164
- return '$0.00';
7488
+ return "$0.00";
7165
7489
  }
7166
- return new Intl.NumberFormat('en-US', {
7167
- style: 'currency',
7168
- currency: 'USD',
7490
+ return new Intl.NumberFormat("en-US", {
7491
+ style: "currency",
7492
+ currency: "USD",
7169
7493
  }).format(value);
7170
7494
  }
7171
7495
  /**
@@ -7178,13 +7502,13 @@ function formatCurrency(value) {
7178
7502
  */
7179
7503
  function formatNumber(value) {
7180
7504
  if (isNaN(value)) {
7181
- return '0';
7505
+ return "0";
7182
7506
  }
7183
- return new Intl.NumberFormat('en-US').format(value);
7507
+ return new Intl.NumberFormat("en-US").format(value);
7184
7508
  }
7185
7509
 
7186
7510
  const log = (message) => {
7187
- console.log(`[${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })}] ${message}`);
7511
+ console.log(`[${new Date().toLocaleString("en-US", { timeZone: "America/New_York" })}] ${message}`);
7188
7512
  };
7189
7513
  // async function testCreateEquitiesTrade() {
7190
7514
  // try {
@@ -7408,18 +7732,18 @@ const log = (message) => {
7408
7732
  // testing retrieving pre-market data (just 9:00am to 9:30am on 1 july 2025 for SPY) using the market data api
7409
7733
  async function testPreMarketData() {
7410
7734
  try {
7411
- log('Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...');
7735
+ log("Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...");
7412
7736
  // Set up the time range in America/New_York, convert to UTC ISO strings
7413
- const symbol = 'SPY';
7414
- const nyTimeZone = 'America/New_York';
7737
+ const symbol = "SPY";
7738
+ const nyTimeZone = "America/New_York";
7415
7739
  // 9:00am and 9:30am in NY time
7416
- const startNY = new Date('2025-07-01T09:00:00-04:00');
7417
- const endNY = new Date('2025-07-01T09:30:00-04:00');
7740
+ const startNY = new Date("2025-07-01T09:00:00-04:00");
7741
+ const endNY = new Date("2025-07-01T09:30:00-04:00");
7418
7742
  const startUTC = startNY.toISOString();
7419
7743
  const endUTC = endNY.toISOString();
7420
7744
  const barsResponse = await marketDataAPI.getHistoricalBars({
7421
7745
  symbols: [symbol],
7422
- timeframe: '1Min',
7746
+ timeframe: "1Min",
7423
7747
  start: startUTC,
7424
7748
  end: endUTC,
7425
7749
  limit: 1000,
@@ -7427,19 +7751,21 @@ async function testPreMarketData() {
7427
7751
  const bars = barsResponse.bars[symbol] || [];
7428
7752
  log(`Fetched ${bars.length} 1-min bars for SPY from 9:00am to 9:30am (NY) on 2025-07-01.`);
7429
7753
  if (bars.length === 0) {
7430
- log('No pre-market bars returned.');
7754
+ log("No pre-market bars returned.");
7431
7755
  return;
7432
7756
  }
7433
7757
  // Print each bar
7434
7758
  bars.forEach((bar, i) => {
7435
- const barTime = new Date(bar.t).toLocaleString('en-US', { timeZone: nyTimeZone });
7759
+ const barTime = new Date(bar.t).toLocaleString("en-US", {
7760
+ timeZone: nyTimeZone,
7761
+ });
7436
7762
  log(`Bar ${i + 1}: ${barTime} | O: ${formatCurrency(bar.o)} H: ${formatCurrency(bar.h)} L: ${formatCurrency(bar.l)} C: ${formatCurrency(bar.c)} V: ${formatNumber(bar.v)} VWAP: ${formatCurrency(bar.vw)} N: ${bar.n}`);
7437
7763
  });
7438
7764
  // Print summary
7439
7765
  const summary = AlpacaMarketDataAPI.analyzeBars(bars);
7440
7766
  if (summary)
7441
7767
  log(`Summary: ${summary}`);
7442
- log('Pre-market data test complete.');
7768
+ log("Pre-market data test complete.");
7443
7769
  }
7444
7770
  catch (error) {
7445
7771
  log(`❌ Error in testPreMarketData: ${error instanceof Error ? error.message : error}`);