@adaptic/utils 0.0.382 → 0.0.900

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 (225) hide show
  1. package/README.md +153 -61
  2. package/dist/index.cjs +60090 -5244
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.mjs +59829 -5244
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/test.js +733 -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 +95 -0
  55. package/dist/types/alpaca/client.d.ts.map +1 -0
  56. package/dist/types/alpaca/crypto/data.d.ts +281 -0
  57. package/dist/types/alpaca/crypto/data.d.ts.map +1 -0
  58. package/dist/types/alpaca/crypto/index.d.ts +75 -0
  59. package/dist/types/alpaca/crypto/index.d.ts.map +1 -0
  60. package/dist/types/alpaca/crypto/orders.d.ts +221 -0
  61. package/dist/types/alpaca/crypto/orders.d.ts.map +1 -0
  62. package/dist/types/alpaca/index.d.ts +205 -0
  63. package/dist/types/alpaca/index.d.ts.map +1 -0
  64. package/dist/types/alpaca/legacy/account.d.ts +34 -0
  65. package/dist/types/alpaca/legacy/account.d.ts.map +1 -0
  66. package/dist/types/alpaca/legacy/assets.d.ts +13 -0
  67. package/dist/types/alpaca/legacy/assets.d.ts.map +1 -0
  68. package/dist/types/alpaca/legacy/auth.d.ts +18 -0
  69. package/dist/types/alpaca/legacy/auth.d.ts.map +1 -0
  70. package/dist/types/alpaca/legacy/index.d.ts +15 -0
  71. package/dist/types/alpaca/legacy/index.d.ts.map +1 -0
  72. package/dist/types/alpaca/legacy/market-data.d.ts +32 -0
  73. package/dist/types/alpaca/legacy/market-data.d.ts.map +1 -0
  74. package/dist/types/alpaca/legacy/orders.d.ts +84 -0
  75. package/dist/types/alpaca/legacy/orders.d.ts.map +1 -0
  76. package/dist/types/alpaca/legacy/positions.d.ts +66 -0
  77. package/dist/types/alpaca/legacy/positions.d.ts.map +1 -0
  78. package/dist/types/alpaca/legacy/utils.d.ts +18 -0
  79. package/dist/types/alpaca/legacy/utils.d.ts.map +1 -0
  80. package/dist/types/alpaca/market-data/bars.d.ts +142 -0
  81. package/dist/types/alpaca/market-data/bars.d.ts.map +1 -0
  82. package/dist/types/alpaca/market-data/index.d.ts +13 -0
  83. package/dist/types/alpaca/market-data/index.d.ts.map +1 -0
  84. package/dist/types/alpaca/market-data/news.d.ts +87 -0
  85. package/dist/types/alpaca/market-data/news.d.ts.map +1 -0
  86. package/dist/types/alpaca/market-data/quotes.d.ts +85 -0
  87. package/dist/types/alpaca/market-data/quotes.d.ts.map +1 -0
  88. package/dist/types/alpaca/market-data/trades.d.ts +98 -0
  89. package/dist/types/alpaca/market-data/trades.d.ts.map +1 -0
  90. package/dist/types/alpaca/options/contracts.d.ts +279 -0
  91. package/dist/types/alpaca/options/contracts.d.ts.map +1 -0
  92. package/dist/types/alpaca/options/data.d.ts +126 -0
  93. package/dist/types/alpaca/options/data.d.ts.map +1 -0
  94. package/dist/types/alpaca/options/index.d.ts +17 -0
  95. package/dist/types/alpaca/options/index.d.ts.map +1 -0
  96. package/dist/types/alpaca/options/orders.d.ts +366 -0
  97. package/dist/types/alpaca/options/orders.d.ts.map +1 -0
  98. package/dist/types/alpaca/options/strategies.d.ts +224 -0
  99. package/dist/types/alpaca/options/strategies.d.ts.map +1 -0
  100. package/dist/types/alpaca/streams/base-stream.d.ts +143 -0
  101. package/dist/types/alpaca/streams/base-stream.d.ts.map +1 -0
  102. package/dist/types/alpaca/streams/crypto-stream.d.ts +173 -0
  103. package/dist/types/alpaca/streams/crypto-stream.d.ts.map +1 -0
  104. package/dist/types/alpaca/streams/index.d.ts +54 -0
  105. package/dist/types/alpaca/streams/index.d.ts.map +1 -0
  106. package/dist/types/alpaca/streams/option-stream.d.ts +167 -0
  107. package/dist/types/alpaca/streams/option-stream.d.ts.map +1 -0
  108. package/dist/types/alpaca/streams/stock-stream.d.ts +176 -0
  109. package/dist/types/alpaca/streams/stock-stream.d.ts.map +1 -0
  110. package/dist/types/alpaca/streams/stream-manager.d.ts +277 -0
  111. package/dist/types/alpaca/streams/stream-manager.d.ts.map +1 -0
  112. package/dist/types/alpaca/streams/trading-stream.d.ts +186 -0
  113. package/dist/types/alpaca/streams/trading-stream.d.ts.map +1 -0
  114. package/dist/types/alpaca/streams.d.ts +88 -0
  115. package/dist/types/alpaca/streams.d.ts.map +1 -0
  116. package/dist/types/alpaca/test-imports.d.ts +7 -0
  117. package/dist/types/alpaca/test-imports.d.ts.map +1 -0
  118. package/dist/types/alpaca/trading/account.d.ts +198 -0
  119. package/dist/types/alpaca/trading/account.d.ts.map +1 -0
  120. package/dist/types/alpaca/trading/bracket-orders.d.ts +162 -0
  121. package/dist/types/alpaca/trading/bracket-orders.d.ts.map +1 -0
  122. package/dist/types/alpaca/trading/clock.d.ts +99 -0
  123. package/dist/types/alpaca/trading/clock.d.ts.map +1 -0
  124. package/dist/types/alpaca/trading/index.d.ts +15 -0
  125. package/dist/types/alpaca/trading/index.d.ts.map +1 -0
  126. package/dist/types/alpaca/trading/oco-orders.d.ts +203 -0
  127. package/dist/types/alpaca/trading/oco-orders.d.ts.map +1 -0
  128. package/dist/types/alpaca/trading/order-utils.d.ts +404 -0
  129. package/dist/types/alpaca/trading/order-utils.d.ts.map +1 -0
  130. package/dist/types/alpaca/trading/orders.d.ts +199 -0
  131. package/dist/types/alpaca/trading/orders.d.ts.map +1 -0
  132. package/dist/types/alpaca/trading/oto-orders.d.ts +282 -0
  133. package/dist/types/alpaca/trading/oto-orders.d.ts.map +1 -0
  134. package/dist/types/alpaca/trading/positions.d.ts +389 -0
  135. package/dist/types/alpaca/trading/positions.d.ts.map +1 -0
  136. package/dist/types/alpaca/trading/smart-orders.d.ts +301 -0
  137. package/dist/types/alpaca/trading/smart-orders.d.ts.map +1 -0
  138. package/dist/types/alpaca/trading/trailing-stops.d.ts +240 -0
  139. package/dist/types/alpaca/trading/trailing-stops.d.ts.map +1 -0
  140. package/dist/types/alpaca-market-data-api.d.ts +12 -10
  141. package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
  142. package/dist/types/alpaca-trading-api.d.ts +12 -12
  143. package/dist/types/alpaca-trading-api.d.ts.map +1 -1
  144. package/dist/types/alphavantage.d.ts +1 -1
  145. package/dist/types/alphavantage.d.ts.map +1 -1
  146. package/dist/types/asset-allocation-algorithm.d.ts +7 -1
  147. package/dist/types/asset-allocation-algorithm.d.ts.map +1 -1
  148. package/dist/types/cache/stampede-protected-cache.d.ts.map +1 -1
  149. package/dist/types/config/api-endpoints.d.ts +94 -0
  150. package/dist/types/config/api-endpoints.d.ts.map +1 -0
  151. package/dist/types/crypto.d.ts +2 -2
  152. package/dist/types/crypto.d.ts.map +1 -1
  153. package/dist/types/display-manager.d.ts +1 -1
  154. package/dist/types/display-manager.d.ts.map +1 -1
  155. package/dist/types/errors/index.d.ts +130 -0
  156. package/dist/types/errors/index.d.ts.map +1 -0
  157. package/dist/types/examples/asset-allocation-example.d.ts +7 -6
  158. package/dist/types/examples/asset-allocation-example.d.ts.map +1 -1
  159. package/dist/types/examples/rate-limiter-example.d.ts +7 -0
  160. package/dist/types/examples/rate-limiter-example.d.ts.map +1 -0
  161. package/dist/types/format-tools.d.ts.map +1 -1
  162. package/dist/types/http-timeout.d.ts +37 -0
  163. package/dist/types/http-timeout.d.ts.map +1 -0
  164. package/dist/types/index.d.ts +393 -55
  165. package/dist/types/index.d.ts.map +1 -1
  166. package/dist/types/logger.d.ts +56 -0
  167. package/dist/types/logger.d.ts.map +1 -0
  168. package/dist/types/logging.d.ts +1 -1
  169. package/dist/types/logging.d.ts.map +1 -1
  170. package/dist/types/market-hours.d.ts.map +1 -1
  171. package/dist/types/market-time.d.ts +75 -13
  172. package/dist/types/market-time.d.ts.map +1 -1
  173. package/dist/types/metrics-calcs.d.ts.map +1 -1
  174. package/dist/types/misc-utils.d.ts +4 -1
  175. package/dist/types/misc-utils.d.ts.map +1 -1
  176. package/dist/types/performance-metrics.d.ts +4 -4
  177. package/dist/types/performance-metrics.d.ts.map +1 -1
  178. package/dist/types/polygon-indices.d.ts +3 -3
  179. package/dist/types/polygon-indices.d.ts.map +1 -1
  180. package/dist/types/polygon.d.ts +1 -1
  181. package/dist/types/polygon.d.ts.map +1 -1
  182. package/dist/types/price-utils.d.ts.map +1 -1
  183. package/dist/types/rate-limiter.d.ts +171 -0
  184. package/dist/types/rate-limiter.d.ts.map +1 -0
  185. package/dist/types/schemas/alpaca-schemas.d.ts +779 -0
  186. package/dist/types/schemas/alpaca-schemas.d.ts.map +1 -0
  187. package/dist/types/schemas/alphavantage-schemas.d.ts +255 -0
  188. package/dist/types/schemas/alphavantage-schemas.d.ts.map +1 -0
  189. package/dist/types/schemas/index.d.ts +21 -0
  190. package/dist/types/schemas/index.d.ts.map +1 -0
  191. package/dist/types/schemas/polygon-schemas.d.ts +551 -0
  192. package/dist/types/schemas/polygon-schemas.d.ts.map +1 -0
  193. package/dist/types/schemas/validate-response.d.ts +88 -0
  194. package/dist/types/schemas/validate-response.d.ts.map +1 -0
  195. package/dist/types/technical-analysis.d.ts +9 -9
  196. package/dist/types/technical-analysis.d.ts.map +1 -1
  197. package/dist/types/time-utils.d.ts.map +1 -1
  198. package/dist/types/types/adaptic-types.d.ts +1 -1
  199. package/dist/types/types/adaptic-types.d.ts.map +1 -1
  200. package/dist/types/types/alpaca-types.d.ts +172 -98
  201. package/dist/types/types/alpaca-types.d.ts.map +1 -1
  202. package/dist/types/types/alphavantage-types.d.ts +2 -2
  203. package/dist/types/types/asset-allocation-types.d.ts +11 -11
  204. package/dist/types/types/index.d.ts +8 -8
  205. package/dist/types/types/index.d.ts.map +1 -1
  206. package/dist/types/types/logging-types.d.ts +2 -2
  207. package/dist/types/types/logging-types.d.ts.map +1 -1
  208. package/dist/types/types/market-time-types.d.ts +4 -4
  209. package/dist/types/types/market-time-types.d.ts.map +1 -1
  210. package/dist/types/types/metrics-types.d.ts +3 -3
  211. package/dist/types/types/metrics-types.d.ts.map +1 -1
  212. package/dist/types/types/polygon-indices-types.d.ts +6 -6
  213. package/dist/types/types/polygon-types.d.ts +3 -3
  214. package/dist/types/types/ta-types.d.ts +3 -3
  215. package/dist/types/utils/auth-validator.d.ts +32 -0
  216. package/dist/types/utils/auth-validator.d.ts.map +1 -0
  217. package/dist/types/utils/http-keep-alive.d.ts +110 -0
  218. package/dist/types/utils/http-keep-alive.d.ts.map +1 -0
  219. package/dist/types/utils/paginator.d.ts +154 -0
  220. package/dist/types/utils/paginator.d.ts.map +1 -0
  221. package/dist/types/utils/retry.d.ts +78 -0
  222. package/dist/types/utils/retry.d.ts.map +1 -0
  223. package/package.json +25 -5
  224. package/dist/types/alpaca-functions.d.ts +0 -233
  225. 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,82 @@ 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
