@adaptic/utils 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -61
- package/dist/index.cjs +55882 -11299
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +55809 -11299
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +709 -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 +4 -4
- package/dist/types/alpaca/client.d.ts.map +1 -1
- package/dist/types/alpaca/crypto/data.d.ts +5 -5
- package/dist/types/alpaca/crypto/data.d.ts.map +1 -1
- package/dist/types/alpaca/crypto/index.d.ts +2 -2
- package/dist/types/alpaca/crypto/orders.d.ts +2 -2
- package/dist/types/alpaca/crypto/orders.d.ts.map +1 -1
- package/dist/types/alpaca/index.d.ts +36 -34
- package/dist/types/alpaca/index.d.ts.map +1 -1
- 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 +4 -4
- package/dist/types/alpaca/market-data/bars.d.ts.map +1 -1
- package/dist/types/alpaca/market-data/index.d.ts +8 -8
- package/dist/types/alpaca/market-data/news.d.ts +3 -3
- package/dist/types/alpaca/market-data/news.d.ts.map +1 -1
- package/dist/types/alpaca/market-data/quotes.d.ts +2 -2
- package/dist/types/alpaca/market-data/quotes.d.ts.map +1 -1
- package/dist/types/alpaca/market-data/trades.d.ts +2 -2
- package/dist/types/alpaca/market-data/trades.d.ts.map +1 -1
- package/dist/types/alpaca/options/contracts.d.ts +2 -2
- package/dist/types/alpaca/options/contracts.d.ts.map +1 -1
- package/dist/types/alpaca/options/data.d.ts +5 -5
- package/dist/types/alpaca/options/data.d.ts.map +1 -1
- package/dist/types/alpaca/options/index.d.ts +12 -12
- package/dist/types/alpaca/options/index.d.ts.map +1 -1
- package/dist/types/alpaca/options/orders.d.ts +4 -4
- package/dist/types/alpaca/options/orders.d.ts.map +1 -1
- package/dist/types/alpaca/options/strategies.d.ts +9 -9
- package/dist/types/alpaca/options/strategies.d.ts.map +1 -1
- package/dist/types/alpaca/streams/base-stream.d.ts +5 -5
- package/dist/types/alpaca/streams/base-stream.d.ts.map +1 -1
- package/dist/types/alpaca/streams/crypto-stream.d.ts +5 -5
- package/dist/types/alpaca/streams/crypto-stream.d.ts.map +1 -1
- package/dist/types/alpaca/streams/index.d.ts +6 -6
- package/dist/types/alpaca/streams/option-stream.d.ts +5 -5
- package/dist/types/alpaca/streams/option-stream.d.ts.map +1 -1
- package/dist/types/alpaca/streams/stock-stream.d.ts +5 -5
- package/dist/types/alpaca/streams/stock-stream.d.ts.map +1 -1
- package/dist/types/alpaca/streams/stream-manager.d.ts +16 -16
- package/dist/types/alpaca/streams/stream-manager.d.ts.map +1 -1
- package/dist/types/alpaca/streams/trading-stream.d.ts +28 -28
- package/dist/types/alpaca/streams/trading-stream.d.ts.map +1 -1
- package/dist/types/alpaca/streams.d.ts +2 -2
- package/dist/types/alpaca/streams.d.ts.map +1 -1
- package/dist/types/alpaca/trading/account.d.ts +2 -2
- package/dist/types/alpaca/trading/account.d.ts.map +1 -1
- package/dist/types/alpaca/trading/bracket-orders.d.ts +3 -3
- package/dist/types/alpaca/trading/bracket-orders.d.ts.map +1 -1
- package/dist/types/alpaca/trading/index.d.ts +12 -12
- package/dist/types/alpaca/trading/oco-orders.d.ts +2 -2
- package/dist/types/alpaca/trading/oco-orders.d.ts.map +1 -1
- package/dist/types/alpaca/trading/order-utils.d.ts +9 -9
- package/dist/types/alpaca/trading/order-utils.d.ts.map +1 -1
- package/dist/types/alpaca/trading/orders.d.ts +2 -2
- package/dist/types/alpaca/trading/orders.d.ts.map +1 -1
- package/dist/types/alpaca/trading/oto-orders.d.ts +3 -3
- package/dist/types/alpaca/trading/oto-orders.d.ts.map +1 -1
- package/dist/types/alpaca/trading/positions.d.ts +3 -3
- package/dist/types/alpaca/trading/positions.d.ts.map +1 -1
- package/dist/types/alpaca/trading/smart-orders.d.ts +12 -12
- package/dist/types/alpaca/trading/smart-orders.d.ts.map +1 -1
- package/dist/types/alpaca/trading/trailing-stops.d.ts +2 -2
- package/dist/types/alpaca/trading/trailing-stops.d.ts.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +10 -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 +59 -72
- 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 +27 -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 +22 -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,63 @@ 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
|
+
* @throws {Error} If credentials are invalid or missing
|
|
6340
|
+
*/
|
|
6341
|
+
function validateAlpacaCredentials(auth) {
|
|
6342
|
+
if (!auth.apiKey ||
|
|
6343
|
+
typeof auth.apiKey !== "string" ||
|
|
6344
|
+
auth.apiKey.trim().length === 0) {
|
|
6345
|
+
throw new Error("Invalid Alpaca API key: must be a non-empty string");
|
|
6346
|
+
}
|
|
6347
|
+
if (!auth.apiSecret ||
|
|
6348
|
+
typeof auth.apiSecret !== "string" ||
|
|
6349
|
+
auth.apiSecret.trim().length === 0) {
|
|
6350
|
+
throw new Error("Invalid Alpaca API secret: must be a non-empty string");
|
|
6351
|
+
}
|
|
6352
|
+
// Alpaca keys are typically 20+ characters
|
|
6353
|
+
if (auth.apiKey.length < 10) {
|
|
6354
|
+
throw new Error("Alpaca API key appears to be too short");
|
|
6355
|
+
}
|
|
6356
|
+
}
|
|
6357
|
+
|
|
6358
|
+
/**
|
|
6359
|
+
* HTTP request timeout utilities
|
|
6360
|
+
* Provides configurable timeout handling for external API calls
|
|
6361
|
+
*/
|
|
6362
|
+
/**
|
|
6363
|
+
* Default timeout values for different external APIs (in milliseconds)
|
|
6364
|
+
* Can be overridden via environment variables
|
|
6365
|
+
*/
|
|
6366
|
+
const DEFAULT_TIMEOUTS = {
|
|
6367
|
+
ALPACA_API: parseInt(process.env.ALPACA_API_TIMEOUT || "30000", 10),
|
|
6368
|
+
POLYGON_API: parseInt(process.env.POLYGON_API_TIMEOUT || "30000", 10),
|
|
6369
|
+
ALPHA_VANTAGE: parseInt(process.env.ALPHA_VANTAGE_API_TIMEOUT || "30000", 10),
|
|
6370
|
+
GENERAL: parseInt(process.env.HTTP_TIMEOUT || "30000", 10),
|
|
6371
|
+
};
|
|
6372
|
+
/**
|
|
6373
|
+
* Creates an AbortSignal that times out after the specified duration
|
|
6374
|
+
* Compatible with fetch API
|
|
6375
|
+
* @param ms - Timeout duration in milliseconds
|
|
6376
|
+
* @returns AbortSignal that will abort after the specified duration
|
|
6377
|
+
*/
|
|
6378
|
+
function createTimeoutSignal(ms) {
|
|
6379
|
+
return AbortSignal.timeout(ms);
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
const log$1 = (message, options = { type: "info" }) => {
|
|
6383
|
+
log$2(message, { ...options, source: "AlpacaMarketDataAPI" });
|
|
6133
6384
|
};
|
|
6134
6385
|
// Default settings for market data API
|
|
6135
|
-
const DEFAULT_ADJUSTMENT =
|
|
6136
|
-
const DEFAULT_FEED =
|
|
6137
|
-
const DEFAULT_CURRENCY =
|
|
6386
|
+
const DEFAULT_ADJUSTMENT = "all";
|
|
6387
|
+
const DEFAULT_FEED = "sip";
|
|
6388
|
+
const DEFAULT_CURRENCY = "USD";
|
|
6138
6389
|
/**
|
|
6139
6390
|
* Singleton class for interacting with Alpaca Market Data API
|
|
6140
6391
|
* Provides methods for fetching historical bars, latest bars, last trades, latest trades, latest quotes, and latest quote for a single symbol
|
|
@@ -6145,56 +6396,79 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6145
6396
|
dataURL;
|
|
6146
6397
|
apiURL;
|
|
6147
6398
|
v1beta1url;
|
|
6148
|
-
stockStreamUrl =
|
|
6149
|
-
optionStreamUrl =
|
|
6150
|
-
cryptoStreamUrl =
|
|
6399
|
+
stockStreamUrl = getStockStreamUrl("PRODUCTION"); // production values
|
|
6400
|
+
optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // production values
|
|
6401
|
+
cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // production values
|
|
6151
6402
|
stockWs = null;
|
|
6152
6403
|
optionWs = null;
|
|
6153
6404
|
cryptoWs = null;
|
|
6154
|
-
stockSubscriptions = {
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6405
|
+
stockSubscriptions = {
|
|
6406
|
+
trades: [],
|
|
6407
|
+
quotes: [],
|
|
6408
|
+
bars: [],
|
|
6409
|
+
};
|
|
6410
|
+
optionSubscriptions = {
|
|
6411
|
+
trades: [],
|
|
6412
|
+
quotes: [],
|
|
6413
|
+
bars: [],
|
|
6414
|
+
};
|
|
6415
|
+
cryptoSubscriptions = {
|
|
6416
|
+
trades: [],
|
|
6417
|
+
quotes: [],
|
|
6418
|
+
bars: [],
|
|
6419
|
+
};
|
|
6420
|
+
setMode(mode = "production") {
|
|
6421
|
+
if (mode === "sandbox") {
|
|
6422
|
+
// sandbox mode
|
|
6423
|
+
this.stockStreamUrl = WEBSOCKET_STREAMS.STOCKS.PRODUCTION; // sandbox uses production for stocks
|
|
6424
|
+
this.optionStreamUrl = getOptionsStreamUrl("SANDBOX");
|
|
6425
|
+
this.cryptoStreamUrl = getCryptoStreamUrl("SANDBOX");
|
|
6162
6426
|
}
|
|
6163
|
-
else if (mode ===
|
|
6164
|
-
|
|
6165
|
-
this.
|
|
6166
|
-
this.
|
|
6427
|
+
else if (mode === "test") {
|
|
6428
|
+
// test mode, can only use ticker FAKEPACA
|
|
6429
|
+
this.stockStreamUrl = getStockStreamUrl("TEST");
|
|
6430
|
+
this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // there's no test mode for options
|
|
6431
|
+
this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // there's no test mode for crypto
|
|
6167
6432
|
}
|
|
6168
|
-
else {
|
|
6169
|
-
|
|
6170
|
-
this.
|
|
6171
|
-
this.
|
|
6433
|
+
else {
|
|
6434
|
+
// production
|
|
6435
|
+
this.stockStreamUrl = getStockStreamUrl("PRODUCTION");
|
|
6436
|
+
this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION");
|
|
6437
|
+
this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION");
|
|
6172
6438
|
}
|
|
6173
6439
|
}
|
|
6174
6440
|
getMode() {
|
|
6175
|
-
if (this.stockStreamUrl.includes(
|
|
6176
|
-
return
|
|
6441
|
+
if (this.stockStreamUrl.includes("sandbox")) {
|
|
6442
|
+
return "sandbox";
|
|
6177
6443
|
}
|
|
6178
|
-
else if (this.stockStreamUrl.includes(
|
|
6179
|
-
return
|
|
6444
|
+
else if (this.stockStreamUrl.includes("test")) {
|
|
6445
|
+
return "test";
|
|
6180
6446
|
}
|
|
6181
6447
|
else {
|
|
6182
|
-
return
|
|
6448
|
+
return "production";
|
|
6183
6449
|
}
|
|
6184
6450
|
}
|
|
6185
6451
|
constructor() {
|
|
6186
6452
|
super();
|
|
6187
|
-
|
|
6453
|
+
// Validate credentials from environment variables before initializing
|
|
6454
|
+
const apiKey = process.env.ALPACA_API_KEY;
|
|
6455
|
+
const apiSecret = process.env.ALPACA_SECRET_KEY;
|
|
6456
|
+
validateAlpacaCredentials({
|
|
6457
|
+
apiKey,
|
|
6458
|
+
apiSecret,
|
|
6459
|
+
isPaper: process.env.ALPACA_ACCOUNT_TYPE === "PAPER",
|
|
6460
|
+
});
|
|
6461
|
+
this.dataURL = MARKET_DATA_API.STOCKS;
|
|
6188
6462
|
this.apiURL =
|
|
6189
|
-
process.env.ALPACA_ACCOUNT_TYPE ===
|
|
6190
|
-
?
|
|
6191
|
-
:
|
|
6192
|
-
this.v1beta1url =
|
|
6193
|
-
this.setMode(
|
|
6463
|
+
process.env.ALPACA_ACCOUNT_TYPE === "PAPER"
|
|
6464
|
+
? getTradingApiUrl("PAPER")
|
|
6465
|
+
: getTradingApiUrl("LIVE"); // used by some, e.g. getAssets
|
|
6466
|
+
this.v1beta1url = MARKET_DATA_API.OPTIONS; // used for options endpoints
|
|
6467
|
+
this.setMode("production"); // sets stockStreamUrl and optionStreamUrl
|
|
6194
6468
|
this.headers = {
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6469
|
+
"APCA-API-KEY-ID": apiKey,
|
|
6470
|
+
"APCA-API-SECRET-KEY": apiSecret,
|
|
6471
|
+
"Content-Type": "application/json",
|
|
6198
6472
|
};
|
|
6199
6473
|
}
|
|
6200
6474
|
static getInstance() {
|
|
@@ -6211,73 +6485,75 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6211
6485
|
}
|
|
6212
6486
|
connect(streamType) {
|
|
6213
6487
|
let url;
|
|
6214
|
-
if (streamType ===
|
|
6488
|
+
if (streamType === "stock") {
|
|
6215
6489
|
url = this.stockStreamUrl;
|
|
6216
6490
|
}
|
|
6217
|
-
else if (streamType ===
|
|
6491
|
+
else if (streamType === "option") {
|
|
6218
6492
|
url = this.optionStreamUrl;
|
|
6219
6493
|
}
|
|
6220
6494
|
else {
|
|
6221
6495
|
url = this.cryptoStreamUrl;
|
|
6222
6496
|
}
|
|
6223
6497
|
const ws = new WebSocket(url);
|
|
6224
|
-
if (streamType ===
|
|
6498
|
+
if (streamType === "stock") {
|
|
6225
6499
|
this.stockWs = ws;
|
|
6226
6500
|
}
|
|
6227
|
-
else if (streamType ===
|
|
6501
|
+
else if (streamType === "option") {
|
|
6228
6502
|
this.optionWs = ws;
|
|
6229
6503
|
}
|
|
6230
6504
|
else {
|
|
6231
6505
|
this.cryptoWs = ws;
|
|
6232
6506
|
}
|
|
6233
|
-
ws.on(
|
|
6234
|
-
log$1(`${streamType} stream connected`, { type:
|
|
6507
|
+
ws.on("open", () => {
|
|
6508
|
+
log$1(`${streamType} stream connected`, { type: "info" });
|
|
6235
6509
|
const authMessage = {
|
|
6236
|
-
action:
|
|
6510
|
+
action: "auth",
|
|
6237
6511
|
key: process.env.ALPACA_API_KEY,
|
|
6238
6512
|
secret: process.env.ALPACA_SECRET_KEY,
|
|
6239
6513
|
};
|
|
6240
6514
|
ws.send(JSON.stringify(authMessage));
|
|
6241
6515
|
});
|
|
6242
|
-
ws.on(
|
|
6516
|
+
ws.on("message", (data) => {
|
|
6243
6517
|
const rawData = data.toString();
|
|
6244
6518
|
let messages;
|
|
6245
6519
|
try {
|
|
6246
6520
|
messages = JSON.parse(rawData);
|
|
6247
6521
|
}
|
|
6248
6522
|
catch (e) {
|
|
6249
|
-
log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type:
|
|
6523
|
+
log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type: "error" });
|
|
6250
6524
|
return;
|
|
6251
6525
|
}
|
|
6252
6526
|
for (const message of messages) {
|
|
6253
|
-
if (message.T ===
|
|
6254
|
-
log$1(`${streamType} stream authenticated`, { type:
|
|
6527
|
+
if (message.T === "success" && message.msg === "authenticated") {
|
|
6528
|
+
log$1(`${streamType} stream authenticated`, { type: "info" });
|
|
6255
6529
|
this.sendSubscription(streamType);
|
|
6256
6530
|
}
|
|
6257
|
-
else if (message.T ===
|
|
6258
|
-
log$1(`${streamType} stream connected message received`, {
|
|
6531
|
+
else if (message.T === "success" && message.msg === "connected") {
|
|
6532
|
+
log$1(`${streamType} stream connected message received`, {
|
|
6533
|
+
type: "debug",
|
|
6534
|
+
});
|
|
6259
6535
|
}
|
|
6260
|
-
else if (message.T ===
|
|
6261
|
-
log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type:
|
|
6536
|
+
else if (message.T === "subscription") {
|
|
6537
|
+
log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type: "info" });
|
|
6262
6538
|
}
|
|
6263
|
-
else if (message.T ===
|
|
6264
|
-
log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type:
|
|
6539
|
+
else if (message.T === "error") {
|
|
6540
|
+
log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: "error" });
|
|
6265
6541
|
}
|
|
6266
6542
|
else if (message.S) {
|
|
6267
6543
|
super.emit(`${streamType}-${message.T}`, message);
|
|
6268
6544
|
super.emit(`${streamType}-data`, message);
|
|
6269
6545
|
}
|
|
6270
6546
|
else {
|
|
6271
|
-
log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type:
|
|
6547
|
+
log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: "debug" });
|
|
6272
6548
|
}
|
|
6273
6549
|
}
|
|
6274
6550
|
});
|
|
6275
|
-
ws.on(
|
|
6276
|
-
log$1(`${streamType} stream disconnected`, { type:
|
|
6277
|
-
if (streamType ===
|
|
6551
|
+
ws.on("close", () => {
|
|
6552
|
+
log$1(`${streamType} stream disconnected`, { type: "warn" });
|
|
6553
|
+
if (streamType === "stock") {
|
|
6278
6554
|
this.stockWs = null;
|
|
6279
6555
|
}
|
|
6280
|
-
else if (streamType ===
|
|
6556
|
+
else if (streamType === "option") {
|
|
6281
6557
|
this.optionWs = null;
|
|
6282
6558
|
}
|
|
6283
6559
|
else {
|
|
@@ -6285,18 +6561,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6285
6561
|
}
|
|
6286
6562
|
// Optional: implement reconnect logic
|
|
6287
6563
|
});
|
|
6288
|
-
ws.on(
|
|
6289
|
-
log$1(`${streamType} stream error: ${error.message}`, { type:
|
|
6564
|
+
ws.on("error", (error) => {
|
|
6565
|
+
log$1(`${streamType} stream error: ${error.message}`, { type: "error" });
|
|
6290
6566
|
});
|
|
6291
6567
|
}
|
|
6292
6568
|
sendSubscription(streamType) {
|
|
6293
6569
|
let ws;
|
|
6294
6570
|
let subscriptions;
|
|
6295
|
-
if (streamType ===
|
|
6571
|
+
if (streamType === "stock") {
|
|
6296
6572
|
ws = this.stockWs;
|
|
6297
6573
|
subscriptions = this.stockSubscriptions;
|
|
6298
6574
|
}
|
|
6299
|
-
else if (streamType ===
|
|
6575
|
+
else if (streamType === "option") {
|
|
6300
6576
|
ws = this.optionWs;
|
|
6301
6577
|
subscriptions = this.optionSubscriptions;
|
|
6302
6578
|
}
|
|
@@ -6305,7 +6581,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6305
6581
|
subscriptions = this.cryptoSubscriptions;
|
|
6306
6582
|
}
|
|
6307
6583
|
log$1(`sendSubscription called for ${streamType} (wsReady=${ws?.readyState === WebSocket.OPEN}, trades=${subscriptions.trades?.length || 0}, quotes=${subscriptions.quotes?.length || 0}, bars=${subscriptions.bars?.length || 0})`, {
|
|
6308
|
-
type:
|
|
6584
|
+
type: "debug",
|
|
6309
6585
|
});
|
|
6310
6586
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
6311
6587
|
const subMessagePayload = {};
|
|
@@ -6320,34 +6596,40 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6320
6596
|
}
|
|
6321
6597
|
if (Object.keys(subMessagePayload).length > 0) {
|
|
6322
6598
|
const subMessage = {
|
|
6323
|
-
action:
|
|
6599
|
+
action: "subscribe",
|
|
6324
6600
|
...subMessagePayload,
|
|
6325
6601
|
};
|
|
6326
6602
|
const messageJson = JSON.stringify(subMessage);
|
|
6327
|
-
log$1(`Sending ${streamType} subscription: ${messageJson}`, {
|
|
6603
|
+
log$1(`Sending ${streamType} subscription: ${messageJson}`, {
|
|
6604
|
+
type: "info",
|
|
6605
|
+
});
|
|
6328
6606
|
ws.send(messageJson);
|
|
6329
6607
|
}
|
|
6330
6608
|
else {
|
|
6331
|
-
log$1(`No ${streamType} subscriptions to send (all arrays empty)`, {
|
|
6609
|
+
log$1(`No ${streamType} subscriptions to send (all arrays empty)`, {
|
|
6610
|
+
type: "debug",
|
|
6611
|
+
});
|
|
6332
6612
|
}
|
|
6333
6613
|
}
|
|
6334
6614
|
else {
|
|
6335
|
-
log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, {
|
|
6615
|
+
log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, {
|
|
6616
|
+
type: "warn",
|
|
6617
|
+
});
|
|
6336
6618
|
}
|
|
6337
6619
|
}
|
|
6338
6620
|
connectStockStream() {
|
|
6339
6621
|
if (!this.stockWs) {
|
|
6340
|
-
this.connect(
|
|
6622
|
+
this.connect("stock");
|
|
6341
6623
|
}
|
|
6342
6624
|
}
|
|
6343
6625
|
connectOptionStream() {
|
|
6344
6626
|
if (!this.optionWs) {
|
|
6345
|
-
this.connect(
|
|
6627
|
+
this.connect("option");
|
|
6346
6628
|
}
|
|
6347
6629
|
}
|
|
6348
6630
|
connectCryptoStream() {
|
|
6349
6631
|
if (!this.cryptoWs) {
|
|
6350
|
-
this.connect(
|
|
6632
|
+
this.connect("crypto");
|
|
6351
6633
|
}
|
|
6352
6634
|
}
|
|
6353
6635
|
disconnectStockStream() {
|
|
@@ -6371,22 +6653,22 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6371
6653
|
* @returns True if the stream is connected
|
|
6372
6654
|
*/
|
|
6373
6655
|
isStreamConnected(streamType) {
|
|
6374
|
-
if (streamType ===
|
|
6375
|
-
return this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN;
|
|
6656
|
+
if (streamType === "stock") {
|
|
6657
|
+
return (this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN);
|
|
6376
6658
|
}
|
|
6377
|
-
else if (streamType ===
|
|
6378
|
-
return this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN;
|
|
6659
|
+
else if (streamType === "option") {
|
|
6660
|
+
return (this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN);
|
|
6379
6661
|
}
|
|
6380
6662
|
else {
|
|
6381
|
-
return this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN;
|
|
6663
|
+
return (this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN);
|
|
6382
6664
|
}
|
|
6383
6665
|
}
|
|
6384
6666
|
subscribe(streamType, subscriptions) {
|
|
6385
6667
|
let currentSubscriptions;
|
|
6386
|
-
if (streamType ===
|
|
6668
|
+
if (streamType === "stock") {
|
|
6387
6669
|
currentSubscriptions = this.stockSubscriptions;
|
|
6388
6670
|
}
|
|
6389
|
-
else if (streamType ===
|
|
6671
|
+
else if (streamType === "option") {
|
|
6390
6672
|
currentSubscriptions = this.optionSubscriptions;
|
|
6391
6673
|
}
|
|
6392
6674
|
else {
|
|
@@ -6394,17 +6676,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6394
6676
|
}
|
|
6395
6677
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6396
6678
|
if (value) {
|
|
6397
|
-
currentSubscriptions[key] = [
|
|
6679
|
+
currentSubscriptions[key] = [
|
|
6680
|
+
...new Set([...(currentSubscriptions[key] || []), ...value]),
|
|
6681
|
+
];
|
|
6398
6682
|
}
|
|
6399
6683
|
});
|
|
6400
6684
|
this.sendSubscription(streamType);
|
|
6401
6685
|
}
|
|
6402
6686
|
unsubscribe(streamType, subscriptions) {
|
|
6403
6687
|
let currentSubscriptions;
|
|
6404
|
-
if (streamType ===
|
|
6688
|
+
if (streamType === "stock") {
|
|
6405
6689
|
currentSubscriptions = this.stockSubscriptions;
|
|
6406
6690
|
}
|
|
6407
|
-
else if (streamType ===
|
|
6691
|
+
else if (streamType === "option") {
|
|
6408
6692
|
currentSubscriptions = this.optionSubscriptions;
|
|
6409
6693
|
}
|
|
6410
6694
|
else {
|
|
@@ -6412,18 +6696,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6412
6696
|
}
|
|
6413
6697
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6414
6698
|
if (value) {
|
|
6415
|
-
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
|
|
6699
|
+
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter((s) => !value.includes(s));
|
|
6416
6700
|
}
|
|
6417
6701
|
});
|
|
6418
6702
|
const unsubMessage = {
|
|
6419
|
-
action:
|
|
6703
|
+
action: "unsubscribe",
|
|
6420
6704
|
...subscriptions,
|
|
6421
6705
|
};
|
|
6422
6706
|
let ws;
|
|
6423
|
-
if (streamType ===
|
|
6707
|
+
if (streamType === "stock") {
|
|
6424
6708
|
ws = this.stockWs;
|
|
6425
6709
|
}
|
|
6426
|
-
else if (streamType ===
|
|
6710
|
+
else if (streamType === "option") {
|
|
6427
6711
|
ws = this.optionWs;
|
|
6428
6712
|
}
|
|
6429
6713
|
else {
|
|
@@ -6433,16 +6717,20 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6433
6717
|
ws.send(JSON.stringify(unsubMessage));
|
|
6434
6718
|
}
|
|
6435
6719
|
}
|
|
6436
|
-
async makeRequest(endpoint, method =
|
|
6437
|
-
const baseUrl = baseUrlName ===
|
|
6720
|
+
async makeRequest(endpoint, method = "GET", params, baseUrlName = "data") {
|
|
6721
|
+
const baseUrl = baseUrlName === "data"
|
|
6722
|
+
? this.dataURL
|
|
6723
|
+
: baseUrlName === "api"
|
|
6724
|
+
? this.apiURL
|
|
6725
|
+
: this.v1beta1url;
|
|
6438
6726
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
6439
6727
|
try {
|
|
6440
6728
|
if (params) {
|
|
6441
6729
|
Object.entries(params).forEach(([key, value]) => {
|
|
6442
6730
|
if (Array.isArray(value)) {
|
|
6443
|
-
url.searchParams.append(key, value.join(
|
|
6731
|
+
url.searchParams.append(key, value.join(","));
|
|
6444
6732
|
}
|
|
6445
|
-
else if (value !== undefined) {
|
|
6733
|
+
else if (value !== undefined && value !== null) {
|
|
6446
6734
|
url.searchParams.append(key, value.toString());
|
|
6447
6735
|
}
|
|
6448
6736
|
});
|
|
@@ -6450,10 +6738,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6450
6738
|
const response = await fetch(url.toString(), {
|
|
6451
6739
|
method,
|
|
6452
6740
|
headers: this.headers,
|
|
6741
|
+
signal: createTimeoutSignal(DEFAULT_TIMEOUTS.ALPACA_API),
|
|
6453
6742
|
});
|
|
6454
6743
|
if (!response.ok) {
|
|
6455
6744
|
const errorText = await response.text();
|
|
6456
|
-
log$1(`Market Data API error (${response.status}): ${errorText}`, {
|
|
6745
|
+
log$1(`Market Data API error (${response.status}): ${errorText}`, {
|
|
6746
|
+
type: "error",
|
|
6747
|
+
});
|
|
6457
6748
|
throw new Error(`Market Data API error (${response.status}): ${errorText}`);
|
|
6458
6749
|
}
|
|
6459
6750
|
const data = await response.json();
|
|
@@ -6461,9 +6752,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6461
6752
|
}
|
|
6462
6753
|
catch (err) {
|
|
6463
6754
|
const error = err;
|
|
6464
|
-
log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type:
|
|
6755
|
+
log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: "error" });
|
|
6465
6756
|
if (error instanceof TypeError) {
|
|
6466
|
-
log$1(`Network error details: ${error.stack}`, { type:
|
|
6757
|
+
log$1(`Network error details: ${error.stack}`, { type: "error" });
|
|
6467
6758
|
}
|
|
6468
6759
|
throw error;
|
|
6469
6760
|
}
|
|
@@ -6476,19 +6767,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6476
6767
|
*/
|
|
6477
6768
|
async getHistoricalBars(params) {
|
|
6478
6769
|
const symbols = params.symbols;
|
|
6479
|
-
const symbolsStr = symbols.join(
|
|
6770
|
+
const symbolsStr = symbols.join(",");
|
|
6480
6771
|
let allBars = {};
|
|
6481
6772
|
let pageToken = null;
|
|
6482
6773
|
let hasMorePages = true;
|
|
6483
6774
|
let totalBarsCount = 0;
|
|
6484
6775
|
let pageCount = 0;
|
|
6485
|
-
let currency =
|
|
6776
|
+
let currency = "";
|
|
6486
6777
|
// Initialize bar arrays for each symbol
|
|
6487
|
-
symbols.forEach(symbol => {
|
|
6778
|
+
symbols.forEach((symbol) => {
|
|
6488
6779
|
allBars[symbol] = [];
|
|
6489
6780
|
});
|
|
6490
|
-
log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start ||
|
|
6491
|
-
type:
|
|
6781
|
+
log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`, {
|
|
6782
|
+
type: "info",
|
|
6492
6783
|
});
|
|
6493
6784
|
while (hasMorePages) {
|
|
6494
6785
|
pageCount++;
|
|
@@ -6498,9 +6789,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6498
6789
|
feed: DEFAULT_FEED,
|
|
6499
6790
|
...(pageToken && { page_token: pageToken }),
|
|
6500
6791
|
};
|
|
6501
|
-
const response = await this.makeRequest(
|
|
6792
|
+
const response = await this.makeRequest("/stocks/bars", "GET", requestParams);
|
|
6502
6793
|
if (!response.bars) {
|
|
6503
|
-
log$1(`No bars data found in response for ${symbolsStr}`, {
|
|
6794
|
+
log$1(`No bars data found in response for ${symbolsStr}`, {
|
|
6795
|
+
type: "warn",
|
|
6796
|
+
});
|
|
6504
6797
|
break;
|
|
6505
6798
|
}
|
|
6506
6799
|
// Track currency from first response
|
|
@@ -6516,7 +6809,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6516
6809
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
6517
6810
|
pageBarsCount += bars.length;
|
|
6518
6811
|
// Track date range for this page
|
|
6519
|
-
bars.forEach(bar => {
|
|
6812
|
+
bars.forEach((bar) => {
|
|
6520
6813
|
const barDate = new Date(bar.t);
|
|
6521
6814
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
6522
6815
|
earliestTimestamp = barDate;
|
|
@@ -6532,21 +6825,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6532
6825
|
hasMorePages = !!pageToken;
|
|
6533
6826
|
// Enhanced logging with date range and progress info
|
|
6534
6827
|
const dateRangeStr = earliestTimestamp && latestTimestamp
|
|
6535
|
-
? `${earliestTimestamp.toLocaleDateString(
|
|
6536
|
-
:
|
|
6537
|
-
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6538
|
-
type:
|
|
6828
|
+
? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
|
|
6829
|
+
: "unknown range";
|
|
6830
|
+
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
|
|
6831
|
+
type: "info",
|
|
6539
6832
|
});
|
|
6540
6833
|
// Prevent infinite loops
|
|
6541
6834
|
if (pageCount > 1000) {
|
|
6542
|
-
log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6835
|
+
log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
|
|
6543
6836
|
break;
|
|
6544
6837
|
}
|
|
6545
6838
|
}
|
|
6546
6839
|
// Final summary
|
|
6547
|
-
const symbolCounts = Object.entries(allBars)
|
|
6840
|
+
const symbolCounts = Object.entries(allBars)
|
|
6841
|
+
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
6842
|
+
.join(", ");
|
|
6548
6843
|
log$1(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
6549
|
-
type:
|
|
6844
|
+
type: "info",
|
|
6550
6845
|
});
|
|
6551
6846
|
return {
|
|
6552
6847
|
bars: allBars,
|
|
@@ -6562,7 +6857,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6562
6857
|
|
|
6563
6858
|
*/
|
|
6564
6859
|
async getLatestBars(symbols, currency) {
|
|
6565
|
-
return this.makeRequest(
|
|
6860
|
+
return this.makeRequest("/stocks/bars/latest", "GET", {
|
|
6566
6861
|
symbols,
|
|
6567
6862
|
feed: DEFAULT_FEED,
|
|
6568
6863
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6574,7 +6869,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6574
6869
|
* @returns Last trade details including price, size, exchange, and conditions
|
|
6575
6870
|
*/
|
|
6576
6871
|
async getLastTrade(symbol) {
|
|
6577
|
-
return this.makeRequest(`/v1/last/stocks/${symbol}`,
|
|
6872
|
+
return this.makeRequest(`/v1/last/stocks/${symbol}`, "GET");
|
|
6578
6873
|
}
|
|
6579
6874
|
/**
|
|
6580
6875
|
* Get the most recent trades for requested symbols
|
|
@@ -6585,7 +6880,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6585
6880
|
|
|
6586
6881
|
*/
|
|
6587
6882
|
async getLatestTrades(symbols, feed, currency) {
|
|
6588
|
-
return this.makeRequest(
|
|
6883
|
+
return this.makeRequest("/stocks/trades/latest", "GET", {
|
|
6589
6884
|
symbols,
|
|
6590
6885
|
feed: feed || DEFAULT_FEED,
|
|
6591
6886
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6601,13 +6896,15 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6601
6896
|
async getLatestQuotes(symbols, feed, currency) {
|
|
6602
6897
|
// Return empty response if symbols array is empty to avoid API error
|
|
6603
6898
|
if (!symbols || symbols.length === 0) {
|
|
6604
|
-
log$1(
|
|
6899
|
+
log$1("No symbols provided to getLatestQuotes, returning empty response", {
|
|
6900
|
+
type: "warn",
|
|
6901
|
+
});
|
|
6605
6902
|
return {
|
|
6606
6903
|
quotes: {},
|
|
6607
6904
|
currency: currency || DEFAULT_CURRENCY,
|
|
6608
6905
|
};
|
|
6609
6906
|
}
|
|
6610
|
-
return this.makeRequest(
|
|
6907
|
+
return this.makeRequest("/stocks/quotes/latest", "GET", {
|
|
6611
6908
|
symbols,
|
|
6612
6909
|
feed: feed || DEFAULT_FEED,
|
|
6613
6910
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6621,7 +6918,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6621
6918
|
* @returns Latest quote data with symbol and currency information
|
|
6622
6919
|
*/
|
|
6623
6920
|
async getLatestQuote(symbol, feed, currency) {
|
|
6624
|
-
return this.makeRequest(`/stocks/${symbol}/quotes/latest`,
|
|
6921
|
+
return this.makeRequest(`/stocks/${symbol}/quotes/latest`, "GET", {
|
|
6625
6922
|
feed: feed || DEFAULT_FEED,
|
|
6626
6923
|
currency,
|
|
6627
6924
|
});
|
|
@@ -6637,13 +6934,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6637
6934
|
const prevMarketDate = getLastFullTradingDate(date);
|
|
6638
6935
|
const response = await this.getHistoricalBars({
|
|
6639
6936
|
symbols: [symbol],
|
|
6640
|
-
timeframe:
|
|
6937
|
+
timeframe: "1Day",
|
|
6641
6938
|
start: prevMarketDate.date.toISOString(),
|
|
6642
6939
|
end: prevMarketDate.date.toISOString(),
|
|
6643
6940
|
limit: 1,
|
|
6644
6941
|
});
|
|
6645
6942
|
if (!response.bars[symbol] || response.bars[symbol].length === 0) {
|
|
6646
|
-
log$1(`No previous close data available for ${symbol}`, {
|
|
6943
|
+
log$1(`No previous close data available for ${symbol}`, {
|
|
6944
|
+
type: "error",
|
|
6945
|
+
symbol,
|
|
6946
|
+
});
|
|
6647
6947
|
return null;
|
|
6648
6948
|
}
|
|
6649
6949
|
return response.bars[symbol][0];
|
|
@@ -6658,7 +6958,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6658
6958
|
async getHourlyPrices(symbol, start, end) {
|
|
6659
6959
|
const response = await this.getHistoricalBars({
|
|
6660
6960
|
symbols: [symbol],
|
|
6661
|
-
timeframe:
|
|
6961
|
+
timeframe: "1Hour",
|
|
6662
6962
|
start: new Date(start).toISOString(),
|
|
6663
6963
|
end: new Date(end).toISOString(),
|
|
6664
6964
|
limit: 96, // Last 96 hours (4 days)
|
|
@@ -6675,7 +6975,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6675
6975
|
async getHalfHourlyPrices(symbol, start, end) {
|
|
6676
6976
|
const response = await this.getHistoricalBars({
|
|
6677
6977
|
symbols: [symbol],
|
|
6678
|
-
timeframe:
|
|
6978
|
+
timeframe: "30Min",
|
|
6679
6979
|
start: new Date(start).toISOString(),
|
|
6680
6980
|
end: new Date(end).toISOString(),
|
|
6681
6981
|
limit: 16 * 2 * 4, // last 4 days, 16 hours per day, 2 bars per hour
|
|
@@ -6692,7 +6992,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6692
6992
|
async getDailyPrices(symbol, start, end) {
|
|
6693
6993
|
const response = await this.getHistoricalBars({
|
|
6694
6994
|
symbols: [symbol],
|
|
6695
|
-
timeframe:
|
|
6995
|
+
timeframe: "1Day",
|
|
6696
6996
|
start: new Date(start).toISOString(),
|
|
6697
6997
|
end: new Date(end).toISOString(),
|
|
6698
6998
|
limit: 100, // Last 100 days
|
|
@@ -6724,7 +7024,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6724
7024
|
*/
|
|
6725
7025
|
static analyzeBars(bars) {
|
|
6726
7026
|
if (!bars || bars.length === 0) {
|
|
6727
|
-
return
|
|
7027
|
+
return "No price data available";
|
|
6728
7028
|
}
|
|
6729
7029
|
const firstBar = bars[0];
|
|
6730
7030
|
const lastBar = bars[bars.length - 1];
|
|
@@ -6749,7 +7049,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6749
7049
|
*/
|
|
6750
7050
|
async getAssets(params) {
|
|
6751
7051
|
// Endpoint: GET /v2/assets
|
|
6752
|
-
return this.makeRequest(
|
|
7052
|
+
return this.makeRequest("/assets", "GET", params, "api"); // use apiURL
|
|
6753
7053
|
}
|
|
6754
7054
|
/**
|
|
6755
7055
|
* Get a single asset by symbol or asset_id
|
|
@@ -6759,7 +7059,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6759
7059
|
*/
|
|
6760
7060
|
async getAsset(symbolOrAssetId) {
|
|
6761
7061
|
// Endpoint: GET /v2/assets/{symbol_or_asset_id}
|
|
6762
|
-
return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`,
|
|
7062
|
+
return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, "GET", undefined, "api");
|
|
6763
7063
|
}
|
|
6764
7064
|
// ===== OPTIONS MARKET DATA METHODS =====
|
|
6765
7065
|
/**
|
|
@@ -6771,7 +7071,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6771
7071
|
*/
|
|
6772
7072
|
async getOptionsChain(params) {
|
|
6773
7073
|
const { underlying_symbol, ...queryParams } = params;
|
|
6774
|
-
return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`,
|
|
7074
|
+
return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, "GET", queryParams, "v1beta1");
|
|
6775
7075
|
}
|
|
6776
7076
|
/**
|
|
6777
7077
|
* Get the most recent trades for requested option contract symbols
|
|
@@ -6783,7 +7083,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6783
7083
|
async getLatestOptionsTrades(params) {
|
|
6784
7084
|
// Remove limit and page_token as they're not supported by this endpoint
|
|
6785
7085
|
const { limit, page_token, ...requestParams } = params;
|
|
6786
|
-
return this.makeRequest(
|
|
7086
|
+
return this.makeRequest("/options/trades/latest", "GET", requestParams, "v1beta1");
|
|
6787
7087
|
}
|
|
6788
7088
|
/**
|
|
6789
7089
|
* Get the most recent quotes for requested option contract symbols
|
|
@@ -6795,7 +7095,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6795
7095
|
async getLatestOptionsQuotes(params) {
|
|
6796
7096
|
// Remove limit and page_token as they're not supported by this endpoint
|
|
6797
7097
|
const { limit, page_token, ...requestParams } = params;
|
|
6798
|
-
return this.makeRequest(
|
|
7098
|
+
return this.makeRequest("/options/quotes/latest", "GET", requestParams, "v1beta1");
|
|
6799
7099
|
}
|
|
6800
7100
|
/**
|
|
6801
7101
|
* Get historical OHLCV bars for option contract symbols
|
|
@@ -6807,18 +7107,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6807
7107
|
*/
|
|
6808
7108
|
async getHistoricalOptionsBars(params) {
|
|
6809
7109
|
const symbols = params.symbols;
|
|
6810
|
-
const symbolsStr = symbols.join(
|
|
7110
|
+
const symbolsStr = symbols.join(",");
|
|
6811
7111
|
let allBars = {};
|
|
6812
7112
|
let pageToken = null;
|
|
6813
7113
|
let hasMorePages = true;
|
|
6814
7114
|
let totalBarsCount = 0;
|
|
6815
7115
|
let pageCount = 0;
|
|
6816
7116
|
// Initialize bar arrays for each symbol
|
|
6817
|
-
symbols.forEach(symbol => {
|
|
7117
|
+
symbols.forEach((symbol) => {
|
|
6818
7118
|
allBars[symbol] = [];
|
|
6819
7119
|
});
|
|
6820
|
-
log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start ||
|
|
6821
|
-
type:
|
|
7120
|
+
log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`, {
|
|
7121
|
+
type: "info",
|
|
6822
7122
|
});
|
|
6823
7123
|
while (hasMorePages) {
|
|
6824
7124
|
pageCount++;
|
|
@@ -6826,9 +7126,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6826
7126
|
...params,
|
|
6827
7127
|
...(pageToken && { page_token: pageToken }),
|
|
6828
7128
|
};
|
|
6829
|
-
const response = await this.makeRequest(
|
|
7129
|
+
const response = await this.makeRequest("/options/bars", "GET", requestParams, "v1beta1");
|
|
6830
7130
|
if (!response.bars) {
|
|
6831
|
-
log$1(`No options bars data found in response for ${symbolsStr}`, {
|
|
7131
|
+
log$1(`No options bars data found in response for ${symbolsStr}`, {
|
|
7132
|
+
type: "warn",
|
|
7133
|
+
});
|
|
6832
7134
|
break;
|
|
6833
7135
|
}
|
|
6834
7136
|
// Combine bars for each symbol
|
|
@@ -6840,7 +7142,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6840
7142
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
6841
7143
|
pageBarsCount += bars.length;
|
|
6842
7144
|
// Track date range for this page
|
|
6843
|
-
bars.forEach(bar => {
|
|
7145
|
+
bars.forEach((bar) => {
|
|
6844
7146
|
const barDate = new Date(bar.t);
|
|
6845
7147
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
6846
7148
|
earliestTimestamp = barDate;
|
|
@@ -6856,21 +7158,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6856
7158
|
hasMorePages = !!pageToken;
|
|
6857
7159
|
// Enhanced logging with date range and progress info
|
|
6858
7160
|
const dateRangeStr = earliestTimestamp && latestTimestamp
|
|
6859
|
-
? `${earliestTimestamp.toLocaleDateString(
|
|
6860
|
-
:
|
|
6861
|
-
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6862
|
-
type:
|
|
7161
|
+
? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
|
|
7162
|
+
: "unknown range";
|
|
7163
|
+
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
|
|
7164
|
+
type: "info",
|
|
6863
7165
|
});
|
|
6864
7166
|
// Prevent infinite loops
|
|
6865
7167
|
if (pageCount > 1000) {
|
|
6866
|
-
log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
7168
|
+
log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
|
|
6867
7169
|
break;
|
|
6868
7170
|
}
|
|
6869
7171
|
}
|
|
6870
7172
|
// Final summary
|
|
6871
|
-
const symbolCounts = Object.entries(allBars)
|
|
7173
|
+
const symbolCounts = Object.entries(allBars)
|
|
7174
|
+
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
7175
|
+
.join(", ");
|
|
6872
7176
|
log$1(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
6873
|
-
type:
|
|
7177
|
+
type: "info",
|
|
6874
7178
|
});
|
|
6875
7179
|
return {
|
|
6876
7180
|
bars: allBars,
|
|
@@ -6887,18 +7191,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6887
7191
|
*/
|
|
6888
7192
|
async getHistoricalOptionsTrades(params) {
|
|
6889
7193
|
const symbols = params.symbols;
|
|
6890
|
-
const symbolsStr = symbols.join(
|
|
7194
|
+
const symbolsStr = symbols.join(",");
|
|
6891
7195
|
let allTrades = {};
|
|
6892
7196
|
let pageToken = null;
|
|
6893
7197
|
let hasMorePages = true;
|
|
6894
7198
|
let totalTradesCount = 0;
|
|
6895
7199
|
let pageCount = 0;
|
|
6896
7200
|
// Initialize trades arrays for each symbol
|
|
6897
|
-
symbols.forEach(symbol => {
|
|
7201
|
+
symbols.forEach((symbol) => {
|
|
6898
7202
|
allTrades[symbol] = [];
|
|
6899
7203
|
});
|
|
6900
|
-
log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start ||
|
|
6901
|
-
type:
|
|
7204
|
+
log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || "no start"} to ${params.end || "no end"})`, {
|
|
7205
|
+
type: "info",
|
|
6902
7206
|
});
|
|
6903
7207
|
while (hasMorePages) {
|
|
6904
7208
|
pageCount++;
|
|
@@ -6906,9 +7210,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6906
7210
|
...params,
|
|
6907
7211
|
...(pageToken && { page_token: pageToken }),
|
|
6908
7212
|
};
|
|
6909
|
-
const response = await this.makeRequest(
|
|
7213
|
+
const response = await this.makeRequest("/options/trades", "GET", requestParams, "v1beta1");
|
|
6910
7214
|
if (!response.trades) {
|
|
6911
|
-
log$1(`No options trades data found in response for ${symbolsStr}`, {
|
|
7215
|
+
log$1(`No options trades data found in response for ${symbolsStr}`, {
|
|
7216
|
+
type: "warn",
|
|
7217
|
+
});
|
|
6912
7218
|
break;
|
|
6913
7219
|
}
|
|
6914
7220
|
// Combine trades for each symbol
|
|
@@ -6920,7 +7226,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6920
7226
|
allTrades[symbol] = [...allTrades[symbol], ...trades];
|
|
6921
7227
|
pageTradesCount += trades.length;
|
|
6922
7228
|
// Track date range for this page
|
|
6923
|
-
trades.forEach(trade => {
|
|
7229
|
+
trades.forEach((trade) => {
|
|
6924
7230
|
const tradeDate = new Date(trade.t);
|
|
6925
7231
|
if (!earliestTimestamp || tradeDate < earliestTimestamp) {
|
|
6926
7232
|
earliestTimestamp = tradeDate;
|
|
@@ -6936,21 +7242,23 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6936
7242
|
hasMorePages = !!pageToken;
|
|
6937
7243
|
// Enhanced logging with date range and progress info
|
|
6938
7244
|
const dateRangeStr = earliestTimestamp && latestTimestamp
|
|
6939
|
-
? `${earliestTimestamp.toLocaleDateString(
|
|
6940
|
-
:
|
|
6941
|
-
log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6942
|
-
type:
|
|
7245
|
+
? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}`
|
|
7246
|
+
: "unknown range";
|
|
7247
|
+
log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`, {
|
|
7248
|
+
type: "info",
|
|
6943
7249
|
});
|
|
6944
7250
|
// Prevent infinite loops
|
|
6945
7251
|
if (pageCount > 1000) {
|
|
6946
|
-
log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
7252
|
+
log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
|
|
6947
7253
|
break;
|
|
6948
7254
|
}
|
|
6949
7255
|
}
|
|
6950
7256
|
// Final summary
|
|
6951
|
-
const symbolCounts = Object.entries(allTrades)
|
|
7257
|
+
const symbolCounts = Object.entries(allTrades)
|
|
7258
|
+
.map(([symbol, trades]) => `${symbol}: ${trades.length}`)
|
|
7259
|
+
.join(", ");
|
|
6952
7260
|
log$1(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
|
|
6953
|
-
type:
|
|
7261
|
+
type: "info",
|
|
6954
7262
|
});
|
|
6955
7263
|
return {
|
|
6956
7264
|
trades: allTrades,
|
|
@@ -6968,7 +7276,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6968
7276
|
async getOptionsSnapshot(params) {
|
|
6969
7277
|
// Remove limit and page_token as they may not be supported by this endpoint
|
|
6970
7278
|
const { limit, page_token, ...requestParams } = params;
|
|
6971
|
-
return this.makeRequest(
|
|
7279
|
+
return this.makeRequest("/options/snapshots", "GET", requestParams, "v1beta1");
|
|
6972
7280
|
}
|
|
6973
7281
|
/**
|
|
6974
7282
|
* Get condition codes for options trades or quotes
|
|
@@ -6979,7 +7287,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6979
7287
|
* @see https://docs.alpaca.markets/reference/optionmetaconditions
|
|
6980
7288
|
*/
|
|
6981
7289
|
async getOptionsConditionCodes(tickType) {
|
|
6982
|
-
return this.makeRequest(`/options/meta/conditions/${tickType}`,
|
|
7290
|
+
return this.makeRequest(`/options/meta/conditions/${tickType}`, "GET", undefined, "v1beta1");
|
|
6983
7291
|
}
|
|
6984
7292
|
/**
|
|
6985
7293
|
* Get exchange codes for options
|
|
@@ -6989,7 +7297,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6989
7297
|
* @see https://docs.alpaca.markets/reference/optionmetaexchanges
|
|
6990
7298
|
*/
|
|
6991
7299
|
async getOptionsExchangeCodes() {
|
|
6992
|
-
return this.makeRequest(
|
|
7300
|
+
return this.makeRequest("/options/meta/exchanges", "GET", undefined, "v1beta1");
|
|
6993
7301
|
}
|
|
6994
7302
|
/**
|
|
6995
7303
|
* Analyzes an array of option bars and returns a summary string
|
|
@@ -6998,7 +7306,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6998
7306
|
*/
|
|
6999
7307
|
static analyzeOptionBars(bars) {
|
|
7000
7308
|
if (!bars || bars.length === 0) {
|
|
7001
|
-
return
|
|
7309
|
+
return "No option price data available";
|
|
7002
7310
|
}
|
|
7003
7311
|
const firstBar = bars[0];
|
|
7004
7312
|
const lastBar = bars[bars.length - 1];
|
|
@@ -7022,7 +7330,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7022
7330
|
*/
|
|
7023
7331
|
static formatOptionGreeks(greeks) {
|
|
7024
7332
|
if (!greeks) {
|
|
7025
|
-
return
|
|
7333
|
+
return "No greeks data available";
|
|
7026
7334
|
}
|
|
7027
7335
|
const parts = [];
|
|
7028
7336
|
if (greeks.delta !== undefined)
|
|
@@ -7035,7 +7343,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7035
7343
|
parts.push(`Vega: ${greeks.vega.toFixed(4)}`);
|
|
7036
7344
|
if (greeks.rho !== undefined)
|
|
7037
7345
|
parts.push(`Rho: ${greeks.rho.toFixed(4)}`);
|
|
7038
|
-
return parts.length > 0 ? parts.join(
|
|
7346
|
+
return parts.length > 0 ? parts.join(", ") : "No greeks data available";
|
|
7039
7347
|
}
|
|
7040
7348
|
/**
|
|
7041
7349
|
* Interprets condition codes using the provided condition codes mapping
|
|
@@ -7045,12 +7353,14 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7045
7353
|
*/
|
|
7046
7354
|
static interpretConditionCodes(conditionCodes, conditionCodesMap) {
|
|
7047
7355
|
if (!conditionCodes || conditionCodes.length === 0) {
|
|
7048
|
-
return
|
|
7356
|
+
return "No conditions";
|
|
7049
7357
|
}
|
|
7050
7358
|
const descriptions = conditionCodes
|
|
7051
7359
|
.map((code) => conditionCodesMap[code] || `Unknown (${code})`)
|
|
7052
7360
|
.filter((desc) => desc !== undefined);
|
|
7053
|
-
return descriptions.length > 0
|
|
7361
|
+
return descriptions.length > 0
|
|
7362
|
+
? descriptions.join(", ")
|
|
7363
|
+
: "No condition descriptions available";
|
|
7054
7364
|
}
|
|
7055
7365
|
/**
|
|
7056
7366
|
* Gets the exchange name from exchange code using the provided exchange codes mapping
|
|
@@ -7059,7 +7369,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7059
7369
|
* @returns Exchange name or formatted unknown exchange
|
|
7060
7370
|
*/
|
|
7061
7371
|
static getExchangeName(exchangeCode, exchangeCodesMap) {
|
|
7062
|
-
return exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})
|
|
7372
|
+
return (exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`);
|
|
7063
7373
|
}
|
|
7064
7374
|
/**
|
|
7065
7375
|
* Fetches news articles from Alpaca API for a symbol, paginating through all results.
|
|
@@ -7072,7 +7382,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7072
7382
|
start: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
|
7073
7383
|
end: new Date(),
|
|
7074
7384
|
limit: 10,
|
|
7075
|
-
sort:
|
|
7385
|
+
sort: "desc",
|
|
7076
7386
|
include_content: true,
|
|
7077
7387
|
};
|
|
7078
7388
|
const mergedParams = { ...defaultParams, ...params };
|
|
@@ -7086,38 +7396,52 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7086
7396
|
if (!content)
|
|
7087
7397
|
return undefined;
|
|
7088
7398
|
// Remove excessive whitespace, newlines, and trim
|
|
7089
|
-
return content.replace(/\s+/g,
|
|
7399
|
+
return content.replace(/\s+/g, " ").trim();
|
|
7090
7400
|
}
|
|
7091
7401
|
while (hasMorePages) {
|
|
7092
7402
|
const queryParams = new URLSearchParams({
|
|
7093
|
-
...(mergedParams.start && {
|
|
7094
|
-
|
|
7403
|
+
...(mergedParams.start && {
|
|
7404
|
+
start: new Date(mergedParams.start).toISOString(),
|
|
7405
|
+
}),
|
|
7406
|
+
...(mergedParams.end && {
|
|
7407
|
+
end: new Date(mergedParams.end).toISOString(),
|
|
7408
|
+
}),
|
|
7095
7409
|
...(symbol && { symbols: symbol }),
|
|
7096
|
-
...(mergedParams.limit && {
|
|
7410
|
+
...(mergedParams.limit && {
|
|
7411
|
+
limit: Math.min(50, maxLimit - fetchedCount).toString(),
|
|
7412
|
+
}),
|
|
7097
7413
|
...(mergedParams.sort && { sort: mergedParams.sort }),
|
|
7098
|
-
...(mergedParams.include_content !== undefined
|
|
7414
|
+
...(mergedParams.include_content !== undefined
|
|
7415
|
+
? { include_content: mergedParams.include_content.toString() }
|
|
7416
|
+
: {}),
|
|
7099
7417
|
...(pageToken && { page_token: pageToken }),
|
|
7100
7418
|
});
|
|
7101
7419
|
const url = `${this.v1beta1url}/news?${queryParams}`;
|
|
7102
|
-
log$1(`Fetching news from: ${url}`, { type:
|
|
7420
|
+
log$1(`Fetching news from: ${url}`, { type: "debug", symbol });
|
|
7103
7421
|
const response = await fetch(url, {
|
|
7104
|
-
method:
|
|
7422
|
+
method: "GET",
|
|
7105
7423
|
headers: this.headers,
|
|
7106
7424
|
});
|
|
7107
7425
|
if (!response.ok) {
|
|
7108
7426
|
const errorText = await response.text();
|
|
7109
|
-
log$1(`Alpaca news API error (${response.status}): ${errorText}`, {
|
|
7427
|
+
log$1(`Alpaca news API error (${response.status}): ${errorText}`, {
|
|
7428
|
+
type: "error",
|
|
7429
|
+
symbol,
|
|
7430
|
+
});
|
|
7110
7431
|
throw new Error(`Alpaca news API error (${response.status}): ${errorText}`);
|
|
7111
7432
|
}
|
|
7112
7433
|
const data = await response.json();
|
|
7113
7434
|
if (!data.news || !Array.isArray(data.news)) {
|
|
7114
|
-
log$1(`No news data found in Alpaca response for ${symbol}`, {
|
|
7435
|
+
log$1(`No news data found in Alpaca response for ${symbol}`, {
|
|
7436
|
+
type: "warn",
|
|
7437
|
+
symbol,
|
|
7438
|
+
});
|
|
7115
7439
|
break;
|
|
7116
7440
|
}
|
|
7117
7441
|
const transformedNews = data.news.map((article) => ({
|
|
7118
7442
|
symbols: article.symbols,
|
|
7119
7443
|
title: article.headline,
|
|
7120
|
-
summary: cleanContent(article.summary) ??
|
|
7444
|
+
summary: cleanContent(article.summary) ?? "",
|
|
7121
7445
|
content: article.content ? cleanContent(article.content) : undefined,
|
|
7122
7446
|
url: article.url,
|
|
7123
7447
|
source: article.source,
|
|
@@ -7130,7 +7454,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7130
7454
|
fetchedCount = newsArticles.length;
|
|
7131
7455
|
pageToken = data.next_page_token || null;
|
|
7132
7456
|
hasMorePages = !!pageToken && (!maxLimit || fetchedCount < maxLimit);
|
|
7133
|
-
log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type:
|
|
7457
|
+
log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: "debug", symbol });
|
|
7134
7458
|
if (maxLimit && fetchedCount >= maxLimit) {
|
|
7135
7459
|
newsArticles = newsArticles.slice(0, maxLimit);
|
|
7136
7460
|
break;
|
|
@@ -7161,11 +7485,11 @@ const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
|
7161
7485
|
*/
|
|
7162
7486
|
function formatCurrency(value) {
|
|
7163
7487
|
if (isNaN(value)) {
|
|
7164
|
-
return
|
|
7488
|
+
return "$0.00";
|
|
7165
7489
|
}
|
|
7166
|
-
return new Intl.NumberFormat(
|
|
7167
|
-
style:
|
|
7168
|
-
currency:
|
|
7490
|
+
return new Intl.NumberFormat("en-US", {
|
|
7491
|
+
style: "currency",
|
|
7492
|
+
currency: "USD",
|
|
7169
7493
|
}).format(value);
|
|
7170
7494
|
}
|
|
7171
7495
|
/**
|
|
@@ -7178,13 +7502,13 @@ function formatCurrency(value) {
|
|
|
7178
7502
|
*/
|
|
7179
7503
|
function formatNumber(value) {
|
|
7180
7504
|
if (isNaN(value)) {
|
|
7181
|
-
return
|
|
7505
|
+
return "0";
|
|
7182
7506
|
}
|
|
7183
|
-
return new Intl.NumberFormat(
|
|
7507
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
7184
7508
|
}
|
|
7185
7509
|
|
|
7186
7510
|
const log = (message) => {
|
|
7187
|
-
console.log(`[${new Date().toLocaleString(
|
|
7511
|
+
console.log(`[${new Date().toLocaleString("en-US", { timeZone: "America/New_York" })}] ${message}`);
|
|
7188
7512
|
};
|
|
7189
7513
|
// async function testCreateEquitiesTrade() {
|
|
7190
7514
|
// try {
|
|
@@ -7408,18 +7732,18 @@ const log = (message) => {
|
|
|
7408
7732
|
// testing retrieving pre-market data (just 9:00am to 9:30am on 1 july 2025 for SPY) using the market data api
|
|
7409
7733
|
async function testPreMarketData() {
|
|
7410
7734
|
try {
|
|
7411
|
-
log(
|
|
7735
|
+
log("Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...");
|
|
7412
7736
|
// Set up the time range in America/New_York, convert to UTC ISO strings
|
|
7413
|
-
const symbol =
|
|
7414
|
-
const nyTimeZone =
|
|
7737
|
+
const symbol = "SPY";
|
|
7738
|
+
const nyTimeZone = "America/New_York";
|
|
7415
7739
|
// 9:00am and 9:30am in NY time
|
|
7416
|
-
const startNY = new Date(
|
|
7417
|
-
const endNY = new Date(
|
|
7740
|
+
const startNY = new Date("2025-07-01T09:00:00-04:00");
|
|
7741
|
+
const endNY = new Date("2025-07-01T09:30:00-04:00");
|
|
7418
7742
|
const startUTC = startNY.toISOString();
|
|
7419
7743
|
const endUTC = endNY.toISOString();
|
|
7420
7744
|
const barsResponse = await marketDataAPI.getHistoricalBars({
|
|
7421
7745
|
symbols: [symbol],
|
|
7422
|
-
timeframe:
|
|
7746
|
+
timeframe: "1Min",
|
|
7423
7747
|
start: startUTC,
|
|
7424
7748
|
end: endUTC,
|
|
7425
7749
|
limit: 1000,
|
|
@@ -7427,19 +7751,21 @@ async function testPreMarketData() {
|
|
|
7427
7751
|
const bars = barsResponse.bars[symbol] || [];
|
|
7428
7752
|
log(`Fetched ${bars.length} 1-min bars for SPY from 9:00am to 9:30am (NY) on 2025-07-01.`);
|
|
7429
7753
|
if (bars.length === 0) {
|
|
7430
|
-
log(
|
|
7754
|
+
log("No pre-market bars returned.");
|
|
7431
7755
|
return;
|
|
7432
7756
|
}
|
|
7433
7757
|
// Print each bar
|
|
7434
7758
|
bars.forEach((bar, i) => {
|
|
7435
|
-
const barTime = new Date(bar.t).toLocaleString(
|
|
7759
|
+
const barTime = new Date(bar.t).toLocaleString("en-US", {
|
|
7760
|
+
timeZone: nyTimeZone,
|
|
7761
|
+
});
|
|
7436
7762
|
log(`Bar ${i + 1}: ${barTime} | O: ${formatCurrency(bar.o)} H: ${formatCurrency(bar.h)} L: ${formatCurrency(bar.l)} C: ${formatCurrency(bar.c)} V: ${formatNumber(bar.v)} VWAP: ${formatCurrency(bar.vw)} N: ${bar.n}`);
|
|
7437
7763
|
});
|
|
7438
7764
|
// Print summary
|
|
7439
7765
|
const summary = AlpacaMarketDataAPI.analyzeBars(bars);
|
|
7440
7766
|
if (summary)
|
|
7441
7767
|
log(`Summary: ${summary}`);
|
|
7442
|
-
log(
|
|
7768
|
+
log("Pre-market data test complete.");
|
|
7443
7769
|
}
|
|
7444
7770
|
catch (error) {
|
|
7445
7771
|
log(`❌ Error in testPreMarketData: ${error instanceof Error ? error.message : error}`);
|