@adaptic/utils 0.0.383 → 0.0.901
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.
- package/README.md +153 -61
- package/dist/index.cjs +60090 -5244
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +59829 -5244
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +733 -383
- package/dist/test.js.map +1 -1
- package/dist/types/__tests__/alpaca-functions.test.d.ts +2 -0
- package/dist/types/__tests__/alpaca-functions.test.d.ts.map +1 -0
- package/dist/types/__tests__/api-endpoints.test.d.ts +2 -0
- package/dist/types/__tests__/api-endpoints.test.d.ts.map +1 -0
- package/dist/types/__tests__/asset-allocation.test.d.ts +2 -0
- package/dist/types/__tests__/asset-allocation.test.d.ts.map +1 -0
- package/dist/types/__tests__/auth-validator.test.d.ts +2 -0
- package/dist/types/__tests__/auth-validator.test.d.ts.map +1 -0
- package/dist/types/__tests__/cache.test.d.ts +2 -0
- package/dist/types/__tests__/cache.test.d.ts.map +1 -0
- package/dist/types/__tests__/errors.test.d.ts +2 -0
- package/dist/types/__tests__/errors.test.d.ts.map +1 -0
- package/dist/types/__tests__/financial-regression.test.d.ts +2 -0
- package/dist/types/__tests__/financial-regression.test.d.ts.map +1 -0
- package/dist/types/__tests__/format-tools.test.d.ts +2 -0
- package/dist/types/__tests__/format-tools.test.d.ts.map +1 -0
- package/dist/types/__tests__/http-keep-alive.test.d.ts +2 -0
- package/dist/types/__tests__/http-keep-alive.test.d.ts.map +1 -0
- package/dist/types/__tests__/http-timeout.test.d.ts +2 -0
- package/dist/types/__tests__/http-timeout.test.d.ts.map +1 -0
- package/dist/types/__tests__/logger.test.d.ts +2 -0
- package/dist/types/__tests__/logger.test.d.ts.map +1 -0
- package/dist/types/__tests__/market-time.test.d.ts +2 -0
- package/dist/types/__tests__/market-time.test.d.ts.map +1 -0
- package/dist/types/__tests__/misc-utils.test.d.ts +2 -0
- package/dist/types/__tests__/misc-utils.test.d.ts.map +1 -0
- package/dist/types/__tests__/paginator.test.d.ts +2 -0
- package/dist/types/__tests__/paginator.test.d.ts.map +1 -0
- package/dist/types/__tests__/performance-metrics.test.d.ts +2 -0
- package/dist/types/__tests__/performance-metrics.test.d.ts.map +1 -0
- package/dist/types/__tests__/polygon.test.d.ts +2 -0
- package/dist/types/__tests__/polygon.test.d.ts.map +1 -0
- package/dist/types/__tests__/price-utils.test.d.ts +2 -0
- package/dist/types/__tests__/price-utils.test.d.ts.map +1 -0
- package/dist/types/__tests__/property-based-financial.test.d.ts +2 -0
- package/dist/types/__tests__/property-based-financial.test.d.ts.map +1 -0
- package/dist/types/__tests__/rate-limiter.test.d.ts +2 -0
- package/dist/types/__tests__/rate-limiter.test.d.ts.map +1 -0
- package/dist/types/__tests__/schema-validation.test.d.ts +2 -0
- package/dist/types/__tests__/schema-validation.test.d.ts.map +1 -0
- package/dist/types/__tests__/technical-analysis.test.d.ts +2 -0
- package/dist/types/__tests__/technical-analysis.test.d.ts.map +1 -0
- package/dist/types/__tests__/time-utils.test.d.ts +2 -0
- package/dist/types/__tests__/time-utils.test.d.ts.map +1 -0
- package/dist/types/adaptic.d.ts +2 -2
- package/dist/types/adaptic.d.ts.map +1 -1
- package/dist/types/alpaca/client.d.ts +95 -0
- package/dist/types/alpaca/client.d.ts.map +1 -0
- package/dist/types/alpaca/crypto/data.d.ts +281 -0
- package/dist/types/alpaca/crypto/data.d.ts.map +1 -0
- package/dist/types/alpaca/crypto/index.d.ts +75 -0
- package/dist/types/alpaca/crypto/index.d.ts.map +1 -0
- package/dist/types/alpaca/crypto/orders.d.ts +221 -0
- package/dist/types/alpaca/crypto/orders.d.ts.map +1 -0
- package/dist/types/alpaca/index.d.ts +205 -0
- package/dist/types/alpaca/index.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/account.d.ts +34 -0
- package/dist/types/alpaca/legacy/account.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/assets.d.ts +13 -0
- package/dist/types/alpaca/legacy/assets.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/auth.d.ts +18 -0
- package/dist/types/alpaca/legacy/auth.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/index.d.ts +15 -0
- package/dist/types/alpaca/legacy/index.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/market-data.d.ts +32 -0
- package/dist/types/alpaca/legacy/market-data.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/orders.d.ts +84 -0
- package/dist/types/alpaca/legacy/orders.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/positions.d.ts +66 -0
- package/dist/types/alpaca/legacy/positions.d.ts.map +1 -0
- package/dist/types/alpaca/legacy/utils.d.ts +18 -0
- package/dist/types/alpaca/legacy/utils.d.ts.map +1 -0
- package/dist/types/alpaca/market-data/bars.d.ts +142 -0
- package/dist/types/alpaca/market-data/bars.d.ts.map +1 -0
- package/dist/types/alpaca/market-data/index.d.ts +13 -0
- package/dist/types/alpaca/market-data/index.d.ts.map +1 -0
- package/dist/types/alpaca/market-data/news.d.ts +87 -0
- package/dist/types/alpaca/market-data/news.d.ts.map +1 -0
- package/dist/types/alpaca/market-data/quotes.d.ts +85 -0
- package/dist/types/alpaca/market-data/quotes.d.ts.map +1 -0
- package/dist/types/alpaca/market-data/trades.d.ts +98 -0
- package/dist/types/alpaca/market-data/trades.d.ts.map +1 -0
- package/dist/types/alpaca/options/contracts.d.ts +279 -0
- package/dist/types/alpaca/options/contracts.d.ts.map +1 -0
- package/dist/types/alpaca/options/data.d.ts +126 -0
- package/dist/types/alpaca/options/data.d.ts.map +1 -0
- package/dist/types/alpaca/options/index.d.ts +17 -0
- package/dist/types/alpaca/options/index.d.ts.map +1 -0
- package/dist/types/alpaca/options/orders.d.ts +366 -0
- package/dist/types/alpaca/options/orders.d.ts.map +1 -0
- package/dist/types/alpaca/options/strategies.d.ts +224 -0
- package/dist/types/alpaca/options/strategies.d.ts.map +1 -0
- package/dist/types/alpaca/streams/base-stream.d.ts +143 -0
- package/dist/types/alpaca/streams/base-stream.d.ts.map +1 -0
- package/dist/types/alpaca/streams/crypto-stream.d.ts +173 -0
- package/dist/types/alpaca/streams/crypto-stream.d.ts.map +1 -0
- package/dist/types/alpaca/streams/index.d.ts +54 -0
- package/dist/types/alpaca/streams/index.d.ts.map +1 -0
- package/dist/types/alpaca/streams/option-stream.d.ts +167 -0
- package/dist/types/alpaca/streams/option-stream.d.ts.map +1 -0
- package/dist/types/alpaca/streams/stock-stream.d.ts +176 -0
- package/dist/types/alpaca/streams/stock-stream.d.ts.map +1 -0
- package/dist/types/alpaca/streams/stream-manager.d.ts +277 -0
- package/dist/types/alpaca/streams/stream-manager.d.ts.map +1 -0
- package/dist/types/alpaca/streams/trading-stream.d.ts +186 -0
- package/dist/types/alpaca/streams/trading-stream.d.ts.map +1 -0
- package/dist/types/alpaca/streams.d.ts +88 -0
- package/dist/types/alpaca/streams.d.ts.map +1 -0
- package/dist/types/alpaca/test-imports.d.ts +7 -0
- package/dist/types/alpaca/test-imports.d.ts.map +1 -0
- package/dist/types/alpaca/trading/account.d.ts +198 -0
- package/dist/types/alpaca/trading/account.d.ts.map +1 -0
- package/dist/types/alpaca/trading/bracket-orders.d.ts +162 -0
- package/dist/types/alpaca/trading/bracket-orders.d.ts.map +1 -0
- package/dist/types/alpaca/trading/clock.d.ts +99 -0
- package/dist/types/alpaca/trading/clock.d.ts.map +1 -0
- package/dist/types/alpaca/trading/index.d.ts +15 -0
- package/dist/types/alpaca/trading/index.d.ts.map +1 -0
- package/dist/types/alpaca/trading/oco-orders.d.ts +203 -0
- package/dist/types/alpaca/trading/oco-orders.d.ts.map +1 -0
- package/dist/types/alpaca/trading/order-utils.d.ts +404 -0
- package/dist/types/alpaca/trading/order-utils.d.ts.map +1 -0
- package/dist/types/alpaca/trading/orders.d.ts +199 -0
- package/dist/types/alpaca/trading/orders.d.ts.map +1 -0
- package/dist/types/alpaca/trading/oto-orders.d.ts +282 -0
- package/dist/types/alpaca/trading/oto-orders.d.ts.map +1 -0
- package/dist/types/alpaca/trading/positions.d.ts +389 -0
- package/dist/types/alpaca/trading/positions.d.ts.map +1 -0
- package/dist/types/alpaca/trading/smart-orders.d.ts +301 -0
- package/dist/types/alpaca/trading/smart-orders.d.ts.map +1 -0
- package/dist/types/alpaca/trading/trailing-stops.d.ts +240 -0
- package/dist/types/alpaca/trading/trailing-stops.d.ts.map +1 -0
- package/dist/types/alpaca-market-data-api.d.ts +12 -10
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/alpaca-trading-api.d.ts +12 -12
- package/dist/types/alpaca-trading-api.d.ts.map +1 -1
- package/dist/types/alphavantage.d.ts +1 -1
- package/dist/types/alphavantage.d.ts.map +1 -1
- package/dist/types/asset-allocation-algorithm.d.ts +7 -1
- package/dist/types/asset-allocation-algorithm.d.ts.map +1 -1
- package/dist/types/cache/stampede-protected-cache.d.ts.map +1 -1
- package/dist/types/config/api-endpoints.d.ts +94 -0
- package/dist/types/config/api-endpoints.d.ts.map +1 -0
- package/dist/types/crypto.d.ts +2 -2
- package/dist/types/crypto.d.ts.map +1 -1
- package/dist/types/display-manager.d.ts +1 -1
- package/dist/types/display-manager.d.ts.map +1 -1
- package/dist/types/errors/index.d.ts +130 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/examples/asset-allocation-example.d.ts +7 -6
- package/dist/types/examples/asset-allocation-example.d.ts.map +1 -1
- package/dist/types/examples/rate-limiter-example.d.ts +7 -0
- package/dist/types/examples/rate-limiter-example.d.ts.map +1 -0
- package/dist/types/format-tools.d.ts.map +1 -1
- package/dist/types/http-timeout.d.ts +37 -0
- package/dist/types/http-timeout.d.ts.map +1 -0
- package/dist/types/index.d.ts +393 -55
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/logger.d.ts +56 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logging.d.ts +1 -1
- package/dist/types/logging.d.ts.map +1 -1
- package/dist/types/market-hours.d.ts.map +1 -1
- package/dist/types/market-time.d.ts +75 -13
- package/dist/types/market-time.d.ts.map +1 -1
- package/dist/types/metrics-calcs.d.ts.map +1 -1
- package/dist/types/misc-utils.d.ts +4 -1
- package/dist/types/misc-utils.d.ts.map +1 -1
- package/dist/types/performance-metrics.d.ts +4 -4
- package/dist/types/performance-metrics.d.ts.map +1 -1
- package/dist/types/polygon-indices.d.ts +3 -3
- package/dist/types/polygon-indices.d.ts.map +1 -1
- package/dist/types/polygon.d.ts +1 -1
- package/dist/types/polygon.d.ts.map +1 -1
- package/dist/types/price-utils.d.ts.map +1 -1
- package/dist/types/rate-limiter.d.ts +171 -0
- package/dist/types/rate-limiter.d.ts.map +1 -0
- package/dist/types/schemas/alpaca-schemas.d.ts +779 -0
- package/dist/types/schemas/alpaca-schemas.d.ts.map +1 -0
- package/dist/types/schemas/alphavantage-schemas.d.ts +255 -0
- package/dist/types/schemas/alphavantage-schemas.d.ts.map +1 -0
- package/dist/types/schemas/index.d.ts +21 -0
- package/dist/types/schemas/index.d.ts.map +1 -0
- package/dist/types/schemas/polygon-schemas.d.ts +551 -0
- package/dist/types/schemas/polygon-schemas.d.ts.map +1 -0
- package/dist/types/schemas/validate-response.d.ts +88 -0
- package/dist/types/schemas/validate-response.d.ts.map +1 -0
- package/dist/types/technical-analysis.d.ts +9 -9
- package/dist/types/technical-analysis.d.ts.map +1 -1
- package/dist/types/time-utils.d.ts.map +1 -1
- package/dist/types/types/adaptic-types.d.ts +1 -1
- package/dist/types/types/adaptic-types.d.ts.map +1 -1
- package/dist/types/types/alpaca-types.d.ts +172 -98
- package/dist/types/types/alpaca-types.d.ts.map +1 -1
- package/dist/types/types/alphavantage-types.d.ts +2 -2
- package/dist/types/types/asset-allocation-types.d.ts +11 -11
- package/dist/types/types/index.d.ts +8 -8
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/logging-types.d.ts +2 -2
- package/dist/types/types/logging-types.d.ts.map +1 -1
- package/dist/types/types/market-time-types.d.ts +4 -4
- package/dist/types/types/market-time-types.d.ts.map +1 -1
- package/dist/types/types/metrics-types.d.ts +3 -3
- package/dist/types/types/metrics-types.d.ts.map +1 -1
- package/dist/types/types/polygon-indices-types.d.ts +6 -6
- package/dist/types/types/polygon-types.d.ts +3 -3
- package/dist/types/types/ta-types.d.ts +3 -3
- package/dist/types/utils/auth-validator.d.ts +32 -0
- package/dist/types/utils/auth-validator.d.ts.map +1 -0
- package/dist/types/utils/http-keep-alive.d.ts +110 -0
- package/dist/types/utils/http-keep-alive.d.ts.map +1 -0
- package/dist/types/utils/paginator.d.ts +154 -0
- package/dist/types/utils/paginator.d.ts.map +1 -0
- package/dist/types/utils/retry.d.ts +78 -0
- package/dist/types/utils/retry.d.ts.map +1 -0
- package/package.json +25 -5
- package/dist/types/alpaca-functions.d.ts +0 -233
- 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(
|
|
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}] ` :
|
|
522
|
+
let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ""}${account ? ` [${account}] ` : ""}${symbol ? ` [${symbol}] ` : ""}${message}`;
|
|
521
523
|
// Add color based on type
|
|
522
|
-
if (options?.type ===
|
|
524
|
+
if (options?.type === "error") {
|
|
523
525
|
logMessage = chalk.red(logMessage);
|
|
524
526
|
}
|
|
525
|
-
else if (options?.type ===
|
|
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 +
|
|
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(
|
|
549
|
-
fs.mkdirSync(
|
|
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,
|
|
554
|
-
const day = String(date.getDate()).padStart(2,
|
|
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(
|
|
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 +
|
|
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(
|
|
575
|
-
fs.mkdirSync(
|
|
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,
|
|
580
|
-
const day = String(date.getDate()).padStart(2,
|
|
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,
|
|
584
|
+
const source = options?.source?.toLowerCase().replace(/\s+/g, "-") || "system";
|
|
583
585
|
const filename = `${source}-${year}-${month}-${day}.log`;
|
|
584
|
-
const filePath = path.join(
|
|
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 +
|
|
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:
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
664
|
-
date:
|
|
665
|
-
time:
|
|
666
|
-
optionsTime:
|
|
667
|
-
notes:
|
|
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
|
-
|
|
670
|
-
date:
|
|
671
|
-
time:
|
|
672
|
-
optionsTime:
|
|
673
|
-
notes:
|
|
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
|
-
|
|
684
|
-
date:
|
|
685
|
-
time:
|
|
686
|
-
optionsTime:
|
|
687
|
-
notes:
|
|
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
|
-
|
|
690
|
-
date:
|
|
691
|
-
time:
|
|
692
|
-
optionsTime:
|
|
693
|
-
notes:
|
|
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
|
-
|
|
704
|
-
date:
|
|
705
|
-
time:
|
|
706
|
-
optionsTime:
|
|
707
|
-
notes:
|
|
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
|
-
|
|
710
|
-
date:
|
|
711
|
-
time:
|
|
712
|
-
optionsTime:
|
|
713
|
-
notes:
|
|
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
|
-
|
|
716
|
-
date:
|
|
717
|
-
time:
|
|
718
|
-
optionsTime:
|
|
719
|
-
notes:
|
|
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:
|
|
735
|
-
REGULAR: {
|
|
736
|
-
|
|
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 =
|
|
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 =
|
|
870
|
+
formatDate(date, outputFormat = "iso") {
|
|
769
871
|
switch (outputFormat) {
|
|
770
|
-
case
|
|
872
|
+
case "unix-seconds":
|
|
771
873
|
return Math.floor(date.getTime() / 1000);
|
|
772
|
-
case
|
|
874
|
+
case "unix-ms":
|
|
773
875
|
return date.getTime();
|
|
774
|
-
case
|
|
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
|
-
|
|
781
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
*
|
|
801
|
-
*
|
|
802
|
-
* @
|
|
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
|
-
|
|
805
|
-
const formattedDate = format(
|
|
806
|
-
const yearEarlyCloses = marketEarlyCloses[
|
|
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
|
|
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
|
-
*
|
|
815
|
-
*
|
|
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
|
|
820
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
831
|
-
|
|
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 =
|
|
996
|
+
const timeInMinutes = nyDate.getHours() * 60 + nyDate.getMinutes();
|
|
835
997
|
// Check for early closure
|
|
836
|
-
if (this.
|
|
837
|
-
const earlyCloseMinutes = this.
|
|
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
|
|
846
|
-
const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
847
|
-
|
|
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
|
|
850
|
-
|
|
851
|
-
|
|
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
|
|
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 +
|
|
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 =
|
|
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
|
|
869
|
-
*
|
|
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
|
-
|
|
873
|
-
const timeInMinutes =
|
|
874
|
-
const startMinutes = this.intradayReporting ===
|
|
875
|
-
? MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
876
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
|
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
|
|
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.
|
|
978
|
-
const earlyCloseMinutes = this.
|
|
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
|
|
1194
|
+
case "YTD":
|
|
1006
1195
|
startDate = set(endDate, { month: 0, date: 1 });
|
|
1007
1196
|
break;
|
|
1008
|
-
case
|
|
1197
|
+
case "1D":
|
|
1009
1198
|
startDate = this.getLastMarketDay(endDate);
|
|
1010
1199
|
break;
|
|
1011
|
-
case
|
|
1200
|
+
case "3D":
|
|
1012
1201
|
startDate = sub(endDate, { days: 3 });
|
|
1013
1202
|
break;
|
|
1014
|
-
case
|
|
1203
|
+
case "1W":
|
|
1015
1204
|
startDate = sub(endDate, { weeks: 1 });
|
|
1016
1205
|
break;
|
|
1017
|
-
case
|
|
1206
|
+
case "2W":
|
|
1018
1207
|
startDate = sub(endDate, { weeks: 2 });
|
|
1019
1208
|
break;
|
|
1020
|
-
case
|
|
1209
|
+
case "1M":
|
|
1021
1210
|
startDate = sub(endDate, { months: 1 });
|
|
1022
1211
|
break;
|
|
1023
|
-
case
|
|
1212
|
+
case "3M":
|
|
1024
1213
|
startDate = sub(endDate, { months: 3 });
|
|
1025
1214
|
break;
|
|
1026
|
-
case
|
|
1215
|
+
case "6M":
|
|
1027
1216
|
startDate = sub(endDate, { months: 6 });
|
|
1028
1217
|
break;
|
|
1029
|
-
case
|
|
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.
|
|
1036
|
-
startDate = this.
|
|
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 =
|
|
1229
|
+
getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat = "iso", }) {
|
|
1041
1230
|
if (!period) {
|
|
1042
|
-
throw new Error(
|
|
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.
|
|
1052
|
-
const isWithinHours = this.
|
|
1053
|
-
const isBeforeHours = this.
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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, {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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,
|
|
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
|
-
|
|
6132
|
-
|
|
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 =
|
|
6136
|
-
const DEFAULT_FEED =
|
|
6137
|
-
const DEFAULT_CURRENCY =
|
|
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
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
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 = {
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
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 ===
|
|
6164
|
-
|
|
6165
|
-
this.
|
|
6166
|
-
this.
|
|
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 {
|
|
6169
|
-
|
|
6170
|
-
this.
|
|
6171
|
-
this.
|
|
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(
|
|
6176
|
-
return
|
|
6462
|
+
if (this.stockStreamUrl.includes("sandbox")) {
|
|
6463
|
+
return "sandbox";
|
|
6177
6464
|
}
|
|
6178
|
-
else if (this.stockStreamUrl.includes(
|
|
6179
|
-
return
|
|
6465
|
+
else if (this.stockStreamUrl.includes("test")) {
|
|
6466
|
+
return "test";
|
|
6180
6467
|
}
|
|
6181
6468
|
else {
|
|
6182
|
-
return
|
|
6469
|
+
return "production";
|
|
6183
6470
|
}
|
|
6184
6471
|
}
|
|
6185
6472
|
constructor() {
|
|
6186
6473
|
super();
|
|
6187
|
-
|
|
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 ===
|
|
6190
|
-
?
|
|
6191
|
-
:
|
|
6192
|
-
this.v1beta1url =
|
|
6193
|
-
this.setMode(
|
|
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
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
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 ===
|
|
6512
|
+
if (streamType === "stock") {
|
|
6215
6513
|
url = this.stockStreamUrl;
|
|
6216
6514
|
}
|
|
6217
|
-
else if (streamType ===
|
|
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 ===
|
|
6522
|
+
if (streamType === "stock") {
|
|
6225
6523
|
this.stockWs = ws;
|
|
6226
6524
|
}
|
|
6227
|
-
else if (streamType ===
|
|
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(
|
|
6234
|
-
log$1(`${streamType} stream connected`, { type:
|
|
6531
|
+
ws.on("open", () => {
|
|
6532
|
+
log$1(`${streamType} stream connected`, { type: "info" });
|
|
6235
6533
|
const authMessage = {
|
|
6236
|
-
action:
|
|
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(
|
|
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:
|
|
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 ===
|
|
6254
|
-
log$1(`${streamType} stream authenticated`, { type:
|
|
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 ===
|
|
6258
|
-
log$1(`${streamType} stream connected message received`, {
|
|
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 ===
|
|
6261
|
-
log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type:
|
|
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 ===
|
|
6264
|
-
log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type:
|
|
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:
|
|
6571
|
+
log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: "debug" });
|
|
6272
6572
|
}
|
|
6273
6573
|
}
|
|
6274
6574
|
});
|
|
6275
|
-
ws.on(
|
|
6276
|
-
log$1(`${streamType} stream disconnected`, { type:
|
|
6277
|
-
if (streamType ===
|
|
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 ===
|
|
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(
|
|
6289
|
-
log$1(`${streamType} stream error: ${error.message}`, { type:
|
|
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 ===
|
|
6595
|
+
if (streamType === "stock") {
|
|
6296
6596
|
ws = this.stockWs;
|
|
6297
6597
|
subscriptions = this.stockSubscriptions;
|
|
6298
6598
|
}
|
|
6299
|
-
else if (streamType ===
|
|
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:
|
|
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:
|
|
6623
|
+
action: "subscribe",
|
|
6324
6624
|
...subMessagePayload,
|
|
6325
6625
|
};
|
|
6326
6626
|
const messageJson = JSON.stringify(subMessage);
|
|
6327
|
-
log$1(`Sending ${streamType} subscription: ${messageJson}`, {
|
|
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)`, {
|
|
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`, {
|
|
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(
|
|
6646
|
+
this.connect("stock");
|
|
6341
6647
|
}
|
|
6342
6648
|
}
|
|
6343
6649
|
connectOptionStream() {
|
|
6344
6650
|
if (!this.optionWs) {
|
|
6345
|
-
this.connect(
|
|
6651
|
+
this.connect("option");
|
|
6346
6652
|
}
|
|
6347
6653
|
}
|
|
6348
6654
|
connectCryptoStream() {
|
|
6349
6655
|
if (!this.cryptoWs) {
|
|
6350
|
-
this.connect(
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
6692
|
+
if (streamType === "stock") {
|
|
6387
6693
|
currentSubscriptions = this.stockSubscriptions;
|
|
6388
6694
|
}
|
|
6389
|
-
else if (streamType ===
|
|
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] = [
|
|
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 ===
|
|
6712
|
+
if (streamType === "stock") {
|
|
6405
6713
|
currentSubscriptions = this.stockSubscriptions;
|
|
6406
6714
|
}
|
|
6407
|
-
else if (streamType ===
|
|
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:
|
|
6727
|
+
action: "unsubscribe",
|
|
6420
6728
|
...subscriptions,
|
|
6421
6729
|
};
|
|
6422
6730
|
let ws;
|
|
6423
|
-
if (streamType ===
|
|
6731
|
+
if (streamType === "stock") {
|
|
6424
6732
|
ws = this.stockWs;
|
|
6425
6733
|
}
|
|
6426
|
-
else if (streamType ===
|
|
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 =
|
|
6437
|
-
const baseUrl = baseUrlName ===
|
|
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}`, {
|
|
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:
|
|
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:
|
|
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 ||
|
|
6491
|
-
type:
|
|
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(
|
|
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}`, {
|
|
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(
|
|
6536
|
-
:
|
|
6537
|
-
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6538
|
-
type:
|
|
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:
|
|
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)
|
|
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:
|
|
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(
|
|
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}`,
|
|
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(
|
|
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(
|
|
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(
|
|
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`,
|
|
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:
|
|
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}`, {
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
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)}`,
|
|
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)}`,
|
|
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(
|
|
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(
|
|
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 ||
|
|
6821
|
-
type:
|
|
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(
|
|
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}`, {
|
|
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(
|
|
6860
|
-
:
|
|
6861
|
-
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6862
|
-
type:
|
|
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:
|
|
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)
|
|
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:
|
|
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 ||
|
|
6901
|
-
type:
|
|
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(
|
|
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}`, {
|
|
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(
|
|
6940
|
-
:
|
|
6941
|
-
log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6942
|
-
type:
|
|
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:
|
|
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)
|
|
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:
|
|
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(
|
|
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}`,
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
7423
|
+
return content.replace(/\s+/g, " ").trim();
|
|
7090
7424
|
}
|
|
7091
7425
|
while (hasMorePages) {
|
|
7092
7426
|
const queryParams = new URLSearchParams({
|
|
7093
|
-
...(mergedParams.start && {
|
|
7094
|
-
|
|
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 && {
|
|
7434
|
+
...(mergedParams.limit && {
|
|
7435
|
+
limit: Math.min(50, maxLimit - fetchedCount).toString(),
|
|
7436
|
+
}),
|
|
7097
7437
|
...(mergedParams.sort && { sort: mergedParams.sort }),
|
|
7098
|
-
...(mergedParams.include_content !== undefined
|
|
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:
|
|
7444
|
+
log$1(`Fetching news from: ${url}`, { type: "debug", symbol });
|
|
7103
7445
|
const response = await fetch(url, {
|
|
7104
|
-
method:
|
|
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}`, {
|
|
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}`, {
|
|
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:
|
|
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
|
|
7512
|
+
return "$0.00";
|
|
7165
7513
|
}
|
|
7166
|
-
return new Intl.NumberFormat(
|
|
7167
|
-
style:
|
|
7168
|
-
currency:
|
|
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
|
|
7529
|
+
return "0";
|
|
7182
7530
|
}
|
|
7183
|
-
return new Intl.NumberFormat(
|
|
7531
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
7184
7532
|
}
|
|
7185
7533
|
|
|
7186
7534
|
const log = (message) => {
|
|
7187
|
-
console.log(`[${new Date().toLocaleString(
|
|
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(
|
|
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 =
|
|
7414
|
-
const nyTimeZone =
|
|
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(
|
|
7417
|
-
const endNY = new Date(
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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}`);
|