+ * @param options - Validation options
6340
+ * @param options.throwOnMissing - If false, missing credentials will log a warning instead of throwing (default: true)
6341
+ * @throws {Error} If credentials are invalid (when throwOnMissing is true)
6342
+ * @returns {boolean} True if credentials are valid, false if missing (when throwOnMissing is false)
6343
+ */
6344
+ function validateAlpacaCredentials(auth, options = { throwOnMissing: true }) {
6345
+ const { throwOnMissing = true } = options;
6346
+ // Check for missing or empty API key
6347
+ if (!auth.apiKey ||
6348
+ typeof auth.apiKey !== "string" ||
6349
+ auth.apiKey.trim().length === 0) {
6350
+ if (throwOnMissing) {
6351
+ throw new Error("Invalid Alpaca API key: must be a non-empty string");
6352
+ }
6353
+ console.warn("[AlpacaAPI] API key not configured. Market data features will be unavailable.");
6354
+ return false;
6355
+ }
6356
+ // Check for missing or empty API secret
6357
+ if (!auth.apiSecret ||
6358
+ typeof auth.apiSecret !== "string" ||
6359
+ auth.apiSecret.trim().length === 0) {
6360
+ if (throwOnMissing) {
6361
+ throw new Error("Invalid Alpaca API secret: must be a non-empty string");
6362
+ }
6363
+ console.warn("[AlpacaAPI] API secret not configured. Market data features will be unavailable.");
6364
+ return false;
6365
+ }
6366
+ // Alpaca keys are typically 20+ characters
6367
+ if (auth.apiKey.length < 10) {
6368
+ if (throwOnMissing) {
6369
+ throw new Error("Alpaca API key appears to be too short");
6370
+ }
6371
+ console.warn("[AlpacaAPI] API key appears to be too short.");
6372
+ return false;
6373
+ }
6374
+ return true;
6375
+ }
6376
+
6377
+ /**
6378
+ * HTTP request timeout utilities
6379
+ * Provides configurable timeout handling for external API calls
6380
+ */
6381
+ /**
6382
+ * Default timeout values for different external APIs (in milliseconds)
6383
+ * Can be overridden via environment variables
6384
+ */
6385
+ const DEFAULT_TIMEOUTS = {
6386
+ ALPACA_API: parseInt(process.env.ALPACA_API_TIMEOUT || "30000", 10),
6387
+ POLYGON_API: parseInt(process.env.POLYGON_API_TIMEOUT || "30000", 10),
6388
+ ALPHA_VANTAGE: parseInt(process.env.ALPHA_VANTAGE_API_TIMEOUT || "30000", 10),
6389
+ GENERAL: parseInt(process.env.HTTP_TIMEOUT || "30000", 10),
6390
+ };
6391
+ /**
6392
+ * Creates an AbortSignal that times out after the specified duration
6393
+ * Compatible with fetch API
6394
+ * @param ms - Timeout duration in milliseconds
6395
+ * @returns AbortSignal that will abort after the specified duration
6396
+ */
6397
+ function createTimeoutSignal(ms) {
6398
+ return AbortSignal.timeout(ms);
6399
+ }
6400
+
6401
+ const log$1 = (message, options = { type: "info" }) => {
6402
+ log$2(message, { ...options, source: "AlpacaMarketDataAPI" });
6133
6403
  };
6134
6404
  // Default settings for market data API
6135
- const DEFAULT_ADJUSTMENT = 'all';
6136
- const DEFAULT_FEED = 'sip';
6137
- const DEFAULT_CURRENCY = 'USD';
6405
+ const DEFAULT_ADJUSTMENT = "all";
6406
+ const DEFAULT_FEED = "sip";
6407
+ const DEFAULT_CURRENCY = "USD";
6138
6408
  /**
6139
6409
  * Singleton class for interacting with Alpaca Market Data API
6140
6410
  * Provides methods for fetching historical bars, latest bars, last trades, latest trades, latest quotes, and latest quote for a single symbol
@@ -6145,56 +6415,84 @@ class AlpacaMarketDataAPI extends EventEmitter {
6145
6415
  dataURL;
6146
6416
  apiURL;
6147
6417
  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
6418
+ /** Whether API credentials are valid and available. False during build time when env vars are missing. */
6419
+ credentialsValid = false;
6420
+ stockStreamUrl = getStockStreamUrl("PRODUCTION"); // production values
6421
+ optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // production values
6422
+ cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // production values
6151
6423
  stockWs = null;
6152
6424
  optionWs = null;
6153
6425
  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';
6426
+ stockSubscriptions = {
6427
+ trades: [],
6428
+ quotes: [],
6429
+ bars: [],
6430
+ };
6431
+ optionSubscriptions = {
6432
+ trades: [],
6433
+ quotes: [],
6434
+ bars: [],
6435
+ };
6436
+ cryptoSubscriptions = {
6437
+ trades: [],
6438
+ quotes: [],
6439
+ bars: [],
6440
+ };
6441
+ setMode(mode = "production") {
6442
+ if (mode === "sandbox") {
6443
+ // sandbox mode
6444
+ this.stockStreamUrl = WEBSOCKET_STREAMS.STOCKS.PRODUCTION; // sandbox uses production for stocks
6445
+ this.optionStreamUrl = getOptionsStreamUrl("SANDBOX");
6446
+ this.cryptoStreamUrl = getCryptoStreamUrl("SANDBOX");
6162
6447
  }
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
6448
+ else if (mode === "test") {
6449
+ // test mode, can only use ticker FAKEPACA
6450
+ this.stockStreamUrl = getStockStreamUrl("TEST");
6451
+ this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // there's no test mode for options
6452
+ this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // there's no test mode for crypto
6167
6453
  }
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';
6454
+ else {
6455
+ // production
6456
+ this.stockStreamUrl = getStockStreamUrl("PRODUCTION");
6457
+ this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION");
6458
+ this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION");
6172
6459
  }
6173
6460
  }
6174
6461
  getMode() {
6175
- if (this.stockStreamUrl.includes('sandbox')) {
6176
- return 'sandbox';
6462
+ if (this.stockStreamUrl.includes("sandbox")) {
6463
+ return "sandbox";
6177
6464
  }
6178
- else if (this.stockStreamUrl.includes('test')) {
6179
- return 'test';
6465
+ else if (this.stockStreamUrl.includes("test")) {
6466
+ return "test";
6180
6467
  }
6181
6468
  else {
6182
- return 'production';
6469
+ return "production";
6183
6470
  }
6184
6471
  }
6185
6472
  constructor() {
6186
6473
  super();
6187
- this.dataURL = 'https://data.alpaca.markets/v2';
6474
+ // Validate credentials from environment variables before initializing
6475
+ // Use throwOnMissing: false to allow initialization during build time
6476
+ // when env vars are not available. Features will be unavailable until
6477
+ // credentials are provided at runtime.
6478
+ const apiKey = process.env.ALPACA_API_KEY || "";
6479
+ const apiSecret = process.env.ALPACA_SECRET_KEY || "";
6480
+ this.credentialsValid = validateAlpacaCredentials({
6481
+ apiKey,
6482
+ apiSecret,
6483
+ isPaper: process.env.ALPACA_ACCOUNT_TYPE === "PAPER",
6484
+ }, { throwOnMissing: false });
6485
+ this.dataURL = MARKET_DATA_API.STOCKS;
6188
6486
  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
6487
+ process.env.ALPACA_ACCOUNT_TYPE === "PAPER"
6488
+ ? getTradingApiUrl("PAPER")
6489
+ : getTradingApiUrl("LIVE"); // used by some, e.g. getAssets
6490
+ this.v1beta1url = MARKET_DATA_API.OPTIONS; // used for options endpoints
6491
+ this.setMode("production"); // sets stockStreamUrl and optionStreamUrl
6194
6492
  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',
6493
+ "APCA-API-KEY-ID": apiKey,
6494
+ "APCA-API-SECRET-KEY": apiSecret,
6495
+ "Content-Type": "application/json",
6198
6496
  };
6199
6497
  }
6200
6498
  static getInstance() {
@@ -6211,73 +6509,75 @@ class AlpacaMarketDataAPI extends EventEmitter {
6211
6509
  }
6212
6510
  connect(streamType) {
6213
6511
  let url;
6214
- if (streamType === 'stock') {
6512
+ if (streamType === "stock") {
6215
6513
  url = this.stockStreamUrl;
6216
6514
  }
6217
- else if (streamType === 'option') {
6515
+ else if (streamType === "option") {
6218
6516
  url = this.optionStreamUrl;
6219
6517
  }
6220
6518
  else {
6221
6519
  url = this.cryptoStreamUrl;
6222
6520
  }
6223
6521
  const ws = new WebSocket(url);
6224
- if (streamType === 'stock') {
6522
+ if (streamType === "stock") {
6225
6523
  this.stockWs = ws;
6226
6524
  }
6227
- else if (streamType === 'option') {
6525
+ else if (streamType === "option") {
6228
6526
  this.optionWs = ws;
6229
6527
  }
6230
6528
  else {
6231
6529
  this.cryptoWs = ws;
6232
6530
  }
6233
- ws.on('open', () => {
6234
- log$1(`${streamType} stream connected`, { type: 'info' });
6531
+ ws.on("open", () => {
6532
+ log$1(`${streamType} stream connected`, { type: "info" });
6235
6533
  const authMessage = {
6236
- action: 'auth',
6534
+ action: "auth",
6237
6535
  key: process.env.ALPACA_API_KEY,
6238
6536
  secret: process.env.ALPACA_SECRET_KEY,
6239
6537
  };
6240
6538
  ws.send(JSON.stringify(authMessage));
6241
6539
  });
6242
- ws.on('message', (data) => {
6540
+ ws.on("message", (data) => {
6243
6541
  const rawData = data.toString();
6244
6542
  let messages;
6245
6543
  try {
6246
6544
  messages = JSON.parse(rawData);
6247
6545
  }
6248
6546
  catch (e) {
6249
- log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type: 'error' });
6547
+ log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type: "error" });
6250
6548
  return;
6251
6549
  }
6252
6550
  for (const message of messages) {
6253
- if (message.T === 'success' && message.msg === 'authenticated') {
6254
- log$1(`${streamType} stream authenticated`, { type: 'info' });
6551
+ if (message.T === "success" && message.msg === "authenticated") {
6552
+ log$1(`${streamType} stream authenticated`, { type: "info" });
6255
6553
  this.sendSubscription(streamType);
6256
6554
  }
6257
- else if (message.T === 'success' && message.msg === 'connected') {
6258
- log$1(`${streamType} stream connected message received`, { type: 'debug' });
6555
+ else if (message.T === "success" && message.msg === "connected") {
6556
+ log$1(`${streamType} stream connected message received`, {
6557
+ type: "debug",
6558
+ });
6259
6559
  }
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' });
6560
+ else if (message.T === "subscription") {
6561
+ log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type: "info" });
6262
6562
  }
6263
- else if (message.T === 'error') {
6264
- log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: 'error' });
6563
+ else if (message.T === "error") {
6564
+ log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: "error" });
6265
6565
  }
6266
6566
  else if (message.S) {
6267
6567
  super.emit(`${streamType}-${message.T}`, message);
6268
6568
  super.emit(`${streamType}-data`, message);
6269
6569
  }
6270
6570
  else {
6271
- log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: 'debug' });
6571
+ log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: "debug" });
6272
6572
  }
6273
6573
  }
6274
6574
  });
6275
- ws.on('close', () => {
6276
- log$1(`${streamType} stream disconnected`, { type: 'warn' });
6277
- if (streamType === 'stock') {
6575
+ ws.on("close", () => {
6576
+ log$1(`${streamType} stream disconnected`, { type: "warn" });
6577
+ if (streamType === "stock") {
6278
6578
  this.stockWs = null;
6279
6579
  }
6280
- else if (streamType === 'option') {
6580
+ else if (streamType === "option") {
6281
6581
  this.optionWs = null;
6282
6582
  }
6283
6583
  else {
@@ -6285,18 +6585,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6285
6585
  }
6286
6586
  // Optional: implement reconnect logic
6287
6587
  });
6288
- ws.on('error', (error) => {
6289
- log$1(`${streamType} stream error: ${error.message}`, { type: 'error' });
6588
+ ws.on("error", (error) => {
6589
+ log$1(`${streamType} stream error: ${error.message}`, { type: "error" });
6290
6590
  });
6291
6591
  }
6292
6592
  sendSubscription(streamType) {
6293
6593
  let ws;
6294
6594
  let subscriptions;
6295
- if (streamType === 'stock') {
6595
+ if (streamType === "stock") {
6296
6596
  ws = this.stockWs;
6297
6597
  subscriptions = this.stockSubscriptions;
6298
6598
  }
6299
- else if (streamType === 'option') {
6599
+ else if (streamType === "option") {
6300
6600
  ws = this.optionWs;
6301
6601
  subscriptions = this.optionSubscriptions;
6302
6602
  }
@@ -6305,7 +6605,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6305
6605
  subscriptions = this.cryptoSubscriptions;
6306
6606
  }
6307
6607
  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',
6608
+ type: "debug",
6309
6609
  });
6310
6610
  if (ws && ws.readyState === WebSocket.OPEN) {
6311
6611
  const subMessagePayload = {};
@@ -6320,34 +6620,40 @@ class AlpacaMarketDataAPI extends EventEmitter {
6320
6620
  }
6321
6621
  if (Object.keys(subMessagePayload).length > 0) {
6322
6622
  const subMessage = {
6323
- action: 'subscribe',
6623
+ action: "subscribe",
6324
6624
  ...subMessagePayload,
6325
6625
  };
6326
6626
  const messageJson = JSON.stringify(subMessage);
6327
- log$1(`Sending ${streamType} subscription: ${messageJson}`, { type: 'info' });
6627
+ log$1(`Sending ${streamType} subscription: ${messageJson}`, {
6628
+ type: "info",
6629
+ });
6328
6630
  ws.send(messageJson);
6329
6631
  }
6330
6632
  else {
6331
- log$1(`No ${streamType} subscriptions to send (all arrays empty)`, { type: 'debug' });
6633
+ log$1(`No ${streamType} subscriptions to send (all arrays empty)`, {
6634
+ type: "debug",
6635
+ });
6332
6636
  }
6333
6637
  }
6334
6638
  else {
6335
- log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, { type: 'warn' });
6639
+ log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, {
6640
+ type: "warn",
6641
+ });
6336
6642
  }
6337
6643
  }
6338
6644
  connectStockStream() {
6339
6645
  if (!this.stockWs) {
6340
- this.connect('stock');
6646
+ this.connect("stock");
6341
6647
  }
6342
6648
  }
6343
6649
  connectOptionStream() {
6344
6650
  if (!this.optionWs) {
6345
- this.connect('option');
6651
+ this.connect("option");
6346
6652
  }
6347
6653
  }
6348
6654
  connectCryptoStream() {
6349
6655
  if (!this.cryptoWs) {
6350
- this.connect('crypto');
6656
+ this.connect("crypto");
6351
6657
  }
6352
6658
  }
6353
6659
  disconnectStockStream() {
@@ -6371,22 +6677,22 @@ class AlpacaMarketDataAPI extends EventEmitter {
6371
6677
  * @returns True if the stream is connected
6372
6678
  */
6373
6679
  isStreamConnected(streamType) {
6374
- if (streamType === 'stock') {
6375
- return this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN;
6680
+ if (streamType === "stock") {
6681
+ return (this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN);
6376
6682
  }
6377
- else if (streamType === 'option') {
6378
- return this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN;
6683
+ else if (streamType === "option") {
6684
+ return (this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN);
6379
6685
  }
6380
6686
  else {
6381
- return this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN;
6687
+ return (this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN);
6382
6688
  }
6383
6689
  }
6384
6690
  subscribe(streamType, subscriptions) {
6385
6691
  let currentSubscriptions;
6386
- if (streamType === 'stock') {
6692
+ if (streamType === "stock") {
6387
6693
  currentSubscriptions = this.stockSubscriptions;
6388
6694
  }
6389
- else if (streamType === 'option') {
6695
+ else if (streamType === "option") {
6390
6696
  currentSubscriptions = this.optionSubscriptions;
6391
6697
  }
6392
6698
  else {
@@ -6394,17 +6700,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
6394
6700
  }
6395
6701
  Object.entries(subscriptions).forEach(([key, value]) => {
6396
6702
  if (value) {
6397
- currentSubscriptions[key] = [...new Set([...(currentSubscriptions[key] || []), ...value])];
6703
+ currentSubscriptions[key] = [
6704
+ ...new Set([...(currentSubscriptions[key] || []), ...value]),
6705
+ ];
6398
6706
  }
6399
6707
  });
6400
6708
  this.sendSubscription(streamType);
6401
6709
  }
6402
6710
  unsubscribe(streamType, subscriptions) {
6403
6711
  let currentSubscriptions;
6404
- if (streamType === 'stock') {
6712
+ if (streamType === "stock") {
6405
6713
  currentSubscriptions = this.stockSubscriptions;
6406
6714
  }
6407
- else if (streamType === 'option') {
6715
+ else if (streamType === "option") {
6408
6716
  currentSubscriptions = this.optionSubscriptions;
6409
6717
  }
6410
6718
  else {
@@ -6412,18 +6720,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6412
6720
  }
6413
6721
  Object.entries(subscriptions).forEach(([key, value]) => {
6414
6722
  if (value) {
6415
- currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
6723
+ currentSubscriptions[key] = (currentSubscriptions[key] || []).filter((s) => !value.includes(s));
6416
6724
  }
6417
6725
  });
6418
6726
  const unsubMessage = {
6419
- action: 'unsubscribe',
6727
+ action: "unsubscribe",
6420
6728
  ...subscriptions,
6421
6729
  };
6422
6730
  let ws;
6423
- if (streamType === 'stock') {
6731
+ if (streamType === "stock") {
6424
6732
  ws = this.stockWs;
6425
6733
  }
6426
- else if (streamType === 'option') {
6734
+ else if (streamType === "option") {
6427
6735
  ws = this.optionWs;
6428
6736
  }
6429
6737
  else {
@@ -6433,16 +6741,20 @@ class AlpacaMarketDataAPI extends EventEmitter {
6433
6741
  ws.send(JSON.stringify(unsubMessage));
6434
6742
  }
6435
6743
  }
6436
- async makeRequest(endpoint, method = 'GET', params, baseUrlName = 'data') {
6437
- const baseUrl = baseUrlName === 'data' ? this.dataURL : baseUrlName === 'api' ? this.apiURL : this.v1beta1url;
6744
+ async makeRequest(endpoint, method = "GET", params, baseUrlName = "data") {
6745
+ const baseUrl = baseUrlName === "data"
6746
+ ? this.dataURL
6747
+ : baseUrlName === "api"
6748
+ ? this.apiURL
6749
+ : this.v1beta1url;
6438
6750
  const url = new URL(`${baseUrl}${endpoint}`);
6439
6751
  try {
6440
6752
  if (params) {
6441
6753
  Object.entries(params).forEach(([key, value]) => {
6442
6754
  if (Array.isArray(value)) {
6443
- url.searchParams.append(key, value.join(','));
6755
+ url.searchParams.append(key, value.join(","));
6444
6756
  }
6445
- else if (value !== undefined) {
6757
+ else if (value !== undefined && value !== null) {
6446
6758
  url.searchParams.append(key, value.toString());
6447
6759
  }
6448
6760
  });
@@ -6450,10 +6762,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
6450
6762
  const response = await fetch(url.toString(), {
6451
6763
  method,
6452
6764
  headers: this.headers,
6765
+ signal: createTimeoutSignal(DEFAULT_TIMEOUTS.ALPACA_API),
6453
6766
  });
6454
6767
  if (!response.ok) {
6455
6768
  const errorText = await response.text();
6456
- log$1(`Market Data API error (${response.status}): ${errorText}`, { type: 'error' });
6769
+ log$1(`Market Data API error (${response.status}): ${errorText}`, {
6770
+ type: "error",
6771
+ });
6457
6772
  throw new Error(`Market Data API error (${response.status}): ${errorText}`);
6458
6773
  }
6459
6774
  const data = await response.json();
@@ -6461,9 +6776,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
6461
6776
  }
6462
6777
  catch (err) {
6463
6778
  const error = err;
6464
- log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: 'error' });
6779
+ log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: "error" });
6465
6780
  if (error instanceof TypeError) {
6466
- log$1(`Network error details: ${error.stack}`, { type: 'error' });
6781
+ log$1(`Network error details: ${error.stack}`, { type: "error" });
6467
6782
  }
6468
6783
  throw error;
6469
6784
  }
@@ -6476,19 +6791,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
6476
6791
  */
6477
6792
  async getHistoricalBars(params) {
6478
6793
  const symbols = params.symbols;
6479
- const symbolsStr = symbols.join(',');
6794
+ const symbolsStr = symbols.join(",");
6480
6795
  let allBars = {};
6481
6796
  let pageToken = null;
6482
6797
  let hasMorePages = true;
6483
6798
  let totalBarsCount = 0;
6484
6799
  let pageCount = 0;
6485
- let currency = '';
6800
+ let currency = "";
6486
6801
  // Initialize bar arrays for each symbol
6487
- symbols.forEach(symbol => {
6802
+ symbols.forEach((symbol) => {
6488
6803
  allBars[symbol] = [];
6489
6804
  });
6490
- log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
6491
- type: 'info'
6805
+ log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`, {
6806
+ type: "info",
6492
6807
  });
6493
6808
  while (hasMorePages) {
6494
6809
  pageCount++;
@@ -6498,9 +6813,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6498
6813
  feed: DEFAULT_FEED,
6499
6814
  ...(pageToken && { page_token: pageToken }),
6500
6815
  };
6501
- const response = await this.makeRequest('/stocks/bars', 'GET', requestParams);
6816
+ const response = await this.makeRequest("/stocks/bars", "GET", requestParams);
6502
6817
  if (!response.bars) {
6503
- log$1(`No bars data found in response for ${symbolsStr}`, { type: 'warn' });
6818
+ log$1(`No bars data found in response for ${symbolsStr}`, {
6819
+ type: "warn",
6820
+ });
6504
6821
  break;
6505
6822
  }
6506
6823
  // Track currency from first response
@@ -6516,7 +6833,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6516
6833
  allBars[symbol] = [...allBars[symbol], ...bars];
6517
6834
  pageBarsCount += bars.length;
6518
6835
  // Track date range for this page
6519
- bars.forEach(bar => {
6836
+ bars.forEach((bar) => {
6520
6837
  const barDate = new Date(bar.t);
6521
6838
  if (!earliestTimestamp || barDate < earliestTimestamp) {
6522
6839
  earliestTimestamp = barDate;
@@ -6532,21 +6849,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
6532
6849
  hasMorePages = !!pageToken;
6533
6850
  // Enhanced logging with date range and progress info
6534
6851
  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'
6852
+ ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
6853
+ : "unknown range";
6854
+ log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
6855
+ type: "info",
6539
6856
  });
6540
6857
  // Prevent infinite loops
6541
6858
  if (pageCount > 1000) {
6542
- log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
6859
+ log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
6543
6860
  break;
6544
6861
  }
6545
6862
  }
6546
6863
  // Final summary
6547
- const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
6864
+ const symbolCounts = Object.entries(allBars)
6865
+ .map(([symbol, bars]) => `${symbol}: ${bars.length}`)
6866
+ .join(", ");
6548
6867
  log$1(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
6549
- type: 'info'
6868
+ type: "info",
6550
6869
  });
6551
6870
  return {
6552
6871
  bars: allBars,
@@ -6562,7 +6881,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6562
6881
 
6563
6882
  */
6564
6883
  async getLatestBars(symbols, currency) {
6565
- return this.makeRequest('/stocks/bars/latest', 'GET', {
6884
+ return this.makeRequest("/stocks/bars/latest", "GET", {
6566
6885
  symbols,
6567
6886
  feed: DEFAULT_FEED,
6568
6887
  currency: currency || DEFAULT_CURRENCY,
@@ -6574,7 +6893,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6574
6893
  * @returns Last trade details including price, size, exchange, and conditions
6575
6894
  */
6576
6895
  async getLastTrade(symbol) {
6577
- return this.makeRequest(`/v1/last/stocks/${symbol}`, 'GET');
6896
+ return this.makeRequest(`/v1/last/stocks/${symbol}`, "GET");
6578
6897
  }
6579
6898
  /**
6580
6899
  * Get the most recent trades for requested symbols
@@ -6585,7 +6904,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6585
6904
 
6586
6905
  */
6587
6906
  async getLatestTrades(symbols, feed, currency) {
6588
- return this.makeRequest('/stocks/trades/latest', 'GET', {
6907
+ return this.makeRequest("/stocks/trades/latest", "GET", {
6589
6908
  symbols,
6590
6909
  feed: feed || DEFAULT_FEED,
6591
6910
  currency: currency || DEFAULT_CURRENCY,
@@ -6601,13 +6920,15 @@ class AlpacaMarketDataAPI extends EventEmitter {
6601
6920
  async getLatestQuotes(symbols, feed, currency) {
6602
6921
  // Return empty response if symbols array is empty to avoid API error
6603
6922
  if (!symbols || symbols.length === 0) {
6604
- log$1('No symbols provided to getLatestQuotes, returning empty response', { type: 'warn' });
6923
+ log$1("No symbols provided to getLatestQuotes, returning empty response", {
6924
+ type: "warn",
6925
+ });
6605
6926
  return {
6606
6927
  quotes: {},
6607
6928
  currency: currency || DEFAULT_CURRENCY,
6608
6929
  };
6609
6930
  }
6610
- return this.makeRequest('/stocks/quotes/latest', 'GET', {
6931
+ return this.makeRequest("/stocks/quotes/latest", "GET", {
6611
6932
  symbols,
6612
6933
  feed: feed || DEFAULT_FEED,
6613
6934
  currency: currency || DEFAULT_CURRENCY,
@@ -6621,7 +6942,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6621
6942
  * @returns Latest quote data with symbol and currency information
6622
6943
  */
6623
6944
  async getLatestQuote(symbol, feed, currency) {
6624
- return this.makeRequest(`/stocks/${symbol}/quotes/latest`, 'GET', {
6945
+ return this.makeRequest(`/stocks/${symbol}/quotes/latest`, "GET", {
6625
6946
  feed: feed || DEFAULT_FEED,
6626
6947
  currency,
6627
6948
  });
@@ -6637,13 +6958,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
6637
6958
  const prevMarketDate = getLastFullTradingDate(date);
6638
6959
  const response = await this.getHistoricalBars({
6639
6960
  symbols: [symbol],
6640
- timeframe: '1Day',
6961
+ timeframe: "1Day",
6641
6962
  start: prevMarketDate.date.toISOString(),
6642
6963
  end: prevMarketDate.date.toISOString(),
6643
6964
  limit: 1,
6644
6965
  });
6645
6966
  if (!response.bars[symbol] || response.bars[symbol].length === 0) {
6646
- log$1(`No previous close data available for ${symbol}`, { type: 'error', symbol });
6967
+ log$1(`No previous close data available for ${symbol}`, {
6968
+ type: "error",
6969
+ symbol,
6970
+ });
6647
6971
  return null;
6648
6972
  }
6649
6973
  return response.bars[symbol][0];
@@ -6658,7 +6982,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6658
6982
  async getHourlyPrices(symbol, start, end) {
6659
6983
  const response = await this.getHistoricalBars({
6660
6984
  symbols: [symbol],
6661
- timeframe: '1Hour',
6985
+ timeframe: "1Hour",
6662
6986
  start: new Date(start).toISOString(),
6663
6987
  end: new Date(end).toISOString(),
6664
6988
  limit: 96, // Last 96 hours (4 days)
@@ -6675,7 +6999,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6675
6999
  async getHalfHourlyPrices(symbol, start, end) {
6676
7000
  const response = await this.getHistoricalBars({
6677
7001
  symbols: [symbol],
6678
- timeframe: '30Min',
7002
+ timeframe: "30Min",
6679
7003
  start: new Date(start).toISOString(),
6680
7004
  end: new Date(end).toISOString(),
6681
7005
  limit: 16 * 2 * 4, // last 4 days, 16 hours per day, 2 bars per hour
@@ -6692,7 +7016,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6692
7016
  async getDailyPrices(symbol, start, end) {
6693
7017
  const response = await this.getHistoricalBars({
6694
7018
  symbols: [symbol],
6695
- timeframe: '1Day',
7019
+ timeframe: "1Day",
6696
7020
  start: new Date(start).toISOString(),
6697
7021
  end: new Date(end).toISOString(),
6698
7022
  limit: 100, // Last 100 days
@@ -6724,7 +7048,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6724
7048
  */
6725
7049
  static analyzeBars(bars) {
6726
7050
  if (!bars || bars.length === 0) {
6727
- return 'No price data available';
7051
+ return "No price data available";
6728
7052
  }
6729
7053
  const firstBar = bars[0];
6730
7054
  const lastBar = bars[bars.length - 1];
@@ -6749,7 +7073,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6749
7073
  */
6750
7074
  async getAssets(params) {
6751
7075
  // Endpoint: GET /v2/assets
6752
- return this.makeRequest('/assets', 'GET', params, 'api'); // use apiURL
7076
+ return this.makeRequest("/assets", "GET", params, "api"); // use apiURL
6753
7077
  }
6754
7078
  /**
6755
7079
  * Get a single asset by symbol or asset_id
@@ -6759,7 +7083,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6759
7083
  */
6760
7084
  async getAsset(symbolOrAssetId) {
6761
7085
  // Endpoint: GET /v2/assets/{symbol_or_asset_id}
6762
- return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, 'GET', undefined, 'api');
7086
+ return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, "GET", undefined, "api");
6763
7087
  }
6764
7088
  // ===== OPTIONS MARKET DATA METHODS =====
6765
7089
  /**
@@ -6771,7 +7095,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6771
7095
  */
6772
7096
  async getOptionsChain(params) {
6773
7097
  const { underlying_symbol, ...queryParams } = params;
6774
- return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, 'GET', queryParams, 'v1beta1');
7098
+ return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, "GET", queryParams, "v1beta1");
6775
7099
  }
6776
7100
  /**
6777
7101
  * Get the most recent trades for requested option contract symbols
@@ -6783,7 +7107,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6783
7107
  async getLatestOptionsTrades(params) {
6784
7108
  // Remove limit and page_token as they're not supported by this endpoint
6785
7109
  const { limit, page_token, ...requestParams } = params;
6786
- return this.makeRequest('/options/trades/latest', 'GET', requestParams, 'v1beta1');
7110
+ return this.makeRequest("/options/trades/latest", "GET", requestParams, "v1beta1");
6787
7111
  }
6788
7112
  /**
6789
7113
  * Get the most recent quotes for requested option contract symbols
@@ -6795,7 +7119,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6795
7119
  async getLatestOptionsQuotes(params) {
6796
7120
  // Remove limit and page_token as they're not supported by this endpoint
6797
7121
  const { limit, page_token, ...requestParams } = params;
6798
- return this.makeRequest('/options/quotes/latest', 'GET', requestParams, 'v1beta1');
7122
+ return this.makeRequest("/options/quotes/latest", "GET", requestParams, "v1beta1");
6799
7123
  }
6800
7124
  /**
6801
7125
  * Get historical OHLCV bars for option contract symbols
@@ -6807,18 +7131,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6807
7131
  */
6808
7132
  async getHistoricalOptionsBars(params) {
6809
7133
  const symbols = params.symbols;
6810
- const symbolsStr = symbols.join(',');
7134
+ const symbolsStr = symbols.join(",");
6811
7135
  let allBars = {};
6812
7136
  let pageToken = null;
6813
7137
  let hasMorePages = true;
6814
7138
  let totalBarsCount = 0;
6815
7139
  let pageCount = 0;
6816
7140
  // Initialize bar arrays for each symbol
6817
- symbols.forEach(symbol => {
7141
+ symbols.forEach((symbol) => {
6818
7142
  allBars[symbol] = [];
6819
7143
  });
6820
- log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
6821
- type: 'info'
7144
+ log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`, {
7145
+ type: "info",
6822
7146
  });
6823
7147
  while (hasMorePages) {
6824
7148
  pageCount++;
@@ -6826,9 +7150,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6826
7150
  ...params,
6827
7151
  ...(pageToken && { page_token: pageToken }),
6828
7152
  };
6829
- const response = await this.makeRequest('/options/bars', 'GET', requestParams, 'v1beta1');
7153
+ const response = await this.makeRequest("/options/bars", "GET", requestParams, "v1beta1");
6830
7154
  if (!response.bars) {
6831
- log$1(`No options bars data found in response for ${symbolsStr}`, { type: 'warn' });
7155
+ log$1(`No options bars data found in response for ${symbolsStr}`, {
7156
+ type: "warn",
7157
+ });
6832
7158
  break;
6833
7159
  }
6834
7160
  // Combine bars for each symbol
@@ -6840,7 +7166,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6840
7166
  allBars[symbol] = [...allBars[symbol], ...bars];
6841
7167
  pageBarsCount += bars.length;
6842
7168
  // Track date range for this page
6843
- bars.forEach(bar => {
7169
+ bars.forEach((bar) => {
6844
7170
  const barDate = new Date(bar.t);
6845
7171
  if (!earliestTimestamp || barDate < earliestTimestamp) {
6846
7172
  earliestTimestamp = barDate;
@@ -6856,21 +7182,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
6856
7182
  hasMorePages = !!pageToken;
6857
7183
  // Enhanced logging with date range and progress info
6858
7184
  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'
7185
+ ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
7186
+ : "unknown range";
7187
+ log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
7188
+ type: "info",
6863
7189
  });
6864
7190
  // Prevent infinite loops
6865
7191
  if (pageCount > 1000) {
6866
- log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
7192
+ log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
6867
7193
  break;
6868
7194
  }
6869
7195
  }
6870
7196
  // Final summary
6871
- const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
7197
+ const symbolCounts = Object.entries(allBars)
7198
+ .map(([symbol, bars]) => `${symbol}: ${bars.length}`)
7199
+ .join(", ");
6872
7200
  log$1(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
6873
- type: 'info'
7201
+ type: "info",
6874
7202
  });
6875
7203
  return {
6876
7204
  bars: allBars,
@@ -6887,18 +7215,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6887
7215
  */
6888
7216
  async getHistoricalOptionsTrades(params) {
6889
7217
  const symbols = params.symbols;
6890
- const symbolsStr = symbols.join(',');
7218
+ const symbolsStr = symbols.join(",");
6891
7219
  let allTrades = {};
6892
7220
  let pageToken = null;
6893
7221
  let hasMorePages = true;
6894
7222
  let totalTradesCount = 0;
6895
7223
  let pageCount = 0;
6896
7224
  // Initialize trades arrays for each symbol
6897
- symbols.forEach(symbol => {
7225
+ symbols.forEach((symbol) => {
6898
7226
  allTrades[symbol] = [];
6899
7227
  });
6900
- log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
6901
- type: 'info'
7228
+ log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || "no start"} to ${params.end || "no end"})`, {
7229
+ type: "info",
6902
7230
  });
6903
7231
  while (hasMorePages) {
6904
7232
  pageCount++;
@@ -6906,9 +7234,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6906
7234
  ...params,
6907
7235
  ...(pageToken && { page_token: pageToken }),
6908
7236
  };
6909
- const response = await this.makeRequest('/options/trades', 'GET', requestParams, 'v1beta1');
7237
+ const response = await this.makeRequest("/options/trades", "GET", requestParams, "v1beta1");
6910
7238
  if (!response.trades) {
6911
- log$1(`No options trades data found in response for ${symbolsStr}`, { type: 'warn' });
7239
+ log$1(`No options trades data found in response for ${symbolsStr}`, {
7240
+ type: "warn",
7241
+ });
6912
7242
  break;
6913
7243
  }
6914
7244
  // Combine trades for each symbol
@@ -6920,7 +7250,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6920
7250
  allTrades[symbol] = [...allTrades[symbol], ...trades];
6921
7251
  pageTradesCount += trades.length;
6922
7252
  // Track date range for this page
6923
- trades.forEach(trade => {
7253
+ trades.forEach((trade) => {
6924
7254
  const tradeDate = new Date(trade.t);
6925
7255
  if (!earliestTimestamp || tradeDate < earliestTimestamp) {
6926
7256
  earliestTimestamp = tradeDate;
@@ -6936,21 +7266,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
6936
7266
  hasMorePages = !!pageToken;
6937
7267
  // Enhanced logging with date range and progress info
6938
7268
  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'
7269
+ ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
7270
+ : "unknown range";
7271
+ log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
7272
+ type: "info",
6943
7273
  });
6944
7274
  // Prevent infinite loops
6945
7275
  if (pageCount > 1000) {
6946
- log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
7276
+ log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
6947
7277
  break;
6948
7278
  }
6949
7279
  }
6950
7280
  // Final summary
6951
- const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
7281
+ const symbolCounts = Object.entries(allTrades)
7282
+ .map(([symbol, trades]) => `${symbol}: ${trades.length}`)
7283
+ .join(", ");
6952
7284
  log$1(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
6953
- type: 'info'
7285
+ type: "info",
6954
7286
  });
6955
7287
  return {
6956
7288
  trades: allTrades,
@@ -6968,7 +7300,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6968
7300
  async getOptionsSnapshot(params) {
6969
7301
  // Remove limit and page_token as they may not be supported by this endpoint
6970
7302
  const { limit, page_token, ...requestParams } = params;
6971
- return this.makeRequest('/options/snapshots', 'GET', requestParams, 'v1beta1');
7303
+ return this.makeRequest("/options/snapshots", "GET", requestParams, "v1beta1");
6972
7304
  }
6973
7305
  /**
6974
7306
  * Get condition codes for options trades or quotes
@@ -6979,7 +7311,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6979
7311
  * @see https://docs.alpaca.markets/reference/optionmetaconditions
6980
7312
  */
6981
7313
  async getOptionsConditionCodes(tickType) {
6982
- return this.makeRequest(`/options/meta/conditions/${tickType}`, 'GET', undefined, 'v1beta1');
7314
+ return this.makeRequest(`/options/meta/conditions/${tickType}`, "GET", undefined, "v1beta1");
6983
7315
  }
6984
7316
  /**
6985
7317
  * Get exchange codes for options
@@ -6989,7 +7321,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6989
7321
  * @see https://docs.alpaca.markets/reference/optionmetaexchanges
6990
7322
  */
6991
7323
  async getOptionsExchangeCodes() {
6992
- return this.makeRequest('/options/meta/exchanges', 'GET', undefined, 'v1beta1');
7324
+ return this.makeRequest("/options/meta/exchanges", "GET", undefined, "v1beta1");
6993
7325
  }
6994
7326
  /**
6995
7327
  * Analyzes an array of option bars and returns a summary string
@@ -6998,7 +7330,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6998
7330
  */
6999
7331
  static analyzeOptionBars(bars) {
7000
7332
  if (!bars || bars.length === 0) {
7001
- return 'No option price data available';
7333
+ return "No option price data available";
7002
7334
  }
7003
7335
  const firstBar = bars[0];
7004
7336
  const lastBar = bars[bars.length - 1];
@@ -7022,7 +7354,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7022
7354
  */
7023
7355
  static formatOptionGreeks(greeks) {
7024
7356
  if (!greeks) {
7025
- return 'No greeks data available';
7357
+ return "No greeks data available";
7026
7358
  }
7027
7359
  const parts = [];
7028
7360
  if (greeks.delta !== undefined)
@@ -7035,7 +7367,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7035
7367
  parts.push(`Vega: ${greeks.vega.toFixed(4)}`);
7036
7368
  if (greeks.rho !== undefined)
7037
7369
  parts.push(`Rho: ${greeks.rho.toFixed(4)}`);
7038
- return parts.length > 0 ? parts.join(', ') : 'No greeks data available';
7370
+ return parts.length > 0 ? parts.join(", ") : "No greeks data available";
7039
7371
  }
7040
7372
  /**
7041
7373
  * Interprets condition codes using the provided condition codes mapping
@@ -7045,12 +7377,14 @@ class AlpacaMarketDataAPI extends EventEmitter {
7045
7377
  */
7046
7378
  static interpretConditionCodes(conditionCodes, conditionCodesMap) {
7047
7379
  if (!conditionCodes || conditionCodes.length === 0) {
7048
- return 'No conditions';
7380
+ return "No conditions";
7049
7381
  }
7050
7382
  const descriptions = conditionCodes
7051
7383
  .map((code) => conditionCodesMap[code] || `Unknown (${code})`)
7052
7384
  .filter((desc) => desc !== undefined);
7053
- return descriptions.length > 0 ? descriptions.join(', ') : 'No condition descriptions available';
7385
+ return descriptions.length > 0
7386
+ ? descriptions.join(", ")
7387
+ : "No condition descriptions available";
7054
7388
  }
7055
7389
  /**
7056
7390
  * Gets the exchange name from exchange code using the provided exchange codes mapping
@@ -7059,7 +7393,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7059
7393
  * @returns Exchange name or formatted unknown exchange
7060
7394
  */
7061
7395
  static getExchangeName(exchangeCode, exchangeCodesMap) {
7062
- return exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`;
7396
+ return (exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`);
7063
7397
  }
7064
7398
  /**
7065
7399
  * Fetches news articles from Alpaca API for a symbol, paginating through all results.
@@ -7072,7 +7406,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7072
7406
  start: new Date(Date.now() - 24 * 60 * 60 * 1000),
7073
7407
  end: new Date(),
7074
7408
  limit: 10,
7075
- sort: 'desc',
7409
+ sort: "desc",
7076
7410
  include_content: true,
7077
7411
  };
7078
7412
  const mergedParams = { ...defaultParams, ...params };
@@ -7086,38 +7420,52 @@ class AlpacaMarketDataAPI extends EventEmitter {
7086
7420
  if (!content)
7087
7421
  return undefined;
7088
7422
  // Remove excessive whitespace, newlines, and trim
7089
- return content.replace(/\s+/g, ' ').trim();
7423
+ return content.replace(/\s+/g, " ").trim();
7090
7424
  }
7091
7425
  while (hasMorePages) {
7092
7426
  const queryParams = new URLSearchParams({
7093
- ...(mergedParams.start && { start: new Date(mergedParams.start).toISOString() }),
7094
- ...(mergedParams.end && { end: new Date(mergedParams.end).toISOString() }),
7427
+ ...(mergedParams.start && {
7428
+ start: new Date(mergedParams.start).toISOString(),
7429
+ }),
7430
+ ...(mergedParams.end && {
7431
+ end: new Date(mergedParams.end).toISOString(),
7432
+ }),
7095
7433
  ...(symbol && { symbols: symbol }),
7096
- ...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
7434
+ ...(mergedParams.limit && {
7435
+ limit: Math.min(50, maxLimit - fetchedCount).toString(),
7436
+ }),
7097
7437
  ...(mergedParams.sort && { sort: mergedParams.sort }),
7098
- ...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
7438
+ ...(mergedParams.include_content !== undefined
7439
+ ? { include_content: mergedParams.include_content.toString() }
7440
+ : {}),
7099
7441
  ...(pageToken && { page_token: pageToken }),
7100
7442
  });
7101
7443
  const url = `${this.v1beta1url}/news?${queryParams}`;
7102
- log$1(`Fetching news from: ${url}`, { type: 'debug', symbol });
7444
+ log$1(`Fetching news from: ${url}`, { type: "debug", symbol });
7103
7445
  const response = await fetch(url, {
7104
- method: 'GET',
7446
+ method: "GET",
7105
7447
  headers: this.headers,
7106
7448
  });
7107
7449
  if (!response.ok) {
7108
7450
  const errorText = await response.text();
7109
- log$1(`Alpaca news API error (${response.status}): ${errorText}`, { type: 'error', symbol });
7451
+ log$1(`Alpaca news API error (${response.status}): ${errorText}`, {
7452
+ type: "error",
7453
+ symbol,
7454
+ });
7110
7455
  throw new Error(`Alpaca news API error (${response.status}): ${errorText}`);
7111
7456
  }
7112
7457
  const data = await response.json();
7113
7458
  if (!data.news || !Array.isArray(data.news)) {
7114
- log$1(`No news data found in Alpaca response for ${symbol}`, { type: 'warn', symbol });
7459
+ log$1(`No news data found in Alpaca response for ${symbol}`, {
7460
+ type: "warn",
7461
+ symbol,
7462
+ });
7115
7463
  break;
7116
7464
  }
7117
7465
  const transformedNews = data.news.map((article) => ({
7118
7466
  symbols: article.symbols,
7119
7467
  title: article.headline,
7120
- summary: cleanContent(article.summary) ?? '',
7468
+ summary: cleanContent(article.summary) ?? "",
7121
7469
  content: article.content ? cleanContent(article.content) : undefined,
7122
7470
  url: article.url,
7123
7471
  source: article.source,
@@ -7130,7 +7478,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
7130
7478
  fetchedCount = newsArticles.length;
7131
7479
  pageToken = data.next_page_token || null;
7132
7480
  hasMorePages = !!pageToken && (!maxLimit || fetchedCount < maxLimit);
7133
- log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: 'debug', symbol });
7481
+ log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: "debug", symbol });
7134
7482
  if (maxLimit && fetchedCount >= maxLimit) {
7135
7483
  newsArticles = newsArticles.slice(0, maxLimit);
7136
7484
  break;
@@ -7161,11 +7509,11 @@ const marketDataAPI = AlpacaMarketDataAPI.getInstance();
7161
7509
  */
7162
7510
  function formatCurrency(value) {
7163
7511
  if (isNaN(value)) {
7164
- return '$0.00';
7512
+ return "$0.00";
7165
7513
  }
7166
- return new Intl.NumberFormat('en-US', {
7167
- style: 'currency',
7168
- currency: 'USD',
7514
+ return new Intl.NumberFormat("en-US", {
7515
+ style: "currency",
7516
+ currency: "USD",
7169
7517
  }).format(value);
7170
7518
  }
7171
7519
  /**
@@ -7178,13 +7526,13 @@ function formatCurrency(value) {
7178
7526
  */
7179
7527
  function formatNumber(value) {
7180
7528
  if (isNaN(value)) {
7181
- return '0';
7529
+ return "0";
7182
7530
  }
7183
- return new Intl.NumberFormat('en-US').format(value);
7531
+ return new Intl.NumberFormat("en-US").format(value);
7184
7532
  }
7185
7533
 
7186
7534
  const log = (message) => {
7187
- console.log(`[${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })}] ${message}`);
7535
+ console.log(`[${new Date().toLocaleString("en-US", { timeZone: "America/New_York" })}] ${message}`);
7188
7536
  };
7189
7537
  // async function testCreateEquitiesTrade() {
7190
7538
  // try {
@@ -7408,18 +7756,18 @@ const log = (message) => {
7408
7756
  // testing retrieving pre-market data (just 9:00am to 9:30am on 1 july 2025 for SPY) using the market data api
7409
7757
  async function testPreMarketData() {
7410
7758
  try {
7411
- log('Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...');
7759
+ log("Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...");
7412
7760
  // Set up the time range in America/New_York, convert to UTC ISO strings
7413
- const symbol = 'SPY';
7414
- const nyTimeZone = 'America/New_York';
7761
+ const symbol = "SPY";
7762
+ const nyTimeZone = "America/New_York";
7415
7763
  // 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');
7764
+ const startNY = new Date("2025-07-01T09:00:00-04:00");
7765
+ const endNY = new Date("2025-07-01T09:30:00-04:00");
7418
7766
  const startUTC = startNY.toISOString();
7419
7767
  const endUTC = endNY.toISOString();
7420
7768
  const barsResponse = await marketDataAPI.getHistoricalBars({
7421
7769
  symbols: [symbol],
7422
- timeframe: '1Min',
7770
+ timeframe: "1Min",
7423
7771
  start: startUTC,
7424
7772
  end: endUTC,
7425
7773
  limit: 1000,
@@ -7427,19 +7775,21 @@ async function testPreMarketData() {
7427
7775
  const bars = barsResponse.bars[symbol] || [];
7428
7776
  log(`Fetched ${bars.length} 1-min bars for SPY from 9:00am to 9:30am (NY) on 2025-07-01.`);
7429
7777
  if (bars.length === 0) {
7430
- log('No pre-market bars returned.');
7778
+ log("No pre-market bars returned.");
7431
7779
  return;
7432
7780
  }
7433
7781
  // Print each bar
7434
7782
  bars.forEach((bar, i) => {
7435
- const barTime = new Date(bar.t).toLocaleString('en-US', { timeZone: nyTimeZone });
7783
+ const barTime = new Date(bar.t).toLocaleString("en-US", {
7784
+ timeZone: nyTimeZone,
7785
+ });
7436
7786
  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
7787
  });
7438
7788
  // Print summary
7439
7789
  const summary = AlpacaMarketDataAPI.analyzeBars(bars);
7440
7790
  if (summary)
7441
7791
  log(`Summary: ${summary}`);
7442
- log('Pre-market data test complete.');
7792
+ log("Pre-market data test complete.");
7443
7793
  }
7444
7794
  catch (error) {
7445
7795
  log(`❌ Error in testPreMarketData: ${error instanceof Error ? error.message : error}`);