@adaptic/utils 0.1.43 → 0.1.45
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 +61 -153
- package/dist/index.cjs +5356 -60315
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +5356 -60054
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +361 -824
- package/dist/test.js.map +1 -1
- package/dist/types/adaptic.d.ts +2 -2
- package/dist/types/adaptic.d.ts.map +1 -1
- package/dist/types/alpaca-functions.d.ts +233 -0
- package/dist/types/alpaca-functions.d.ts.map +1 -0
- package/dist/types/alpaca-market-data-api.d.ts +9 -24
- 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 +1 -7
- 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/crypto.d.ts +4 -4
- 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/examples/asset-allocation-example.d.ts +6 -7
- package/dist/types/examples/asset-allocation-example.d.ts.map +1 -1
- package/dist/types/format-tools.d.ts.map +1 -1
- package/dist/types/index.d.ts +55 -393
- package/dist/types/index.d.ts.map +1 -1
- 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 +13 -75
- 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 +1 -4
- package/dist/types/misc-utils.d.ts.map +1 -1
- package/dist/types/performance-metrics.d.ts +6 -6
- 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/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 +89 -252
- 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 +4 -4
- 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/package.json +6 -24
- package/dist/types/__tests__/alpaca-functions.test.d.ts +0 -2
- package/dist/types/__tests__/alpaca-functions.test.d.ts.map +0 -1
- package/dist/types/__tests__/api-endpoints.test.d.ts +0 -2
- package/dist/types/__tests__/api-endpoints.test.d.ts.map +0 -1
- package/dist/types/__tests__/asset-allocation.test.d.ts +0 -2
- package/dist/types/__tests__/asset-allocation.test.d.ts.map +0 -1
- package/dist/types/__tests__/auth-validator.test.d.ts +0 -2
- package/dist/types/__tests__/auth-validator.test.d.ts.map +0 -1
- package/dist/types/__tests__/cache.test.d.ts +0 -2
- package/dist/types/__tests__/cache.test.d.ts.map +0 -1
- package/dist/types/__tests__/errors.test.d.ts +0 -2
- package/dist/types/__tests__/errors.test.d.ts.map +0 -1
- package/dist/types/__tests__/financial-regression.test.d.ts +0 -2
- package/dist/types/__tests__/financial-regression.test.d.ts.map +0 -1
- package/dist/types/__tests__/format-tools.test.d.ts +0 -2
- package/dist/types/__tests__/format-tools.test.d.ts.map +0 -1
- package/dist/types/__tests__/http-keep-alive.test.d.ts +0 -2
- package/dist/types/__tests__/http-keep-alive.test.d.ts.map +0 -1
- package/dist/types/__tests__/http-timeout.test.d.ts +0 -2
- package/dist/types/__tests__/http-timeout.test.d.ts.map +0 -1
- package/dist/types/__tests__/logger.test.d.ts +0 -2
- package/dist/types/__tests__/logger.test.d.ts.map +0 -1
- package/dist/types/__tests__/market-time.test.d.ts +0 -2
- package/dist/types/__tests__/market-time.test.d.ts.map +0 -1
- package/dist/types/__tests__/misc-utils.test.d.ts +0 -2
- package/dist/types/__tests__/misc-utils.test.d.ts.map +0 -1
- package/dist/types/__tests__/paginator.test.d.ts +0 -2
- package/dist/types/__tests__/paginator.test.d.ts.map +0 -1
- package/dist/types/__tests__/performance-metrics.test.d.ts +0 -2
- package/dist/types/__tests__/performance-metrics.test.d.ts.map +0 -1
- package/dist/types/__tests__/polygon.test.d.ts +0 -2
- package/dist/types/__tests__/polygon.test.d.ts.map +0 -1
- package/dist/types/__tests__/price-utils.test.d.ts +0 -2
- package/dist/types/__tests__/price-utils.test.d.ts.map +0 -1
- package/dist/types/__tests__/property-based-financial.test.d.ts +0 -2
- package/dist/types/__tests__/property-based-financial.test.d.ts.map +0 -1
- package/dist/types/__tests__/rate-limiter.test.d.ts +0 -2
- package/dist/types/__tests__/rate-limiter.test.d.ts.map +0 -1
- package/dist/types/__tests__/schema-validation.test.d.ts +0 -2
- package/dist/types/__tests__/schema-validation.test.d.ts.map +0 -1
- package/dist/types/__tests__/technical-analysis.test.d.ts +0 -2
- package/dist/types/__tests__/technical-analysis.test.d.ts.map +0 -1
- package/dist/types/__tests__/time-utils.test.d.ts +0 -2
- package/dist/types/__tests__/time-utils.test.d.ts.map +0 -1
- package/dist/types/alpaca/client.d.ts +0 -95
- package/dist/types/alpaca/client.d.ts.map +0 -1
- package/dist/types/alpaca/crypto/data.d.ts +0 -281
- package/dist/types/alpaca/crypto/data.d.ts.map +0 -1
- package/dist/types/alpaca/crypto/index.d.ts +0 -75
- package/dist/types/alpaca/crypto/index.d.ts.map +0 -1
- package/dist/types/alpaca/crypto/orders.d.ts +0 -221
- package/dist/types/alpaca/crypto/orders.d.ts.map +0 -1
- package/dist/types/alpaca/index.d.ts +0 -205
- package/dist/types/alpaca/index.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/account.d.ts +0 -34
- package/dist/types/alpaca/legacy/account.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/assets.d.ts +0 -13
- package/dist/types/alpaca/legacy/assets.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/auth.d.ts +0 -18
- package/dist/types/alpaca/legacy/auth.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/index.d.ts +0 -15
- package/dist/types/alpaca/legacy/index.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/market-data.d.ts +0 -32
- package/dist/types/alpaca/legacy/market-data.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/orders.d.ts +0 -84
- package/dist/types/alpaca/legacy/orders.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/positions.d.ts +0 -66
- package/dist/types/alpaca/legacy/positions.d.ts.map +0 -1
- package/dist/types/alpaca/legacy/utils.d.ts +0 -18
- package/dist/types/alpaca/legacy/utils.d.ts.map +0 -1
- package/dist/types/alpaca/market-data/bars.d.ts +0 -142
- package/dist/types/alpaca/market-data/bars.d.ts.map +0 -1
- package/dist/types/alpaca/market-data/index.d.ts +0 -13
- package/dist/types/alpaca/market-data/index.d.ts.map +0 -1
- package/dist/types/alpaca/market-data/news.d.ts +0 -87
- package/dist/types/alpaca/market-data/news.d.ts.map +0 -1
- package/dist/types/alpaca/market-data/quotes.d.ts +0 -85
- package/dist/types/alpaca/market-data/quotes.d.ts.map +0 -1
- package/dist/types/alpaca/market-data/trades.d.ts +0 -98
- package/dist/types/alpaca/market-data/trades.d.ts.map +0 -1
- package/dist/types/alpaca/options/contracts.d.ts +0 -279
- package/dist/types/alpaca/options/contracts.d.ts.map +0 -1
- package/dist/types/alpaca/options/data.d.ts +0 -126
- package/dist/types/alpaca/options/data.d.ts.map +0 -1
- package/dist/types/alpaca/options/index.d.ts +0 -17
- package/dist/types/alpaca/options/index.d.ts.map +0 -1
- package/dist/types/alpaca/options/orders.d.ts +0 -366
- package/dist/types/alpaca/options/orders.d.ts.map +0 -1
- package/dist/types/alpaca/options/strategies.d.ts +0 -224
- package/dist/types/alpaca/options/strategies.d.ts.map +0 -1
- package/dist/types/alpaca/streams/base-stream.d.ts +0 -143
- package/dist/types/alpaca/streams/base-stream.d.ts.map +0 -1
- package/dist/types/alpaca/streams/crypto-stream.d.ts +0 -173
- package/dist/types/alpaca/streams/crypto-stream.d.ts.map +0 -1
- package/dist/types/alpaca/streams/index.d.ts +0 -54
- package/dist/types/alpaca/streams/index.d.ts.map +0 -1
- package/dist/types/alpaca/streams/option-stream.d.ts +0 -167
- package/dist/types/alpaca/streams/option-stream.d.ts.map +0 -1
- package/dist/types/alpaca/streams/stock-stream.d.ts +0 -176
- package/dist/types/alpaca/streams/stock-stream.d.ts.map +0 -1
- package/dist/types/alpaca/streams/stream-manager.d.ts +0 -277
- package/dist/types/alpaca/streams/stream-manager.d.ts.map +0 -1
- package/dist/types/alpaca/streams/trading-stream.d.ts +0 -186
- package/dist/types/alpaca/streams/trading-stream.d.ts.map +0 -1
- package/dist/types/alpaca/streams.d.ts +0 -88
- package/dist/types/alpaca/streams.d.ts.map +0 -1
- package/dist/types/alpaca/test-imports.d.ts +0 -7
- package/dist/types/alpaca/test-imports.d.ts.map +0 -1
- package/dist/types/alpaca/trading/account.d.ts +0 -198
- package/dist/types/alpaca/trading/account.d.ts.map +0 -1
- package/dist/types/alpaca/trading/bracket-orders.d.ts +0 -162
- package/dist/types/alpaca/trading/bracket-orders.d.ts.map +0 -1
- package/dist/types/alpaca/trading/clock.d.ts +0 -99
- package/dist/types/alpaca/trading/clock.d.ts.map +0 -1
- package/dist/types/alpaca/trading/index.d.ts +0 -15
- package/dist/types/alpaca/trading/index.d.ts.map +0 -1
- package/dist/types/alpaca/trading/oco-orders.d.ts +0 -203
- package/dist/types/alpaca/trading/oco-orders.d.ts.map +0 -1
- package/dist/types/alpaca/trading/order-utils.d.ts +0 -404
- package/dist/types/alpaca/trading/order-utils.d.ts.map +0 -1
- package/dist/types/alpaca/trading/orders.d.ts +0 -199
- package/dist/types/alpaca/trading/orders.d.ts.map +0 -1
- package/dist/types/alpaca/trading/oto-orders.d.ts +0 -282
- package/dist/types/alpaca/trading/oto-orders.d.ts.map +0 -1
- package/dist/types/alpaca/trading/positions.d.ts +0 -389
- package/dist/types/alpaca/trading/positions.d.ts.map +0 -1
- package/dist/types/alpaca/trading/smart-orders.d.ts +0 -301
- package/dist/types/alpaca/trading/smart-orders.d.ts.map +0 -1
- package/dist/types/alpaca/trading/trailing-stops.d.ts +0 -240
- package/dist/types/alpaca/trading/trailing-stops.d.ts.map +0 -1
- package/dist/types/config/api-endpoints.d.ts +0 -94
- package/dist/types/config/api-endpoints.d.ts.map +0 -1
- package/dist/types/errors/index.d.ts +0 -130
- package/dist/types/errors/index.d.ts.map +0 -1
- package/dist/types/examples/rate-limiter-example.d.ts +0 -7
- package/dist/types/examples/rate-limiter-example.d.ts.map +0 -1
- package/dist/types/http-timeout.d.ts +0 -37
- package/dist/types/http-timeout.d.ts.map +0 -1
- package/dist/types/logger.d.ts +0 -56
- package/dist/types/logger.d.ts.map +0 -1
- package/dist/types/rate-limiter.d.ts +0 -171
- package/dist/types/rate-limiter.d.ts.map +0 -1
- package/dist/types/schemas/alpaca-schemas.d.ts +0 -779
- package/dist/types/schemas/alpaca-schemas.d.ts.map +0 -1
- package/dist/types/schemas/alphavantage-schemas.d.ts +0 -255
- package/dist/types/schemas/alphavantage-schemas.d.ts.map +0 -1
- package/dist/types/schemas/index.d.ts +0 -21
- package/dist/types/schemas/index.d.ts.map +0 -1
- package/dist/types/schemas/polygon-schemas.d.ts +0 -551
- package/dist/types/schemas/polygon-schemas.d.ts.map +0 -1
- package/dist/types/schemas/validate-response.d.ts +0 -88
- package/dist/types/schemas/validate-response.d.ts.map +0 -1
- package/dist/types/utils/auth-validator.d.ts +0 -32
- package/dist/types/utils/auth-validator.d.ts.map +0 -1
- package/dist/types/utils/http-keep-alive.d.ts +0 -110
- package/dist/types/utils/http-keep-alive.d.ts.map +0 -1
- package/dist/types/utils/paginator.d.ts +0 -154
- package/dist/types/utils/paginator.d.ts.map +0 -1
- package/dist/types/utils/retry.d.ts +0 -78
- package/dist/types/utils/retry.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,22 +513,20 @@ class DisplayManager {
|
|
|
513
513
|
cursorTo(process.stdout, 0);
|
|
514
514
|
// Format the timestamp
|
|
515
515
|
const date = new Date();
|
|
516
|
-
const timestamp = date.toLocaleString(
|
|
517
|
-
timeZone: "America/New_York",
|
|
518
|
-
});
|
|
516
|
+
const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
519
517
|
const account = options?.account;
|
|
520
518
|
const symbol = options?.symbol;
|
|
521
519
|
// Build the log message
|
|
522
|
-
let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` :
|
|
520
|
+
let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
|
|
523
521
|
// Add color based on type
|
|
524
|
-
if (options?.type ===
|
|
522
|
+
if (options?.type === 'error') {
|
|
525
523
|
logMessage = chalk.red(logMessage);
|
|
526
524
|
}
|
|
527
|
-
else if (options?.type ===
|
|
525
|
+
else if (options?.type === 'warn') {
|
|
528
526
|
logMessage = chalk.yellow(logMessage);
|
|
529
527
|
}
|
|
530
528
|
// Write the log message
|
|
531
|
-
process.stdout.write(logMessage +
|
|
529
|
+
process.stdout.write(logMessage + '\n');
|
|
532
530
|
// Log to file
|
|
533
531
|
if (symbol) {
|
|
534
532
|
// Log to symbol-specific file if symbol is provided
|
|
@@ -547,20 +545,20 @@ class DisplayManager {
|
|
|
547
545
|
writeSymbolLog(symbol, date, logMessage, options) {
|
|
548
546
|
try {
|
|
549
547
|
// Create logs directory if it doesn't exist
|
|
550
|
-
if (!fs.existsSync(
|
|
551
|
-
fs.mkdirSync(
|
|
548
|
+
if (!fs.existsSync('logs')) {
|
|
549
|
+
fs.mkdirSync('logs', { recursive: true });
|
|
552
550
|
}
|
|
553
551
|
// Format date for filename: YYYY-MM-DD
|
|
554
552
|
const year = date.getFullYear();
|
|
555
|
-
const month = String(date.getMonth() + 1).padStart(2,
|
|
556
|
-
const day = String(date.getDate()).padStart(2,
|
|
553
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
554
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
557
555
|
// Create filename: SYM-YYYY-MM-DD.log
|
|
558
556
|
const filename = `${symbol}-${year}-${month}-${day}.log`;
|
|
559
|
-
const filePath = path.join(
|
|
557
|
+
const filePath = path.join('logs', filename);
|
|
560
558
|
// Strip ANSI color codes from log message
|
|
561
|
-
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g,
|
|
559
|
+
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, '');
|
|
562
560
|
// Write to file (append if exists, create if not)
|
|
563
|
-
fs.appendFileSync(filePath, plainLogMessage +
|
|
561
|
+
fs.appendFileSync(filePath, plainLogMessage + '\n');
|
|
564
562
|
}
|
|
565
563
|
catch (error) {
|
|
566
564
|
// Only log to console - don't try to log to file again to avoid potential infinite loop
|
|
@@ -573,21 +571,21 @@ class DisplayManager {
|
|
|
573
571
|
writeGenericLog(date, logMessage, options) {
|
|
574
572
|
try {
|
|
575
573
|
// Create logs directory if it doesn't exist
|
|
576
|
-
if (!fs.existsSync(
|
|
577
|
-
fs.mkdirSync(
|
|
574
|
+
if (!fs.existsSync('logs')) {
|
|
575
|
+
fs.mkdirSync('logs', { recursive: true });
|
|
578
576
|
}
|
|
579
577
|
// Format date for filename: YYYY-MM-DD
|
|
580
578
|
const year = date.getFullYear();
|
|
581
|
-
const month = String(date.getMonth() + 1).padStart(2,
|
|
582
|
-
const day = String(date.getDate()).padStart(2,
|
|
579
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
580
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
583
581
|
// Create filename: system-YYYY-MM-DD.log
|
|
584
|
-
const source = options?.source?.toLowerCase().replace(/\s+/g,
|
|
582
|
+
const source = options?.source?.toLowerCase().replace(/\s+/g, '-') || 'system';
|
|
585
583
|
const filename = `${source}-${year}-${month}-${day}.log`;
|
|
586
|
-
const filePath = path.join(
|
|
584
|
+
const filePath = path.join('logs', filename);
|
|
587
585
|
// Strip ANSI color codes from log message
|
|
588
|
-
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g,
|
|
586
|
+
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, '');
|
|
589
587
|
// Write to file (append if exists, create if not)
|
|
590
|
-
fs.appendFileSync(filePath, plainLogMessage +
|
|
588
|
+
fs.appendFileSync(filePath, plainLogMessage + '\n');
|
|
591
589
|
}
|
|
592
590
|
catch (error) {
|
|
593
591
|
// Only log to console - don't try to log to file again to avoid potential infinite loop
|
|
@@ -615,206 +613,112 @@ class DisplayManager {
|
|
|
615
613
|
* @param options.symbol The trading symbol associated with this log.
|
|
616
614
|
* @param options.logToFile Force logging to a file even when no symbol is provided.
|
|
617
615
|
*/
|
|
618
|
-
function log$2(message, options = { source:
|
|
616
|
+
function log$2(message, options = { source: 'Server', type: 'info' }) {
|
|
619
617
|
const displayManager = DisplayManager.getInstance();
|
|
620
618
|
displayManager.log(message, options);
|
|
621
619
|
}
|
|
622
620
|
|
|
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
|
-
|
|
697
621
|
// market-hours.ts
|
|
698
622
|
const marketHolidays = {
|
|
699
623
|
2024: {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
624
|
+
'New Year\'s Day': { date: '2024-01-01' },
|
|
625
|
+
'Martin Luther King, Jr. Day': { date: '2024-01-15' },
|
|
626
|
+
'Washington\'s Birthday': { date: '2024-02-19' },
|
|
627
|
+
'Good Friday': { date: '2024-03-29' },
|
|
628
|
+
'Memorial Day': { date: '2024-05-27' },
|
|
629
|
+
'Juneteenth National Independence Day': { date: '2024-06-19' },
|
|
630
|
+
'Independence Day': { date: '2024-07-04' },
|
|
631
|
+
'Labor Day': { date: '2024-09-02' },
|
|
632
|
+
'Thanksgiving Day': { date: '2024-11-28' },
|
|
633
|
+
'Christmas Day': { date: '2024-12-25' }
|
|
710
634
|
},
|
|
711
635
|
2025: {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
636
|
+
'New Year\'s Day': { date: '2025-01-01' },
|
|
637
|
+
'Jimmy Carter Memorial Day': { date: '2025-01-09' },
|
|
638
|
+
'Martin Luther King, Jr. Day': { date: '2025-01-20' },
|
|
639
|
+
'Washington\'s Birthday': { date: '2025-02-17' },
|
|
640
|
+
'Good Friday': { date: '2025-04-18' },
|
|
641
|
+
'Memorial Day': { date: '2025-05-26' },
|
|
642
|
+
'Juneteenth National Independence Day': { date: '2025-06-19' },
|
|
643
|
+
'Independence Day': { date: '2025-07-04' },
|
|
644
|
+
'Labor Day': { date: '2025-09-01' },
|
|
645
|
+
'Thanksgiving Day': { date: '2025-11-27' },
|
|
646
|
+
'Christmas Day': { date: '2025-12-25' }
|
|
723
647
|
},
|
|
724
648
|
2026: {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
},
|
|
649
|
+
'New Year\'s Day': { date: '2026-01-01' },
|
|
650
|
+
'Martin Luther King, Jr. Day': { date: '2026-01-19' },
|
|
651
|
+
'Washington\'s Birthday': { date: '2026-02-16' },
|
|
652
|
+
'Good Friday': { date: '2026-04-03' },
|
|
653
|
+
'Memorial Day': { date: '2026-05-25' },
|
|
654
|
+
'Juneteenth National Independence Day': { date: '2026-06-19' },
|
|
655
|
+
'Independence Day': { date: '2026-07-03' },
|
|
656
|
+
'Labor Day': { date: '2026-09-07' },
|
|
657
|
+
'Thanksgiving Day': { date: '2026-11-26' },
|
|
658
|
+
'Christmas Day': { date: '2026-12-25' }
|
|
659
|
+
}
|
|
748
660
|
};
|
|
749
661
|
const marketEarlyCloses = {
|
|
750
662
|
2024: {
|
|
751
|
-
|
|
752
|
-
date:
|
|
753
|
-
time:
|
|
754
|
-
optionsTime:
|
|
755
|
-
notes:
|
|
756
|
-
},
|
|
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.",
|
|
663
|
+
'2024-07-03': {
|
|
664
|
+
date: '2024-07-03',
|
|
665
|
+
time: '13:00',
|
|
666
|
+
optionsTime: '13:15',
|
|
667
|
+
notes: 'Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
762
668
|
},
|
|
763
|
-
|
|
764
|
-
date:
|
|
765
|
-
time:
|
|
766
|
-
optionsTime:
|
|
767
|
-
notes:
|
|
669
|
+
'2024-11-29': {
|
|
670
|
+
date: '2024-11-29',
|
|
671
|
+
time: '13:00',
|
|
672
|
+
optionsTime: '13:15',
|
|
673
|
+
notes: 'Market closes early on Friday, November 29, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
768
674
|
},
|
|
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
|
+
}
|
|
769
681
|
},
|
|
770
682
|
2025: {
|
|
771
|
-
|
|
772
|
-
date:
|
|
773
|
-
time:
|
|
774
|
-
optionsTime:
|
|
775
|
-
notes:
|
|
776
|
-
},
|
|
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.",
|
|
683
|
+
'2025-07-03': {
|
|
684
|
+
date: '2025-07-03',
|
|
685
|
+
time: '13:00',
|
|
686
|
+
optionsTime: '13:15',
|
|
687
|
+
notes: 'Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
782
688
|
},
|
|
783
|
-
|
|
784
|
-
date:
|
|
785
|
-
time:
|
|
786
|
-
optionsTime:
|
|
787
|
-
notes:
|
|
689
|
+
'2025-11-28': {
|
|
690
|
+
date: '2025-11-28',
|
|
691
|
+
time: '13:00',
|
|
692
|
+
optionsTime: '13:15',
|
|
693
|
+
notes: 'Market closes early on Friday, November 28, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
788
694
|
},
|
|
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
|
+
}
|
|
789
701
|
},
|
|
790
702
|
2026: {
|
|
791
|
-
|
|
792
|
-
date:
|
|
793
|
-
time:
|
|
794
|
-
optionsTime:
|
|
795
|
-
notes:
|
|
796
|
-
},
|
|
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.",
|
|
802
|
-
},
|
|
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.",
|
|
703
|
+
'2026-07-02': {
|
|
704
|
+
date: '2026-07-02',
|
|
705
|
+
time: '13:00',
|
|
706
|
+
optionsTime: '13:15',
|
|
707
|
+
notes: 'Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
808
708
|
},
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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.",
|
|
709
|
+
'2026-11-27': {
|
|
710
|
+
date: '2026-11-27',
|
|
711
|
+
time: '13:00',
|
|
712
|
+
optionsTime: '13:15',
|
|
713
|
+
notes: 'Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
816
714
|
},
|
|
817
|
-
|
|
715
|
+
'2026-12-24': {
|
|
716
|
+
date: '2026-12-24',
|
|
717
|
+
time: '13:00',
|
|
718
|
+
optionsTime: '13:15',
|
|
719
|
+
notes: 'Market closes early on Thursday, December 24, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
720
|
+
}
|
|
721
|
+
}
|
|
818
722
|
};
|
|
819
723
|
|
|
820
724
|
// market-time.ts
|
|
@@ -827,15 +731,9 @@ const marketEarlyCloses = {
|
|
|
827
731
|
* Early extended market hours are 1:00pm-5:00pm on early close days
|
|
828
732
|
*/
|
|
829
733
|
const MARKET_TIMES = {
|
|
830
|
-
TIMEZONE:
|
|
831
|
-
REGULAR: {
|
|
832
|
-
|
|
833
|
-
END: { HOUR: 16, MINUTE: 0},
|
|
834
|
-
},
|
|
835
|
-
EXTENDED: {
|
|
836
|
-
START: { HOUR: 4, MINUTE: 0},
|
|
837
|
-
END: { HOUR: 20, MINUTE: 0},
|
|
838
|
-
},
|
|
734
|
+
TIMEZONE: 'America/New_York',
|
|
735
|
+
REGULAR: { START: { HOUR: 9, MINUTE: 30}, END: { HOUR: 16, MINUTE: 0} },
|
|
736
|
+
EXTENDED: { START: { HOUR: 4, MINUTE: 0}, END: { HOUR: 20, MINUTE: 0} },
|
|
839
737
|
};
|
|
840
738
|
/**
|
|
841
739
|
* Utility class for handling market time-related operations
|
|
@@ -848,7 +746,7 @@ class MarketTimeUtil {
|
|
|
848
746
|
* @param {string} [timezone='America/New_York'] - The timezone to use for market time calculations
|
|
849
747
|
* @param {IntradayReporting} [intradayReporting='market_hours'] - The intraday reporting mode
|
|
850
748
|
*/
|
|
851
|
-
constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting =
|
|
749
|
+
constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting = 'market_hours') {
|
|
852
750
|
this.validateTimezone(timezone);
|
|
853
751
|
this.timezone = timezone;
|
|
854
752
|
this.intradayReporting = intradayReporting;
|
|
@@ -867,37 +765,25 @@ class MarketTimeUtil {
|
|
|
867
765
|
throw new Error(`Invalid timezone: ${timezone}`);
|
|
868
766
|
}
|
|
869
767
|
}
|
|
870
|
-
formatDate(date, outputFormat =
|
|
768
|
+
formatDate(date, outputFormat = 'iso') {
|
|
871
769
|
switch (outputFormat) {
|
|
872
|
-
case
|
|
770
|
+
case 'unix-seconds':
|
|
873
771
|
return Math.floor(date.getTime() / 1000);
|
|
874
|
-
case
|
|
772
|
+
case 'unix-ms':
|
|
875
773
|
return date.getTime();
|
|
876
|
-
case
|
|
774
|
+
case 'iso':
|
|
877
775
|
default:
|
|
878
776
|
// return with timezone offset
|
|
879
777
|
return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
880
778
|
}
|
|
881
779
|
}
|
|
882
|
-
|
|
883
|
-
|
|
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();
|
|
780
|
+
isWeekend(date) {
|
|
781
|
+
const day = date.getDay();
|
|
890
782
|
return day === 0 || day === 6;
|
|
891
783
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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()];
|
|
784
|
+
isHoliday(date) {
|
|
785
|
+
const formattedDate = format(date, 'yyyy-MM-dd');
|
|
786
|
+
const yearHolidays = marketHolidays[date.getFullYear()];
|
|
901
787
|
for (const holiday in yearHolidays) {
|
|
902
788
|
if (yearHolidays[holiday].date === formattedDate) {
|
|
903
789
|
return true;
|
|
@@ -905,98 +791,50 @@ class MarketTimeUtil {
|
|
|
905
791
|
}
|
|
906
792
|
return false;
|
|
907
793
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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()];
|
|
794
|
+
isEarlyCloseDay(date) {
|
|
795
|
+
const formattedDate = format(date, 'yyyy-MM-dd');
|
|
796
|
+
const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
|
|
917
797
|
return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
|
|
918
798
|
}
|
|
919
799
|
/**
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
* @
|
|
923
|
-
* @returns The early close time in minutes from midnight, or null if not an early close day
|
|
800
|
+
* Get the early close time for a given date
|
|
801
|
+
* @param date - The date to get the early close time for
|
|
802
|
+
* @returns The early close time in minutes from midnight, or null if there is no early close
|
|
924
803
|
*/
|
|
925
|
-
|
|
926
|
-
const formattedDate = format(
|
|
927
|
-
const yearEarlyCloses = marketEarlyCloses[
|
|
804
|
+
getEarlyCloseTime(date) {
|
|
805
|
+
const formattedDate = format(date, 'yyyy-MM-dd');
|
|
806
|
+
const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
|
|
928
807
|
if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
|
|
929
|
-
const [hours, minutes] = yearEarlyCloses[formattedDate].time
|
|
930
|
-
.split(":")
|
|
931
|
-
.map(Number);
|
|
808
|
+
const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(':').map(Number);
|
|
932
809
|
return hours * 60 + minutes;
|
|
933
810
|
}
|
|
934
811
|
return null;
|
|
935
812
|
}
|
|
936
813
|
/**
|
|
937
|
-
*
|
|
938
|
-
*
|
|
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)
|
|
814
|
+
* Check if a given date is a market day
|
|
815
|
+
* @param date - The date to check
|
|
969
816
|
* @returns true if the date is a market day, false otherwise
|
|
970
817
|
*/
|
|
971
818
|
isMarketDay(date) {
|
|
972
|
-
const
|
|
973
|
-
|
|
819
|
+
const isWeekendDay = this.isWeekend(date);
|
|
820
|
+
const isHolidayDay = this.isHoliday(date);
|
|
821
|
+
const returner = !isWeekendDay && !isHolidayDay;
|
|
822
|
+
return returner;
|
|
974
823
|
}
|
|
975
824
|
/**
|
|
976
|
-
* Check if a given date is within market hours
|
|
977
|
-
*
|
|
978
|
-
* @param date - The date to check (any timezone)
|
|
825
|
+
* Check if a given date is within market hours
|
|
826
|
+
* @param date - The date to check
|
|
979
827
|
* @returns true if the date is within market hours, false otherwise
|
|
980
828
|
*/
|
|
981
829
|
isWithinMarketHours(date) {
|
|
982
|
-
|
|
983
|
-
|
|
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)) {
|
|
830
|
+
// Check for holidays first
|
|
831
|
+
if (this.isHoliday(date)) {
|
|
994
832
|
return false;
|
|
995
833
|
}
|
|
996
|
-
const timeInMinutes =
|
|
834
|
+
const timeInMinutes = date.getHours() * 60 + date.getMinutes();
|
|
997
835
|
// Check for early closure
|
|
998
|
-
if (this.
|
|
999
|
-
const earlyCloseMinutes = this.
|
|
836
|
+
if (this.isEarlyCloseDay(date)) {
|
|
837
|
+
const earlyCloseMinutes = this.getEarlyCloseTime(date);
|
|
1000
838
|
if (earlyCloseMinutes !== null && timeInMinutes > earlyCloseMinutes) {
|
|
1001
839
|
return false;
|
|
1002
840
|
}
|
|
@@ -1004,50 +842,38 @@ class MarketTimeUtil {
|
|
|
1004
842
|
// Regular market hours logic
|
|
1005
843
|
let returner;
|
|
1006
844
|
switch (this.intradayReporting) {
|
|
1007
|
-
case
|
|
1008
|
-
const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
1009
|
-
|
|
1010
|
-
const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 +
|
|
1011
|
-
MARKET_TIMES.EXTENDED.END.MINUTE;
|
|
845
|
+
case 'extended_hours': {
|
|
846
|
+
const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 + MARKET_TIMES.EXTENDED.START.MINUTE;
|
|
847
|
+
const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
|
|
1012
848
|
// Comprehensive handling of times crossing midnight
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
const adjustedTimeInMinutes = adjustedNyDate.getHours() * 60 + adjustedNyDate.getMinutes();
|
|
1017
|
-
returner =
|
|
1018
|
-
adjustedTimeInMinutes >= extendedStartMinutes &&
|
|
1019
|
-
adjustedTimeInMinutes <= extendedEndMinutes;
|
|
849
|
+
const adjustedDate = timeInMinutes < extendedStartMinutes ? sub(date, { days: 1 }) : date;
|
|
850
|
+
const adjustedTimeInMinutes = adjustedDate.getHours() * 60 + adjustedDate.getMinutes();
|
|
851
|
+
returner = adjustedTimeInMinutes >= extendedStartMinutes && adjustedTimeInMinutes <= extendedEndMinutes;
|
|
1020
852
|
break;
|
|
1021
853
|
}
|
|
1022
|
-
case
|
|
854
|
+
case 'continuous':
|
|
1023
855
|
returner = true;
|
|
1024
856
|
break;
|
|
1025
857
|
default: {
|
|
1026
858
|
// market_hours
|
|
1027
|
-
const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 +
|
|
1028
|
-
MARKET_TIMES.REGULAR.START.MINUTE;
|
|
859
|
+
const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
|
|
1029
860
|
const regularEndMinutes = MARKET_TIMES.REGULAR.END.HOUR * 60 + MARKET_TIMES.REGULAR.END.MINUTE;
|
|
1030
|
-
returner =
|
|
1031
|
-
timeInMinutes >= regularStartMinutes &&
|
|
1032
|
-
timeInMinutes <= regularEndMinutes;
|
|
861
|
+
returner = timeInMinutes >= regularStartMinutes && timeInMinutes <= regularEndMinutes;
|
|
1033
862
|
break;
|
|
1034
863
|
}
|
|
1035
864
|
}
|
|
1036
865
|
return returner;
|
|
1037
866
|
}
|
|
1038
867
|
/**
|
|
1039
|
-
* Check if a
|
|
1040
|
-
*
|
|
1041
|
-
* @param nyDate - Date in market timezone representation
|
|
868
|
+
* Check if a given date is before market hours
|
|
869
|
+
* @param date - The date to check
|
|
1042
870
|
* @returns true if the date is before market hours, false otherwise
|
|
1043
871
|
*/
|
|
1044
|
-
|
|
1045
|
-
const timeInMinutes =
|
|
1046
|
-
const startMinutes = this.intradayReporting ===
|
|
1047
|
-
? MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
1048
|
-
|
|
1049
|
-
: MARKET_TIMES.REGULAR.START.HOUR * 60 +
|
|
1050
|
-
MARKET_TIMES.REGULAR.START.MINUTE;
|
|
872
|
+
isBeforeMarketHours(date) {
|
|
873
|
+
const timeInMinutes = date.getHours() * 60 + date.getMinutes();
|
|
874
|
+
const startMinutes = this.intradayReporting === 'extended_hours'
|
|
875
|
+
? MARKET_TIMES.EXTENDED.START.HOUR * 60 + MARKET_TIMES.EXTENDED.START.MINUTE
|
|
876
|
+
: MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
|
|
1051
877
|
return timeInMinutes < startMinutes;
|
|
1052
878
|
}
|
|
1053
879
|
/**
|
|
@@ -1057,7 +883,7 @@ class MarketTimeUtil {
|
|
|
1057
883
|
*/
|
|
1058
884
|
getLastTradingDate(currentDate = new Date()) {
|
|
1059
885
|
const nowET = toZonedTime(currentDate, this.timezone);
|
|
1060
|
-
const isMarketDayToday = this.
|
|
886
|
+
const isMarketDayToday = this.isMarketDay(nowET);
|
|
1061
887
|
const currentMinutes = nowET.getHours() * 60 + nowET.getMinutes();
|
|
1062
888
|
const marketOpenMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
|
|
1063
889
|
if (isMarketDayToday && currentMinutes >= marketOpenMinutes) {
|
|
@@ -1067,7 +893,7 @@ class MarketTimeUtil {
|
|
|
1067
893
|
else {
|
|
1068
894
|
// Before market open, or not a market day, return previous trading day
|
|
1069
895
|
let lastTradingDate = sub(nowET, { days: 1 });
|
|
1070
|
-
while (!this.
|
|
896
|
+
while (!this.isMarketDay(lastTradingDate)) {
|
|
1071
897
|
lastTradingDate = sub(lastTradingDate, { days: 1 });
|
|
1072
898
|
}
|
|
1073
899
|
return lastTradingDate;
|
|
@@ -1075,7 +901,7 @@ class MarketTimeUtil {
|
|
|
1075
901
|
}
|
|
1076
902
|
getLastMarketDay(date) {
|
|
1077
903
|
let currentDate = sub(date, { days: 1 });
|
|
1078
|
-
while (!this.
|
|
904
|
+
while (!this.isMarketDay(currentDate)) {
|
|
1079
905
|
currentDate = sub(currentDate, { days: 1 });
|
|
1080
906
|
}
|
|
1081
907
|
return currentDate;
|
|
@@ -1084,7 +910,7 @@ class MarketTimeUtil {
|
|
|
1084
910
|
const nowET = toZonedTime(currentDate, this.timezone);
|
|
1085
911
|
// If today is a market day and we're after extended hours close
|
|
1086
912
|
// then return today since it's a completed trading day
|
|
1087
|
-
if (this.
|
|
913
|
+
if (this.isMarketDay(nowET)) {
|
|
1088
914
|
const timeInMinutes = nowET.getHours() * 60 + nowET.getMinutes();
|
|
1089
915
|
const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
|
|
1090
916
|
// Check if we're after market close (including extended hours)
|
|
@@ -1108,33 +934,18 @@ class MarketTimeUtil {
|
|
|
1108
934
|
* @property {string} yyyymmdd - The date in YYYY-MM-DD format
|
|
1109
935
|
* @property {string} dateISOString - Full ISO date string
|
|
1110
936
|
*/
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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)) {
|
|
937
|
+
getNextMarketDay(date) {
|
|
938
|
+
let currentDate = add(date, { days: 1 });
|
|
939
|
+
while (!this.isMarketDay(currentDate)) {
|
|
1119
940
|
currentDate = add(currentDate, { days: 1 });
|
|
1120
941
|
}
|
|
1121
942
|
return currentDate;
|
|
1122
943
|
}
|
|
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
|
-
}
|
|
1133
944
|
getDayBoundaries(date) {
|
|
1134
945
|
let start;
|
|
1135
946
|
let end;
|
|
1136
947
|
switch (this.intradayReporting) {
|
|
1137
|
-
case
|
|
948
|
+
case 'extended_hours': {
|
|
1138
949
|
start = set(date, {
|
|
1139
950
|
hours: MARKET_TIMES.EXTENDED.START.HOUR,
|
|
1140
951
|
minutes: MARKET_TIMES.EXTENDED.START.MINUTE,
|
|
@@ -1149,7 +960,7 @@ class MarketTimeUtil {
|
|
|
1149
960
|
});
|
|
1150
961
|
break;
|
|
1151
962
|
}
|
|
1152
|
-
case
|
|
963
|
+
case 'continuous': {
|
|
1153
964
|
start = startOfDay(date);
|
|
1154
965
|
end = endOfDay(date);
|
|
1155
966
|
break;
|
|
@@ -1162,9 +973,9 @@ class MarketTimeUtil {
|
|
|
1162
973
|
seconds: 0,
|
|
1163
974
|
milliseconds: 0,
|
|
1164
975
|
});
|
|
1165
|
-
// Check for early close
|
|
1166
|
-
if (this.
|
|
1167
|
-
const earlyCloseMinutes = this.
|
|
976
|
+
// Check for early close
|
|
977
|
+
if (this.isEarlyCloseDay(date)) {
|
|
978
|
+
const earlyCloseMinutes = this.getEarlyCloseTime(date);
|
|
1168
979
|
if (earlyCloseMinutes !== null) {
|
|
1169
980
|
const earlyCloseHours = Math.floor(earlyCloseMinutes / 60);
|
|
1170
981
|
const earlyCloseMinutesRemainder = earlyCloseMinutes % 60;
|
|
@@ -1191,44 +1002,44 @@ class MarketTimeUtil {
|
|
|
1191
1002
|
calculatePeriodStartDate(endDate, period) {
|
|
1192
1003
|
let startDate;
|
|
1193
1004
|
switch (period) {
|
|
1194
|
-
case
|
|
1005
|
+
case 'YTD':
|
|
1195
1006
|
startDate = set(endDate, { month: 0, date: 1 });
|
|
1196
1007
|
break;
|
|
1197
|
-
case
|
|
1008
|
+
case '1D':
|
|
1198
1009
|
startDate = this.getLastMarketDay(endDate);
|
|
1199
1010
|
break;
|
|
1200
|
-
case
|
|
1011
|
+
case '3D':
|
|
1201
1012
|
startDate = sub(endDate, { days: 3 });
|
|
1202
1013
|
break;
|
|
1203
|
-
case
|
|
1014
|
+
case '1W':
|
|
1204
1015
|
startDate = sub(endDate, { weeks: 1 });
|
|
1205
1016
|
break;
|
|
1206
|
-
case
|
|
1017
|
+
case '2W':
|
|
1207
1018
|
startDate = sub(endDate, { weeks: 2 });
|
|
1208
1019
|
break;
|
|
1209
|
-
case
|
|
1020
|
+
case '1M':
|
|
1210
1021
|
startDate = sub(endDate, { months: 1 });
|
|
1211
1022
|
break;
|
|
1212
|
-
case
|
|
1023
|
+
case '3M':
|
|
1213
1024
|
startDate = sub(endDate, { months: 3 });
|
|
1214
1025
|
break;
|
|
1215
|
-
case
|
|
1026
|
+
case '6M':
|
|
1216
1027
|
startDate = sub(endDate, { months: 6 });
|
|
1217
1028
|
break;
|
|
1218
|
-
case
|
|
1029
|
+
case '1Y':
|
|
1219
1030
|
startDate = sub(endDate, { years: 1 });
|
|
1220
1031
|
break;
|
|
1221
1032
|
default:
|
|
1222
1033
|
throw new Error(`Invalid period: ${period}`);
|
|
1223
1034
|
}
|
|
1224
|
-
while (!this.
|
|
1225
|
-
startDate = this.
|
|
1035
|
+
while (!this.isMarketDay(startDate)) {
|
|
1036
|
+
startDate = this.getNextMarketDay(startDate);
|
|
1226
1037
|
}
|
|
1227
1038
|
return startDate;
|
|
1228
1039
|
}
|
|
1229
|
-
getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat =
|
|
1040
|
+
getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat = 'iso', }) {
|
|
1230
1041
|
if (!period) {
|
|
1231
|
-
throw new Error(
|
|
1042
|
+
throw new Error('Period is required');
|
|
1232
1043
|
}
|
|
1233
1044
|
if (intraday_reporting) {
|
|
1234
1045
|
this.intradayReporting = intraday_reporting;
|
|
@@ -1237,9 +1048,9 @@ class MarketTimeUtil {
|
|
|
1237
1048
|
const zonedEndDate = toZonedTime(end, this.timezone);
|
|
1238
1049
|
let startDate;
|
|
1239
1050
|
let endDate;
|
|
1240
|
-
const isCurrentMarketDay = this.
|
|
1241
|
-
const isWithinHours = this.
|
|
1242
|
-
const isBeforeHours = this.
|
|
1051
|
+
const isCurrentMarketDay = this.isMarketDay(zonedEndDate);
|
|
1052
|
+
const isWithinHours = this.isWithinMarketHours(zonedEndDate);
|
|
1053
|
+
const isBeforeHours = this.isBeforeMarketHours(zonedEndDate);
|
|
1243
1054
|
// First determine the end date based on current market conditions
|
|
1244
1055
|
if (isCurrentMarketDay) {
|
|
1245
1056
|
if (isBeforeHours) {
|
|
@@ -1273,7 +1084,7 @@ class MarketTimeUtil {
|
|
|
1273
1084
|
const utcEnd = fromZonedTime(endDate, this.timezone);
|
|
1274
1085
|
// Ensure start is not after end
|
|
1275
1086
|
if (isBefore(utcEnd, utcStart)) {
|
|
1276
|
-
throw new Error(
|
|
1087
|
+
throw new Error('Start date cannot be after end date');
|
|
1277
1088
|
}
|
|
1278
1089
|
return {
|
|
1279
1090
|
start: this.formatDate(utcStart, outputFormat),
|
|
@@ -1284,7 +1095,7 @@ class MarketTimeUtil {
|
|
|
1284
1095
|
const { date = new Date() } = options;
|
|
1285
1096
|
const zonedDate = toZonedTime(date, this.timezone);
|
|
1286
1097
|
// Check if market is closed for the day
|
|
1287
|
-
if (this.
|
|
1098
|
+
if (this.isWeekend(zonedDate) || this.isHoliday(zonedDate)) {
|
|
1288
1099
|
return {
|
|
1289
1100
|
marketOpen: false,
|
|
1290
1101
|
open: null,
|
|
@@ -1298,10 +1109,10 @@ class MarketTimeUtil {
|
|
|
1298
1109
|
let regularCloseTime = MARKET_TIMES.REGULAR.END;
|
|
1299
1110
|
const extendedOpenTime = MARKET_TIMES.EXTENDED.START;
|
|
1300
1111
|
let extendedCloseTime = MARKET_TIMES.EXTENDED.END;
|
|
1301
|
-
// Check for early close
|
|
1302
|
-
const isEarlyClose = this.
|
|
1112
|
+
// Check for early close
|
|
1113
|
+
const isEarlyClose = this.isEarlyCloseDay(zonedDate);
|
|
1303
1114
|
if (isEarlyClose) {
|
|
1304
|
-
const earlyCloseMinutes = this.
|
|
1115
|
+
const earlyCloseMinutes = this.getEarlyCloseTime(zonedDate);
|
|
1305
1116
|
if (earlyCloseMinutes !== null) {
|
|
1306
1117
|
// For regular hours, use the early close time
|
|
1307
1118
|
regularCloseTime = {
|
|
@@ -1317,22 +1128,10 @@ class MarketTimeUtil {
|
|
|
1317
1128
|
};
|
|
1318
1129
|
}
|
|
1319
1130
|
}
|
|
1320
|
-
const open = fromZonedTime(set(dayStart, {
|
|
1321
|
-
|
|
1322
|
-
|
|
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);
|
|
1131
|
+
const open = fromZonedTime(set(dayStart, { hours: regularOpenTime.HOUR, minutes: regularOpenTime.MINUTE }), this.timezone);
|
|
1132
|
+
const close = fromZonedTime(set(dayStart, { hours: regularCloseTime.HOUR, minutes: regularCloseTime.MINUTE }), this.timezone);
|
|
1133
|
+
const openExt = fromZonedTime(set(dayStart, { hours: extendedOpenTime.HOUR, minutes: extendedOpenTime.MINUTE }), this.timezone);
|
|
1134
|
+
const closeExt = fromZonedTime(set(dayStart, { hours: extendedCloseTime.HOUR, minutes: extendedCloseTime.MINUTE }), this.timezone);
|
|
1336
1135
|
return {
|
|
1337
1136
|
marketOpen: true,
|
|
1338
1137
|
open,
|
|
@@ -1355,7 +1154,7 @@ function getLastFullTradingDate(currentDate = new Date()) {
|
|
|
1355
1154
|
// Format the date in NY timezone to ensure consistency
|
|
1356
1155
|
return {
|
|
1357
1156
|
date,
|
|
1358
|
-
YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE,
|
|
1157
|
+
YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE, 'yyyy-MM-dd'),
|
|
1359
1158
|
};
|
|
1360
1159
|
}
|
|
1361
1160
|
|
|
@@ -6329,82 +6128,13 @@ function requireWebsocketServer () {
|
|
|
6329
6128
|
|
|
6330
6129
|
requireWebsocketServer();
|
|
6331
6130
|
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
* Provides fast, synchronous validation of API credentials before making requests
|
|
6335
|
-
*/
|
|
6336
|
-
/**
|
|
6337
|
-
* Validates Alpaca API credentials
|
|
6338
|
-
* @param auth - Authentication object containing API key and secret
|
|
6339
|
-
* @param options - Validation options
|
|
6340
|
-
* @param options.throwOnMissing - If false, missing credentials will log a warning instead of throwing (default: true)
|
|
6341
|
-
* @throws {Error} If credentials are invalid (when throwOnMissing is true)
|
|
6342
|
-
* @returns {boolean} True if credentials are valid, false if missing (when throwOnMissing is false)
|
|
6343
|
-
*/
|
|
6344
|
-
function validateAlpacaCredentials(auth, options = { throwOnMissing: true }) {
|
|
6345
|
-
const { throwOnMissing = true } = options;
|
|
6346
|
-
// Check for missing or empty API key
|
|
6347
|
-
if (!auth.apiKey ||
|
|
6348
|
-
typeof auth.apiKey !== "string" ||
|
|
6349
|
-
auth.apiKey.trim().length === 0) {
|
|
6350
|
-
if (throwOnMissing) {
|
|
6351
|
-
throw new Error("Invalid Alpaca API key: must be a non-empty string");
|
|
6352
|
-
}
|
|
6353
|
-
console.warn("[AlpacaAPI] API key not configured. Market data features will be unavailable.");
|
|
6354
|
-
return false;
|
|
6355
|
-
}
|
|
6356
|
-
// Check for missing or empty API secret
|
|
6357
|
-
if (!auth.apiSecret ||
|
|
6358
|
-
typeof auth.apiSecret !== "string" ||
|
|
6359
|
-
auth.apiSecret.trim().length === 0) {
|
|
6360
|
-
if (throwOnMissing) {
|
|
6361
|
-
throw new Error("Invalid Alpaca API secret: must be a non-empty string");
|
|
6362
|
-
}
|
|
6363
|
-
console.warn("[AlpacaAPI] API secret not configured. Market data features will be unavailable.");
|
|
6364
|
-
return false;
|
|
6365
|
-
}
|
|
6366
|
-
// Alpaca keys are typically 20+ characters
|
|
6367
|
-
if (auth.apiKey.length < 10) {
|
|
6368
|
-
if (throwOnMissing) {
|
|
6369
|
-
throw new Error("Alpaca API key appears to be too short");
|
|
6370
|
-
}
|
|
6371
|
-
console.warn("[AlpacaAPI] API key appears to be too short.");
|
|
6372
|
-
return false;
|
|
6373
|
-
}
|
|
6374
|
-
return true;
|
|
6375
|
-
}
|
|
6376
|
-
|
|
6377
|
-
/**
|
|
6378
|
-
* HTTP request timeout utilities
|
|
6379
|
-
* Provides configurable timeout handling for external API calls
|
|
6380
|
-
*/
|
|
6381
|
-
/**
|
|
6382
|
-
* Default timeout values for different external APIs (in milliseconds)
|
|
6383
|
-
* Can be overridden via environment variables
|
|
6384
|
-
*/
|
|
6385
|
-
const DEFAULT_TIMEOUTS = {
|
|
6386
|
-
ALPACA_API: parseInt(process.env.ALPACA_API_TIMEOUT || "30000", 10),
|
|
6387
|
-
POLYGON_API: parseInt(process.env.POLYGON_API_TIMEOUT || "30000", 10),
|
|
6388
|
-
ALPHA_VANTAGE: parseInt(process.env.ALPHA_VANTAGE_API_TIMEOUT || "30000", 10),
|
|
6389
|
-
GENERAL: parseInt(process.env.HTTP_TIMEOUT || "30000", 10),
|
|
6390
|
-
};
|
|
6391
|
-
/**
|
|
6392
|
-
* Creates an AbortSignal that times out after the specified duration
|
|
6393
|
-
* Compatible with fetch API
|
|
6394
|
-
* @param ms - Timeout duration in milliseconds
|
|
6395
|
-
* @returns AbortSignal that will abort after the specified duration
|
|
6396
|
-
*/
|
|
6397
|
-
function createTimeoutSignal(ms) {
|
|
6398
|
-
return AbortSignal.timeout(ms);
|
|
6399
|
-
}
|
|
6400
|
-
|
|
6401
|
-
const log$1 = (message, options = { type: "info" }) => {
|
|
6402
|
-
log$2(message, { ...options, source: "AlpacaMarketDataAPI" });
|
|
6131
|
+
const log$1 = (message, options = { type: 'info' }) => {
|
|
6132
|
+
log$2(message, { ...options, source: 'AlpacaMarketDataAPI' });
|
|
6403
6133
|
};
|
|
6404
6134
|
// Default settings for market data API
|
|
6405
|
-
const DEFAULT_ADJUSTMENT =
|
|
6406
|
-
const DEFAULT_FEED =
|
|
6407
|
-
const DEFAULT_CURRENCY =
|
|
6135
|
+
const DEFAULT_ADJUSTMENT = 'all';
|
|
6136
|
+
const DEFAULT_FEED = 'sip';
|
|
6137
|
+
const DEFAULT_CURRENCY = 'USD';
|
|
6408
6138
|
/**
|
|
6409
6139
|
* Singleton class for interacting with Alpaca Market Data API
|
|
6410
6140
|
* Provides methods for fetching historical bars, latest bars, last trades, latest trades, latest quotes, and latest quote for a single symbol
|
|
@@ -6415,84 +6145,50 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6415
6145
|
dataURL;
|
|
6416
6146
|
apiURL;
|
|
6417
6147
|
v1beta1url;
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
stockStreamUrl = getStockStreamUrl("PRODUCTION"); // production values
|
|
6421
|
-
optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // production values
|
|
6422
|
-
cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // production values
|
|
6148
|
+
stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip'; // production values
|
|
6149
|
+
optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // production values
|
|
6423
6150
|
stockWs = null;
|
|
6424
6151
|
optionWs = null;
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
optionSubscriptions = {
|
|
6432
|
-
trades: [],
|
|
6433
|
-
quotes: [],
|
|
6434
|
-
bars: [],
|
|
6435
|
-
};
|
|
6436
|
-
cryptoSubscriptions = {
|
|
6437
|
-
trades: [],
|
|
6438
|
-
quotes: [],
|
|
6439
|
-
bars: [],
|
|
6440
|
-
};
|
|
6441
|
-
setMode(mode = "production") {
|
|
6442
|
-
if (mode === "sandbox") {
|
|
6443
|
-
// sandbox mode
|
|
6444
|
-
this.stockStreamUrl = WEBSOCKET_STREAMS.STOCKS.PRODUCTION; // sandbox uses production for stocks
|
|
6445
|
-
this.optionStreamUrl = getOptionsStreamUrl("SANDBOX");
|
|
6446
|
-
this.cryptoStreamUrl = getCryptoStreamUrl("SANDBOX");
|
|
6152
|
+
stockSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6153
|
+
optionSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6154
|
+
setMode(mode = 'production') {
|
|
6155
|
+
if (mode === 'sandbox') { // sandbox mode
|
|
6156
|
+
this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
|
|
6157
|
+
this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
|
|
6447
6158
|
}
|
|
6448
|
-
else if (mode ===
|
|
6449
|
-
|
|
6450
|
-
this.
|
|
6451
|
-
this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // there's no test mode for options
|
|
6452
|
-
this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // there's no test mode for crypto
|
|
6159
|
+
else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
|
|
6160
|
+
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
|
|
6161
|
+
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // there's no test mode for options
|
|
6453
6162
|
}
|
|
6454
|
-
else {
|
|
6455
|
-
|
|
6456
|
-
this.
|
|
6457
|
-
this.optionStreamUrl = getOptionsStreamUrl("PRODUCTION");
|
|
6458
|
-
this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION");
|
|
6163
|
+
else { // production
|
|
6164
|
+
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
|
|
6165
|
+
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
|
|
6459
6166
|
}
|
|
6460
6167
|
}
|
|
6461
6168
|
getMode() {
|
|
6462
|
-
if (this.stockStreamUrl.includes(
|
|
6463
|
-
return
|
|
6169
|
+
if (this.stockStreamUrl.includes('sandbox')) {
|
|
6170
|
+
return 'sandbox';
|
|
6464
6171
|
}
|
|
6465
|
-
else if (this.stockStreamUrl.includes(
|
|
6466
|
-
return
|
|
6172
|
+
else if (this.stockStreamUrl.includes('test')) {
|
|
6173
|
+
return 'test';
|
|
6467
6174
|
}
|
|
6468
6175
|
else {
|
|
6469
|
-
return
|
|
6176
|
+
return 'production';
|
|
6470
6177
|
}
|
|
6471
6178
|
}
|
|
6472
6179
|
constructor() {
|
|
6473
6180
|
super();
|
|
6474
|
-
|
|
6475
|
-
// Use throwOnMissing: false to allow initialization during build time
|
|
6476
|
-
// when env vars are not available. Features will be unavailable until
|
|
6477
|
-
// credentials are provided at runtime.
|
|
6478
|
-
const apiKey = process.env.ALPACA_API_KEY || "";
|
|
6479
|
-
const apiSecret = process.env.ALPACA_SECRET_KEY || "";
|
|
6480
|
-
this.credentialsValid = validateAlpacaCredentials({
|
|
6481
|
-
apiKey,
|
|
6482
|
-
apiSecret,
|
|
6483
|
-
isPaper: process.env.ALPACA_ACCOUNT_TYPE === "PAPER",
|
|
6484
|
-
}, { throwOnMissing: false });
|
|
6485
|
-
this.dataURL = MARKET_DATA_API.STOCKS;
|
|
6181
|
+
this.dataURL = 'https://data.alpaca.markets/v2';
|
|
6486
6182
|
this.apiURL =
|
|
6487
|
-
process.env.ALPACA_ACCOUNT_TYPE ===
|
|
6488
|
-
?
|
|
6489
|
-
:
|
|
6490
|
-
this.v1beta1url =
|
|
6491
|
-
this.setMode(
|
|
6183
|
+
process.env.ALPACA_ACCOUNT_TYPE === 'PAPER'
|
|
6184
|
+
? 'https://paper-api.alpaca.markets/v2'
|
|
6185
|
+
: 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
|
|
6186
|
+
this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
|
|
6187
|
+
this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
|
|
6492
6188
|
this.headers = {
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6189
|
+
'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
|
|
6190
|
+
'APCA-API-SECRET-KEY': process.env.ALPACA_SECRET_KEY,
|
|
6191
|
+
'Content-Type': 'application/json',
|
|
6496
6192
|
};
|
|
6497
6193
|
}
|
|
6498
6194
|
static getInstance() {
|
|
@@ -6508,105 +6204,57 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6508
6204
|
return super.emit(event, ...args);
|
|
6509
6205
|
}
|
|
6510
6206
|
connect(streamType) {
|
|
6511
|
-
|
|
6512
|
-
if (streamType === "stock") {
|
|
6513
|
-
url = this.stockStreamUrl;
|
|
6514
|
-
}
|
|
6515
|
-
else if (streamType === "option") {
|
|
6516
|
-
url = this.optionStreamUrl;
|
|
6517
|
-
}
|
|
6518
|
-
else {
|
|
6519
|
-
url = this.cryptoStreamUrl;
|
|
6520
|
-
}
|
|
6207
|
+
const url = streamType === 'stock' ? this.stockStreamUrl : this.optionStreamUrl;
|
|
6521
6208
|
const ws = new WebSocket(url);
|
|
6522
|
-
if (streamType ===
|
|
6209
|
+
if (streamType === 'stock') {
|
|
6523
6210
|
this.stockWs = ws;
|
|
6524
6211
|
}
|
|
6525
|
-
else if (streamType === "option") {
|
|
6526
|
-
this.optionWs = ws;
|
|
6527
|
-
}
|
|
6528
6212
|
else {
|
|
6529
|
-
this.
|
|
6213
|
+
this.optionWs = ws;
|
|
6530
6214
|
}
|
|
6531
|
-
ws.on(
|
|
6532
|
-
log$1(`${streamType} stream connected`, { type:
|
|
6215
|
+
ws.on('open', () => {
|
|
6216
|
+
log$1(`${streamType} stream connected`, { type: 'info' });
|
|
6533
6217
|
const authMessage = {
|
|
6534
|
-
action:
|
|
6218
|
+
action: 'auth',
|
|
6535
6219
|
key: process.env.ALPACA_API_KEY,
|
|
6536
6220
|
secret: process.env.ALPACA_SECRET_KEY,
|
|
6537
6221
|
};
|
|
6538
6222
|
ws.send(JSON.stringify(authMessage));
|
|
6539
6223
|
});
|
|
6540
|
-
ws.on(
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
try {
|
|
6544
|
-
messages = JSON.parse(rawData);
|
|
6545
|
-
}
|
|
6546
|
-
catch (e) {
|
|
6547
|
-
log$1(`${streamType} stream received invalid JSON: ${rawData.substring(0, 200)}`, { type: "error" });
|
|
6548
|
-
return;
|
|
6549
|
-
}
|
|
6224
|
+
ws.on('message', (data) => {
|
|
6225
|
+
//log(`RAW MESSASGE: ${data.toString()}`);
|
|
6226
|
+
const messages = JSON.parse(data.toString());
|
|
6550
6227
|
for (const message of messages) {
|
|
6551
|
-
if (message.T ===
|
|
6552
|
-
log$1(`${streamType} stream authenticated`, { type:
|
|
6228
|
+
if (message.T === 'success' && message.msg === 'authenticated') {
|
|
6229
|
+
log$1(`${streamType} stream authenticated`, { type: 'info' });
|
|
6553
6230
|
this.sendSubscription(streamType);
|
|
6554
6231
|
}
|
|
6555
|
-
else if (message.T ===
|
|
6556
|
-
log$1(`${streamType} stream
|
|
6557
|
-
type: "debug",
|
|
6558
|
-
});
|
|
6559
|
-
}
|
|
6560
|
-
else if (message.T === "subscription") {
|
|
6561
|
-
log$1(`${streamType} subscription confirmed: trades=${message.trades?.length || 0}, quotes=${message.quotes?.length || 0}, bars=${message.bars?.length || 0}`, { type: "info" });
|
|
6562
|
-
}
|
|
6563
|
-
else if (message.T === "error") {
|
|
6564
|
-
log$1(`${streamType} stream error: ${message.msg} (code: ${message.code}, raw: ${JSON.stringify(message)})`, { type: "error" });
|
|
6232
|
+
else if (message.T === 'error') {
|
|
6233
|
+
log$1(`${streamType} stream error: ${message.msg}`, { type: 'error' });
|
|
6565
6234
|
}
|
|
6566
6235
|
else if (message.S) {
|
|
6567
6236
|
super.emit(`${streamType}-${message.T}`, message);
|
|
6568
6237
|
super.emit(`${streamType}-data`, message);
|
|
6569
6238
|
}
|
|
6570
|
-
else {
|
|
6571
|
-
log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: "debug" });
|
|
6572
|
-
}
|
|
6573
6239
|
}
|
|
6574
6240
|
});
|
|
6575
|
-
ws.on(
|
|
6576
|
-
log$1(`${streamType} stream disconnected`, { type:
|
|
6577
|
-
if (streamType ===
|
|
6241
|
+
ws.on('close', () => {
|
|
6242
|
+
log$1(`${streamType} stream disconnected`, { type: 'warn' });
|
|
6243
|
+
if (streamType === 'stock') {
|
|
6578
6244
|
this.stockWs = null;
|
|
6579
6245
|
}
|
|
6580
|
-
else if (streamType === "option") {
|
|
6581
|
-
this.optionWs = null;
|
|
6582
|
-
}
|
|
6583
6246
|
else {
|
|
6584
|
-
this.
|
|
6247
|
+
this.optionWs = null;
|
|
6585
6248
|
}
|
|
6586
6249
|
// Optional: implement reconnect logic
|
|
6587
6250
|
});
|
|
6588
|
-
ws.on(
|
|
6589
|
-
log$1(`${streamType} stream error: ${error.message}`, { type:
|
|
6251
|
+
ws.on('error', (error) => {
|
|
6252
|
+
log$1(`${streamType} stream error: ${error.message}`, { type: 'error' });
|
|
6590
6253
|
});
|
|
6591
6254
|
}
|
|
6592
6255
|
sendSubscription(streamType) {
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
if (streamType === "stock") {
|
|
6596
|
-
ws = this.stockWs;
|
|
6597
|
-
subscriptions = this.stockSubscriptions;
|
|
6598
|
-
}
|
|
6599
|
-
else if (streamType === "option") {
|
|
6600
|
-
ws = this.optionWs;
|
|
6601
|
-
subscriptions = this.optionSubscriptions;
|
|
6602
|
-
}
|
|
6603
|
-
else {
|
|
6604
|
-
ws = this.cryptoWs;
|
|
6605
|
-
subscriptions = this.cryptoSubscriptions;
|
|
6606
|
-
}
|
|
6607
|
-
log$1(`sendSubscription called for ${streamType} (wsReady=${ws?.readyState === WebSocket.OPEN}, trades=${subscriptions.trades?.length || 0}, quotes=${subscriptions.quotes?.length || 0}, bars=${subscriptions.bars?.length || 0})`, {
|
|
6608
|
-
type: "debug",
|
|
6609
|
-
});
|
|
6256
|
+
const ws = streamType === 'stock' ? this.stockWs : this.optionWs;
|
|
6257
|
+
const subscriptions = streamType === 'stock' ? this.stockSubscriptions : this.optionSubscriptions;
|
|
6610
6258
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
6611
6259
|
const subMessagePayload = {};
|
|
6612
6260
|
if (subscriptions.trades.length > 0) {
|
|
@@ -6620,40 +6268,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6620
6268
|
}
|
|
6621
6269
|
if (Object.keys(subMessagePayload).length > 0) {
|
|
6622
6270
|
const subMessage = {
|
|
6623
|
-
action:
|
|
6271
|
+
action: 'subscribe',
|
|
6624
6272
|
...subMessagePayload,
|
|
6625
6273
|
};
|
|
6626
|
-
|
|
6627
|
-
log$1(`Sending ${streamType} subscription: ${messageJson}`, {
|
|
6628
|
-
type: "info",
|
|
6629
|
-
});
|
|
6630
|
-
ws.send(messageJson);
|
|
6631
|
-
}
|
|
6632
|
-
else {
|
|
6633
|
-
log$1(`No ${streamType} subscriptions to send (all arrays empty)`, {
|
|
6634
|
-
type: "debug",
|
|
6635
|
-
});
|
|
6274
|
+
ws.send(JSON.stringify(subMessage));
|
|
6636
6275
|
}
|
|
6637
6276
|
}
|
|
6638
|
-
else {
|
|
6639
|
-
log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, {
|
|
6640
|
-
type: "warn",
|
|
6641
|
-
});
|
|
6642
|
-
}
|
|
6643
6277
|
}
|
|
6644
6278
|
connectStockStream() {
|
|
6645
6279
|
if (!this.stockWs) {
|
|
6646
|
-
this.connect(
|
|
6280
|
+
this.connect('stock');
|
|
6647
6281
|
}
|
|
6648
6282
|
}
|
|
6649
6283
|
connectOptionStream() {
|
|
6650
6284
|
if (!this.optionWs) {
|
|
6651
|
-
this.connect(
|
|
6652
|
-
}
|
|
6653
|
-
}
|
|
6654
|
-
connectCryptoStream() {
|
|
6655
|
-
if (!this.cryptoWs) {
|
|
6656
|
-
this.connect("crypto");
|
|
6285
|
+
this.connect('option');
|
|
6657
6286
|
}
|
|
6658
6287
|
}
|
|
6659
6288
|
disconnectStockStream() {
|
|
@@ -6666,95 +6295,41 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6666
6295
|
this.optionWs.close();
|
|
6667
6296
|
}
|
|
6668
6297
|
}
|
|
6669
|
-
disconnectCryptoStream() {
|
|
6670
|
-
if (this.cryptoWs) {
|
|
6671
|
-
this.cryptoWs.close();
|
|
6672
|
-
}
|
|
6673
|
-
}
|
|
6674
|
-
/**
|
|
6675
|
-
* Check if a specific stream is connected
|
|
6676
|
-
* @param streamType - The type of stream to check
|
|
6677
|
-
* @returns True if the stream is connected
|
|
6678
|
-
*/
|
|
6679
|
-
isStreamConnected(streamType) {
|
|
6680
|
-
if (streamType === "stock") {
|
|
6681
|
-
return (this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN);
|
|
6682
|
-
}
|
|
6683
|
-
else if (streamType === "option") {
|
|
6684
|
-
return (this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN);
|
|
6685
|
-
}
|
|
6686
|
-
else {
|
|
6687
|
-
return (this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN);
|
|
6688
|
-
}
|
|
6689
|
-
}
|
|
6690
6298
|
subscribe(streamType, subscriptions) {
|
|
6691
|
-
|
|
6692
|
-
if (streamType === "stock") {
|
|
6693
|
-
currentSubscriptions = this.stockSubscriptions;
|
|
6694
|
-
}
|
|
6695
|
-
else if (streamType === "option") {
|
|
6696
|
-
currentSubscriptions = this.optionSubscriptions;
|
|
6697
|
-
}
|
|
6698
|
-
else {
|
|
6699
|
-
currentSubscriptions = this.cryptoSubscriptions;
|
|
6700
|
-
}
|
|
6299
|
+
const currentSubscriptions = streamType === 'stock' ? this.stockSubscriptions : this.optionSubscriptions;
|
|
6701
6300
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6702
6301
|
if (value) {
|
|
6703
|
-
currentSubscriptions[key] = [
|
|
6704
|
-
...new Set([...(currentSubscriptions[key] || []), ...value]),
|
|
6705
|
-
];
|
|
6302
|
+
currentSubscriptions[key] = [...new Set([...(currentSubscriptions[key] || []), ...value])];
|
|
6706
6303
|
}
|
|
6707
6304
|
});
|
|
6708
6305
|
this.sendSubscription(streamType);
|
|
6709
6306
|
}
|
|
6710
6307
|
unsubscribe(streamType, subscriptions) {
|
|
6711
|
-
|
|
6712
|
-
if (streamType === "stock") {
|
|
6713
|
-
currentSubscriptions = this.stockSubscriptions;
|
|
6714
|
-
}
|
|
6715
|
-
else if (streamType === "option") {
|
|
6716
|
-
currentSubscriptions = this.optionSubscriptions;
|
|
6717
|
-
}
|
|
6718
|
-
else {
|
|
6719
|
-
currentSubscriptions = this.cryptoSubscriptions;
|
|
6720
|
-
}
|
|
6308
|
+
const currentSubscriptions = streamType === 'stock' ? this.stockSubscriptions : this.optionSubscriptions;
|
|
6721
6309
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6722
6310
|
if (value) {
|
|
6723
|
-
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(
|
|
6311
|
+
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
|
|
6724
6312
|
}
|
|
6725
6313
|
});
|
|
6726
6314
|
const unsubMessage = {
|
|
6727
|
-
action:
|
|
6315
|
+
action: 'unsubscribe',
|
|
6728
6316
|
...subscriptions,
|
|
6729
6317
|
};
|
|
6730
|
-
|
|
6731
|
-
if (streamType === "stock") {
|
|
6732
|
-
ws = this.stockWs;
|
|
6733
|
-
}
|
|
6734
|
-
else if (streamType === "option") {
|
|
6735
|
-
ws = this.optionWs;
|
|
6736
|
-
}
|
|
6737
|
-
else {
|
|
6738
|
-
ws = this.cryptoWs;
|
|
6739
|
-
}
|
|
6318
|
+
const ws = streamType === 'stock' ? this.stockWs : this.optionWs;
|
|
6740
6319
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
6741
6320
|
ws.send(JSON.stringify(unsubMessage));
|
|
6742
6321
|
}
|
|
6743
6322
|
}
|
|
6744
|
-
async makeRequest(endpoint, method =
|
|
6745
|
-
const baseUrl = baseUrlName ===
|
|
6746
|
-
? this.dataURL
|
|
6747
|
-
: baseUrlName === "api"
|
|
6748
|
-
? this.apiURL
|
|
6749
|
-
: this.v1beta1url;
|
|
6323
|
+
async makeRequest(endpoint, method = 'GET', params, baseUrlName = 'data') {
|
|
6324
|
+
const baseUrl = baseUrlName === 'data' ? this.dataURL : baseUrlName === 'api' ? this.apiURL : this.v1beta1url;
|
|
6750
6325
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
6751
6326
|
try {
|
|
6752
6327
|
if (params) {
|
|
6753
6328
|
Object.entries(params).forEach(([key, value]) => {
|
|
6754
6329
|
if (Array.isArray(value)) {
|
|
6755
|
-
url.searchParams.append(key, value.join(
|
|
6330
|
+
url.searchParams.append(key, value.join(','));
|
|
6756
6331
|
}
|
|
6757
|
-
else if (value !== undefined
|
|
6332
|
+
else if (value !== undefined) {
|
|
6758
6333
|
url.searchParams.append(key, value.toString());
|
|
6759
6334
|
}
|
|
6760
6335
|
});
|
|
@@ -6762,13 +6337,10 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6762
6337
|
const response = await fetch(url.toString(), {
|
|
6763
6338
|
method,
|
|
6764
6339
|
headers: this.headers,
|
|
6765
|
-
signal: createTimeoutSignal(DEFAULT_TIMEOUTS.ALPACA_API),
|
|
6766
6340
|
});
|
|
6767
6341
|
if (!response.ok) {
|
|
6768
6342
|
const errorText = await response.text();
|
|
6769
|
-
log$1(`Market Data API error (${response.status}): ${errorText}`, {
|
|
6770
|
-
type: "error",
|
|
6771
|
-
});
|
|
6343
|
+
log$1(`Market Data API error (${response.status}): ${errorText}`, { type: 'error' });
|
|
6772
6344
|
throw new Error(`Market Data API error (${response.status}): ${errorText}`);
|
|
6773
6345
|
}
|
|
6774
6346
|
const data = await response.json();
|
|
@@ -6776,9 +6348,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6776
6348
|
}
|
|
6777
6349
|
catch (err) {
|
|
6778
6350
|
const error = err;
|
|
6779
|
-
log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type:
|
|
6351
|
+
log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: 'error' });
|
|
6780
6352
|
if (error instanceof TypeError) {
|
|
6781
|
-
log$1(`Network error details: ${error.stack}`, { type:
|
|
6353
|
+
log$1(`Network error details: ${error.stack}`, { type: 'error' });
|
|
6782
6354
|
}
|
|
6783
6355
|
throw error;
|
|
6784
6356
|
}
|
|
@@ -6791,19 +6363,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6791
6363
|
*/
|
|
6792
6364
|
async getHistoricalBars(params) {
|
|
6793
6365
|
const symbols = params.symbols;
|
|
6794
|
-
const symbolsStr = symbols.join(
|
|
6366
|
+
const symbolsStr = symbols.join(',');
|
|
6795
6367
|
let allBars = {};
|
|
6796
6368
|
let pageToken = null;
|
|
6797
6369
|
let hasMorePages = true;
|
|
6798
6370
|
let totalBarsCount = 0;
|
|
6799
6371
|
let pageCount = 0;
|
|
6800
|
-
let currency =
|
|
6372
|
+
let currency = '';
|
|
6801
6373
|
// Initialize bar arrays for each symbol
|
|
6802
|
-
symbols.forEach(
|
|
6374
|
+
symbols.forEach(symbol => {
|
|
6803
6375
|
allBars[symbol] = [];
|
|
6804
6376
|
});
|
|
6805
|
-
log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start ||
|
|
6806
|
-
type:
|
|
6377
|
+
log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
6378
|
+
type: 'info'
|
|
6807
6379
|
});
|
|
6808
6380
|
while (hasMorePages) {
|
|
6809
6381
|
pageCount++;
|
|
@@ -6813,11 +6385,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6813
6385
|
feed: DEFAULT_FEED,
|
|
6814
6386
|
...(pageToken && { page_token: pageToken }),
|
|
6815
6387
|
};
|
|
6816
|
-
const response = await this.makeRequest(
|
|
6388
|
+
const response = await this.makeRequest('/stocks/bars', 'GET', requestParams);
|
|
6817
6389
|
if (!response.bars) {
|
|
6818
|
-
log$1(`No bars data found in response for ${symbolsStr}`, {
|
|
6819
|
-
type: "warn",
|
|
6820
|
-
});
|
|
6390
|
+
log$1(`No bars data found in response for ${symbolsStr}`, { type: 'warn' });
|
|
6821
6391
|
break;
|
|
6822
6392
|
}
|
|
6823
6393
|
// Track currency from first response
|
|
@@ -6833,7 +6403,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6833
6403
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
6834
6404
|
pageBarsCount += bars.length;
|
|
6835
6405
|
// Track date range for this page
|
|
6836
|
-
bars.forEach(
|
|
6406
|
+
bars.forEach(bar => {
|
|
6837
6407
|
const barDate = new Date(bar.t);
|
|
6838
6408
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
6839
6409
|
earliestTimestamp = barDate;
|
|
@@ -6849,23 +6419,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6849
6419
|
hasMorePages = !!pageToken;
|
|
6850
6420
|
// Enhanced logging with date range and progress info
|
|
6851
6421
|
const dateRangeStr = earliestTimestamp && latestTimestamp
|
|
6852
|
-
? `${earliestTimestamp.toLocaleDateString(
|
|
6853
|
-
:
|
|
6854
|
-
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
6855
|
-
type:
|
|
6422
|
+
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
6423
|
+
: 'unknown range';
|
|
6424
|
+
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
6425
|
+
type: 'info'
|
|
6856
6426
|
});
|
|
6857
6427
|
// Prevent infinite loops
|
|
6858
6428
|
if (pageCount > 1000) {
|
|
6859
|
-
log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6429
|
+
log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
6860
6430
|
break;
|
|
6861
6431
|
}
|
|
6862
6432
|
}
|
|
6863
6433
|
// Final summary
|
|
6864
|
-
const symbolCounts = Object.entries(allBars)
|
|
6865
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
6866
|
-
.join(", ");
|
|
6434
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
6867
6435
|
log$1(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
6868
|
-
type:
|
|
6436
|
+
type: 'info'
|
|
6869
6437
|
});
|
|
6870
6438
|
return {
|
|
6871
6439
|
bars: allBars,
|
|
@@ -6881,7 +6449,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6881
6449
|
|
|
6882
6450
|
*/
|
|
6883
6451
|
async getLatestBars(symbols, currency) {
|
|
6884
|
-
return this.makeRequest(
|
|
6452
|
+
return this.makeRequest('/stocks/bars/latest', 'GET', {
|
|
6885
6453
|
symbols,
|
|
6886
6454
|
feed: DEFAULT_FEED,
|
|
6887
6455
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6893,7 +6461,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6893
6461
|
* @returns Last trade details including price, size, exchange, and conditions
|
|
6894
6462
|
*/
|
|
6895
6463
|
async getLastTrade(symbol) {
|
|
6896
|
-
return this.makeRequest(`/v1/last/stocks/${symbol}`,
|
|
6464
|
+
return this.makeRequest(`/v1/last/stocks/${symbol}`, 'GET');
|
|
6897
6465
|
}
|
|
6898
6466
|
/**
|
|
6899
6467
|
* Get the most recent trades for requested symbols
|
|
@@ -6904,7 +6472,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6904
6472
|
|
|
6905
6473
|
*/
|
|
6906
6474
|
async getLatestTrades(symbols, feed, currency) {
|
|
6907
|
-
return this.makeRequest(
|
|
6475
|
+
return this.makeRequest('/stocks/trades/latest', 'GET', {
|
|
6908
6476
|
symbols,
|
|
6909
6477
|
feed: feed || DEFAULT_FEED,
|
|
6910
6478
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6920,15 +6488,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6920
6488
|
async getLatestQuotes(symbols, feed, currency) {
|
|
6921
6489
|
// Return empty response if symbols array is empty to avoid API error
|
|
6922
6490
|
if (!symbols || symbols.length === 0) {
|
|
6923
|
-
log$1(
|
|
6924
|
-
type: "warn",
|
|
6925
|
-
});
|
|
6491
|
+
log$1('No symbols provided to getLatestQuotes, returning empty response', { type: 'warn' });
|
|
6926
6492
|
return {
|
|
6927
6493
|
quotes: {},
|
|
6928
6494
|
currency: currency || DEFAULT_CURRENCY,
|
|
6929
6495
|
};
|
|
6930
6496
|
}
|
|
6931
|
-
return this.makeRequest(
|
|
6497
|
+
return this.makeRequest('/stocks/quotes/latest', 'GET', {
|
|
6932
6498
|
symbols,
|
|
6933
6499
|
feed: feed || DEFAULT_FEED,
|
|
6934
6500
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6942,7 +6508,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6942
6508
|
* @returns Latest quote data with symbol and currency information
|
|
6943
6509
|
*/
|
|
6944
6510
|
async getLatestQuote(symbol, feed, currency) {
|
|
6945
|
-
return this.makeRequest(`/stocks/${symbol}/quotes/latest`,
|
|
6511
|
+
return this.makeRequest(`/stocks/${symbol}/quotes/latest`, 'GET', {
|
|
6946
6512
|
feed: feed || DEFAULT_FEED,
|
|
6947
6513
|
currency,
|
|
6948
6514
|
});
|
|
@@ -6958,16 +6524,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6958
6524
|
const prevMarketDate = getLastFullTradingDate(date);
|
|
6959
6525
|
const response = await this.getHistoricalBars({
|
|
6960
6526
|
symbols: [symbol],
|
|
6961
|
-
timeframe:
|
|
6527
|
+
timeframe: '1Day',
|
|
6962
6528
|
start: prevMarketDate.date.toISOString(),
|
|
6963
6529
|
end: prevMarketDate.date.toISOString(),
|
|
6964
6530
|
limit: 1,
|
|
6965
6531
|
});
|
|
6966
6532
|
if (!response.bars[symbol] || response.bars[symbol].length === 0) {
|
|
6967
|
-
log$1(`No previous close data available for ${symbol}`, {
|
|
6968
|
-
type: "error",
|
|
6969
|
-
symbol,
|
|
6970
|
-
});
|
|
6533
|
+
log$1(`No previous close data available for ${symbol}`, { type: 'error', symbol });
|
|
6971
6534
|
return null;
|
|
6972
6535
|
}
|
|
6973
6536
|
return response.bars[symbol][0];
|
|
@@ -6982,7 +6545,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6982
6545
|
async getHourlyPrices(symbol, start, end) {
|
|
6983
6546
|
const response = await this.getHistoricalBars({
|
|
6984
6547
|
symbols: [symbol],
|
|
6985
|
-
timeframe:
|
|
6548
|
+
timeframe: '1Hour',
|
|
6986
6549
|
start: new Date(start).toISOString(),
|
|
6987
6550
|
end: new Date(end).toISOString(),
|
|
6988
6551
|
limit: 96, // Last 96 hours (4 days)
|
|
@@ -6999,7 +6562,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6999
6562
|
async getHalfHourlyPrices(symbol, start, end) {
|
|
7000
6563
|
const response = await this.getHistoricalBars({
|
|
7001
6564
|
symbols: [symbol],
|
|
7002
|
-
timeframe:
|
|
6565
|
+
timeframe: '30Min',
|
|
7003
6566
|
start: new Date(start).toISOString(),
|
|
7004
6567
|
end: new Date(end).toISOString(),
|
|
7005
6568
|
limit: 16 * 2 * 4, // last 4 days, 16 hours per day, 2 bars per hour
|
|
@@ -7016,7 +6579,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7016
6579
|
async getDailyPrices(symbol, start, end) {
|
|
7017
6580
|
const response = await this.getHistoricalBars({
|
|
7018
6581
|
symbols: [symbol],
|
|
7019
|
-
timeframe:
|
|
6582
|
+
timeframe: '1Day',
|
|
7020
6583
|
start: new Date(start).toISOString(),
|
|
7021
6584
|
end: new Date(end).toISOString(),
|
|
7022
6585
|
limit: 100, // Last 100 days
|
|
@@ -7048,7 +6611,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7048
6611
|
*/
|
|
7049
6612
|
static analyzeBars(bars) {
|
|
7050
6613
|
if (!bars || bars.length === 0) {
|
|
7051
|
-
return
|
|
6614
|
+
return 'No price data available';
|
|
7052
6615
|
}
|
|
7053
6616
|
const firstBar = bars[0];
|
|
7054
6617
|
const lastBar = bars[bars.length - 1];
|
|
@@ -7073,7 +6636,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7073
6636
|
*/
|
|
7074
6637
|
async getAssets(params) {
|
|
7075
6638
|
// Endpoint: GET /v2/assets
|
|
7076
|
-
return this.makeRequest(
|
|
6639
|
+
return this.makeRequest('/assets', 'GET', params, 'api'); // use apiURL
|
|
7077
6640
|
}
|
|
7078
6641
|
/**
|
|
7079
6642
|
* Get a single asset by symbol or asset_id
|
|
@@ -7083,7 +6646,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7083
6646
|
*/
|
|
7084
6647
|
async getAsset(symbolOrAssetId) {
|
|
7085
6648
|
// Endpoint: GET /v2/assets/{symbol_or_asset_id}
|
|
7086
|
-
return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`,
|
|
6649
|
+
return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, 'GET', undefined, 'api');
|
|
7087
6650
|
}
|
|
7088
6651
|
// ===== OPTIONS MARKET DATA METHODS =====
|
|
7089
6652
|
/**
|
|
@@ -7095,7 +6658,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7095
6658
|
*/
|
|
7096
6659
|
async getOptionsChain(params) {
|
|
7097
6660
|
const { underlying_symbol, ...queryParams } = params;
|
|
7098
|
-
return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`,
|
|
6661
|
+
return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, 'GET', queryParams, 'v1beta1');
|
|
7099
6662
|
}
|
|
7100
6663
|
/**
|
|
7101
6664
|
* Get the most recent trades for requested option contract symbols
|
|
@@ -7107,7 +6670,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7107
6670
|
async getLatestOptionsTrades(params) {
|
|
7108
6671
|
// Remove limit and page_token as they're not supported by this endpoint
|
|
7109
6672
|
const { limit, page_token, ...requestParams } = params;
|
|
7110
|
-
return this.makeRequest(
|
|
6673
|
+
return this.makeRequest('/options/trades/latest', 'GET', requestParams, 'v1beta1');
|
|
7111
6674
|
}
|
|
7112
6675
|
/**
|
|
7113
6676
|
* Get the most recent quotes for requested option contract symbols
|
|
@@ -7119,7 +6682,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7119
6682
|
async getLatestOptionsQuotes(params) {
|
|
7120
6683
|
// Remove limit and page_token as they're not supported by this endpoint
|
|
7121
6684
|
const { limit, page_token, ...requestParams } = params;
|
|
7122
|
-
return this.makeRequest(
|
|
6685
|
+
return this.makeRequest('/options/quotes/latest', 'GET', requestParams, 'v1beta1');
|
|
7123
6686
|
}
|
|
7124
6687
|
/**
|
|
7125
6688
|
* Get historical OHLCV bars for option contract symbols
|
|
@@ -7131,18 +6694,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7131
6694
|
*/
|
|
7132
6695
|
async getHistoricalOptionsBars(params) {
|
|
7133
6696
|
const symbols = params.symbols;
|
|
7134
|
-
const symbolsStr = symbols.join(
|
|
6697
|
+
const symbolsStr = symbols.join(',');
|
|
7135
6698
|
let allBars = {};
|
|
7136
6699
|
let pageToken = null;
|
|
7137
6700
|
let hasMorePages = true;
|
|
7138
6701
|
let totalBarsCount = 0;
|
|
7139
6702
|
let pageCount = 0;
|
|
7140
6703
|
// Initialize bar arrays for each symbol
|
|
7141
|
-
symbols.forEach(
|
|
6704
|
+
symbols.forEach(symbol => {
|
|
7142
6705
|
allBars[symbol] = [];
|
|
7143
6706
|
});
|
|
7144
|
-
log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start ||
|
|
7145
|
-
type:
|
|
6707
|
+
log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
6708
|
+
type: 'info'
|
|
7146
6709
|
});
|
|
7147
6710
|
while (hasMorePages) {
|
|
7148
6711
|
pageCount++;
|
|
@@ -7150,11 +6713,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7150
6713
|
...params,
|
|
7151
6714
|
...(pageToken && { page_token: pageToken }),
|
|
7152
6715
|
};
|
|
7153
|
-
const response = await this.makeRequest(
|
|
6716
|
+
const response = await this.makeRequest('/options/bars', 'GET', requestParams, 'v1beta1');
|
|
7154
6717
|
if (!response.bars) {
|
|
7155
|
-
log$1(`No options bars data found in response for ${symbolsStr}`, {
|
|
7156
|
-
type: "warn",
|
|
7157
|
-
});
|
|
6718
|
+
log$1(`No options bars data found in response for ${symbolsStr}`, { type: 'warn' });
|
|
7158
6719
|
break;
|
|
7159
6720
|
}
|
|
7160
6721
|
// Combine bars for each symbol
|
|
@@ -7166,7 +6727,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7166
6727
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
7167
6728
|
pageBarsCount += bars.length;
|
|
7168
6729
|
// Track date range for this page
|
|
7169
|
-
bars.forEach(
|
|
6730
|
+
bars.forEach(bar => {
|
|
7170
6731
|
const barDate = new Date(bar.t);
|
|
7171
6732
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
7172
6733
|
earliestTimestamp = barDate;
|
|
@@ -7182,23 +6743,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7182
6743
|
hasMorePages = !!pageToken;
|
|
7183
6744
|
// Enhanced logging with date range and progress info
|
|
7184
6745
|
const dateRangeStr = earliestTimestamp && latestTimestamp
|
|
7185
|
-
? `${earliestTimestamp.toLocaleDateString(
|
|
7186
|
-
:
|
|
7187
|
-
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
7188
|
-
type:
|
|
6746
|
+
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
6747
|
+
: 'unknown range';
|
|
6748
|
+
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
6749
|
+
type: 'info'
|
|
7189
6750
|
});
|
|
7190
6751
|
// Prevent infinite loops
|
|
7191
6752
|
if (pageCount > 1000) {
|
|
7192
|
-
log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6753
|
+
log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
7193
6754
|
break;
|
|
7194
6755
|
}
|
|
7195
6756
|
}
|
|
7196
6757
|
// Final summary
|
|
7197
|
-
const symbolCounts = Object.entries(allBars)
|
|
7198
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
7199
|
-
.join(", ");
|
|
6758
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
7200
6759
|
log$1(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
7201
|
-
type:
|
|
6760
|
+
type: 'info'
|
|
7202
6761
|
});
|
|
7203
6762
|
return {
|
|
7204
6763
|
bars: allBars,
|
|
@@ -7215,18 +6774,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7215
6774
|
*/
|
|
7216
6775
|
async getHistoricalOptionsTrades(params) {
|
|
7217
6776
|
const symbols = params.symbols;
|
|
7218
|
-
const symbolsStr = symbols.join(
|
|
6777
|
+
const symbolsStr = symbols.join(',');
|
|
7219
6778
|
let allTrades = {};
|
|
7220
6779
|
let pageToken = null;
|
|
7221
6780
|
let hasMorePages = true;
|
|
7222
6781
|
let totalTradesCount = 0;
|
|
7223
6782
|
let pageCount = 0;
|
|
7224
6783
|
// Initialize trades arrays for each symbol
|
|
7225
|
-
symbols.forEach(
|
|
6784
|
+
symbols.forEach(symbol => {
|
|
7226
6785
|
allTrades[symbol] = [];
|
|
7227
6786
|
});
|
|
7228
|
-
log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start ||
|
|
7229
|
-
type:
|
|
6787
|
+
log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
6788
|
+
type: 'info'
|
|
7230
6789
|
});
|
|
7231
6790
|
while (hasMorePages) {
|
|
7232
6791
|
pageCount++;
|
|
@@ -7234,11 +6793,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7234
6793
|
...params,
|
|
7235
6794
|
...(pageToken && { page_token: pageToken }),
|
|
7236
6795
|
};
|
|
7237
|
-
const response = await this.makeRequest(
|
|
6796
|
+
const response = await this.makeRequest('/options/trades', 'GET', requestParams, 'v1beta1');
|
|
7238
6797
|
if (!response.trades) {
|
|
7239
|
-
log$1(`No options trades data found in response for ${symbolsStr}`, {
|
|
7240
|
-
type: "warn",
|
|
7241
|
-
});
|
|
6798
|
+
log$1(`No options trades data found in response for ${symbolsStr}`, { type: 'warn' });
|
|
7242
6799
|
break;
|
|
7243
6800
|
}
|
|
7244
6801
|
// Combine trades for each symbol
|
|
@@ -7250,7 +6807,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7250
6807
|
allTrades[symbol] = [...allTrades[symbol], ...trades];
|
|
7251
6808
|
pageTradesCount += trades.length;
|
|
7252
6809
|
// Track date range for this page
|
|
7253
|
-
trades.forEach(
|
|
6810
|
+
trades.forEach(trade => {
|
|
7254
6811
|
const tradeDate = new Date(trade.t);
|
|
7255
6812
|
if (!earliestTimestamp || tradeDate < earliestTimestamp) {
|
|
7256
6813
|
earliestTimestamp = tradeDate;
|
|
@@ -7266,23 +6823,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7266
6823
|
hasMorePages = !!pageToken;
|
|
7267
6824
|
// Enhanced logging with date range and progress info
|
|
7268
6825
|
const dateRangeStr = earliestTimestamp && latestTimestamp
|
|
7269
|
-
? `${earliestTimestamp.toLocaleDateString(
|
|
7270
|
-
:
|
|
7271
|
-
log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ?
|
|
7272
|
-
type:
|
|
6826
|
+
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
6827
|
+
: 'unknown range';
|
|
6828
|
+
log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
6829
|
+
type: 'info'
|
|
7273
6830
|
});
|
|
7274
6831
|
// Prevent infinite loops
|
|
7275
6832
|
if (pageCount > 1000) {
|
|
7276
|
-
log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6833
|
+
log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
7277
6834
|
break;
|
|
7278
6835
|
}
|
|
7279
6836
|
}
|
|
7280
6837
|
// Final summary
|
|
7281
|
-
const symbolCounts = Object.entries(allTrades)
|
|
7282
|
-
.map(([symbol, trades]) => `${symbol}: ${trades.length}`)
|
|
7283
|
-
.join(", ");
|
|
6838
|
+
const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
|
|
7284
6839
|
log$1(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
|
|
7285
|
-
type:
|
|
6840
|
+
type: 'info'
|
|
7286
6841
|
});
|
|
7287
6842
|
return {
|
|
7288
6843
|
trades: allTrades,
|
|
@@ -7300,7 +6855,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7300
6855
|
async getOptionsSnapshot(params) {
|
|
7301
6856
|
// Remove limit and page_token as they may not be supported by this endpoint
|
|
7302
6857
|
const { limit, page_token, ...requestParams } = params;
|
|
7303
|
-
return this.makeRequest(
|
|
6858
|
+
return this.makeRequest('/options/snapshots', 'GET', requestParams, 'v1beta1');
|
|
7304
6859
|
}
|
|
7305
6860
|
/**
|
|
7306
6861
|
* Get condition codes for options trades or quotes
|
|
@@ -7311,7 +6866,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7311
6866
|
* @see https://docs.alpaca.markets/reference/optionmetaconditions
|
|
7312
6867
|
*/
|
|
7313
6868
|
async getOptionsConditionCodes(tickType) {
|
|
7314
|
-
return this.makeRequest(`/options/meta/conditions/${tickType}`,
|
|
6869
|
+
return this.makeRequest(`/options/meta/conditions/${tickType}`, 'GET', undefined, 'v1beta1');
|
|
7315
6870
|
}
|
|
7316
6871
|
/**
|
|
7317
6872
|
* Get exchange codes for options
|
|
@@ -7321,7 +6876,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7321
6876
|
* @see https://docs.alpaca.markets/reference/optionmetaexchanges
|
|
7322
6877
|
*/
|
|
7323
6878
|
async getOptionsExchangeCodes() {
|
|
7324
|
-
return this.makeRequest(
|
|
6879
|
+
return this.makeRequest('/options/meta/exchanges', 'GET', undefined, 'v1beta1');
|
|
7325
6880
|
}
|
|
7326
6881
|
/**
|
|
7327
6882
|
* Analyzes an array of option bars and returns a summary string
|
|
@@ -7330,7 +6885,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7330
6885
|
*/
|
|
7331
6886
|
static analyzeOptionBars(bars) {
|
|
7332
6887
|
if (!bars || bars.length === 0) {
|
|
7333
|
-
return
|
|
6888
|
+
return 'No option price data available';
|
|
7334
6889
|
}
|
|
7335
6890
|
const firstBar = bars[0];
|
|
7336
6891
|
const lastBar = bars[bars.length - 1];
|
|
@@ -7354,7 +6909,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7354
6909
|
*/
|
|
7355
6910
|
static formatOptionGreeks(greeks) {
|
|
7356
6911
|
if (!greeks) {
|
|
7357
|
-
return
|
|
6912
|
+
return 'No greeks data available';
|
|
7358
6913
|
}
|
|
7359
6914
|
const parts = [];
|
|
7360
6915
|
if (greeks.delta !== undefined)
|
|
@@ -7367,7 +6922,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7367
6922
|
parts.push(`Vega: ${greeks.vega.toFixed(4)}`);
|
|
7368
6923
|
if (greeks.rho !== undefined)
|
|
7369
6924
|
parts.push(`Rho: ${greeks.rho.toFixed(4)}`);
|
|
7370
|
-
return parts.length > 0 ? parts.join(
|
|
6925
|
+
return parts.length > 0 ? parts.join(', ') : 'No greeks data available';
|
|
7371
6926
|
}
|
|
7372
6927
|
/**
|
|
7373
6928
|
* Interprets condition codes using the provided condition codes mapping
|
|
@@ -7377,14 +6932,12 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7377
6932
|
*/
|
|
7378
6933
|
static interpretConditionCodes(conditionCodes, conditionCodesMap) {
|
|
7379
6934
|
if (!conditionCodes || conditionCodes.length === 0) {
|
|
7380
|
-
return
|
|
6935
|
+
return 'No conditions';
|
|
7381
6936
|
}
|
|
7382
6937
|
const descriptions = conditionCodes
|
|
7383
6938
|
.map((code) => conditionCodesMap[code] || `Unknown (${code})`)
|
|
7384
6939
|
.filter((desc) => desc !== undefined);
|
|
7385
|
-
return descriptions.length > 0
|
|
7386
|
-
? descriptions.join(", ")
|
|
7387
|
-
: "No condition descriptions available";
|
|
6940
|
+
return descriptions.length > 0 ? descriptions.join(', ') : 'No condition descriptions available';
|
|
7388
6941
|
}
|
|
7389
6942
|
/**
|
|
7390
6943
|
* Gets the exchange name from exchange code using the provided exchange codes mapping
|
|
@@ -7393,7 +6946,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7393
6946
|
* @returns Exchange name or formatted unknown exchange
|
|
7394
6947
|
*/
|
|
7395
6948
|
static getExchangeName(exchangeCode, exchangeCodesMap) {
|
|
7396
|
-
return
|
|
6949
|
+
return exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`;
|
|
7397
6950
|
}
|
|
7398
6951
|
/**
|
|
7399
6952
|
* Fetches news articles from Alpaca API for a symbol, paginating through all results.
|
|
@@ -7406,7 +6959,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7406
6959
|
start: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
|
7407
6960
|
end: new Date(),
|
|
7408
6961
|
limit: 10,
|
|
7409
|
-
sort:
|
|
6962
|
+
sort: 'desc',
|
|
7410
6963
|
include_content: true,
|
|
7411
6964
|
};
|
|
7412
6965
|
const mergedParams = { ...defaultParams, ...params };
|
|
@@ -7420,52 +6973,38 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7420
6973
|
if (!content)
|
|
7421
6974
|
return undefined;
|
|
7422
6975
|
// Remove excessive whitespace, newlines, and trim
|
|
7423
|
-
return content.replace(/\s+/g,
|
|
6976
|
+
return content.replace(/\s+/g, ' ').trim();
|
|
7424
6977
|
}
|
|
7425
6978
|
while (hasMorePages) {
|
|
7426
6979
|
const queryParams = new URLSearchParams({
|
|
7427
|
-
...(mergedParams.start && {
|
|
7428
|
-
|
|
7429
|
-
}),
|
|
7430
|
-
...(mergedParams.end && {
|
|
7431
|
-
end: new Date(mergedParams.end).toISOString(),
|
|
7432
|
-
}),
|
|
6980
|
+
...(mergedParams.start && { start: new Date(mergedParams.start).toISOString() }),
|
|
6981
|
+
...(mergedParams.end && { end: new Date(mergedParams.end).toISOString() }),
|
|
7433
6982
|
...(symbol && { symbols: symbol }),
|
|
7434
|
-
...(mergedParams.limit && {
|
|
7435
|
-
limit: Math.min(50, maxLimit - fetchedCount).toString(),
|
|
7436
|
-
}),
|
|
6983
|
+
...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
|
|
7437
6984
|
...(mergedParams.sort && { sort: mergedParams.sort }),
|
|
7438
|
-
...(mergedParams.include_content !== undefined
|
|
7439
|
-
? { include_content: mergedParams.include_content.toString() }
|
|
7440
|
-
: {}),
|
|
6985
|
+
...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
|
|
7441
6986
|
...(pageToken && { page_token: pageToken }),
|
|
7442
6987
|
});
|
|
7443
6988
|
const url = `${this.v1beta1url}/news?${queryParams}`;
|
|
7444
|
-
log$1(`Fetching news from: ${url}`, { type:
|
|
6989
|
+
log$1(`Fetching news from: ${url}`, { type: 'debug', symbol });
|
|
7445
6990
|
const response = await fetch(url, {
|
|
7446
|
-
method:
|
|
6991
|
+
method: 'GET',
|
|
7447
6992
|
headers: this.headers,
|
|
7448
6993
|
});
|
|
7449
6994
|
if (!response.ok) {
|
|
7450
6995
|
const errorText = await response.text();
|
|
7451
|
-
log$1(`Alpaca news API error (${response.status}): ${errorText}`, {
|
|
7452
|
-
type: "error",
|
|
7453
|
-
symbol,
|
|
7454
|
-
});
|
|
6996
|
+
log$1(`Alpaca news API error (${response.status}): ${errorText}`, { type: 'error', symbol });
|
|
7455
6997
|
throw new Error(`Alpaca news API error (${response.status}): ${errorText}`);
|
|
7456
6998
|
}
|
|
7457
6999
|
const data = await response.json();
|
|
7458
7000
|
if (!data.news || !Array.isArray(data.news)) {
|
|
7459
|
-
log$1(`No news data found in Alpaca response for ${symbol}`, {
|
|
7460
|
-
type: "warn",
|
|
7461
|
-
symbol,
|
|
7462
|
-
});
|
|
7001
|
+
log$1(`No news data found in Alpaca response for ${symbol}`, { type: 'warn', symbol });
|
|
7463
7002
|
break;
|
|
7464
7003
|
}
|
|
7465
7004
|
const transformedNews = data.news.map((article) => ({
|
|
7466
7005
|
symbols: article.symbols,
|
|
7467
7006
|
title: article.headline,
|
|
7468
|
-
summary: cleanContent(article.summary) ??
|
|
7007
|
+
summary: cleanContent(article.summary) ?? '',
|
|
7469
7008
|
content: article.content ? cleanContent(article.content) : undefined,
|
|
7470
7009
|
url: article.url,
|
|
7471
7010
|
source: article.source,
|
|
@@ -7478,7 +7017,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7478
7017
|
fetchedCount = newsArticles.length;
|
|
7479
7018
|
pageToken = data.next_page_token || null;
|
|
7480
7019
|
hasMorePages = !!pageToken && (!maxLimit || fetchedCount < maxLimit);
|
|
7481
|
-
log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type:
|
|
7020
|
+
log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: 'debug', symbol });
|
|
7482
7021
|
if (maxLimit && fetchedCount >= maxLimit) {
|
|
7483
7022
|
newsArticles = newsArticles.slice(0, maxLimit);
|
|
7484
7023
|
break;
|
|
@@ -7509,11 +7048,11 @@ const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
|
7509
7048
|
*/
|
|
7510
7049
|
function formatCurrency(value) {
|
|
7511
7050
|
if (isNaN(value)) {
|
|
7512
|
-
return
|
|
7051
|
+
return '$0.00';
|
|
7513
7052
|
}
|
|
7514
|
-
return new Intl.NumberFormat(
|
|
7515
|
-
style:
|
|
7516
|
-
currency:
|
|
7053
|
+
return new Intl.NumberFormat('en-US', {
|
|
7054
|
+
style: 'currency',
|
|
7055
|
+
currency: 'USD',
|
|
7517
7056
|
}).format(value);
|
|
7518
7057
|
}
|
|
7519
7058
|
/**
|
|
@@ -7526,13 +7065,13 @@ function formatCurrency(value) {
|
|
|
7526
7065
|
*/
|
|
7527
7066
|
function formatNumber(value) {
|
|
7528
7067
|
if (isNaN(value)) {
|
|
7529
|
-
return
|
|
7068
|
+
return '0';
|
|
7530
7069
|
}
|
|
7531
|
-
return new Intl.NumberFormat(
|
|
7070
|
+
return new Intl.NumberFormat('en-US').format(value);
|
|
7532
7071
|
}
|
|
7533
7072
|
|
|
7534
7073
|
const log = (message) => {
|
|
7535
|
-
console.log(`[${new Date().toLocaleString(
|
|
7074
|
+
console.log(`[${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })}] ${message}`);
|
|
7536
7075
|
};
|
|
7537
7076
|
// async function testCreateEquitiesTrade() {
|
|
7538
7077
|
// try {
|
|
@@ -7756,18 +7295,18 @@ const log = (message) => {
|
|
|
7756
7295
|
// testing retrieving pre-market data (just 9:00am to 9:30am on 1 july 2025 for SPY) using the market data api
|
|
7757
7296
|
async function testPreMarketData() {
|
|
7758
7297
|
try {
|
|
7759
|
-
log(
|
|
7298
|
+
log('Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...');
|
|
7760
7299
|
// Set up the time range in America/New_York, convert to UTC ISO strings
|
|
7761
|
-
const symbol =
|
|
7762
|
-
const nyTimeZone =
|
|
7300
|
+
const symbol = 'SPY';
|
|
7301
|
+
const nyTimeZone = 'America/New_York';
|
|
7763
7302
|
// 9:00am and 9:30am in NY time
|
|
7764
|
-
const startNY = new Date(
|
|
7765
|
-
const endNY = new Date(
|
|
7303
|
+
const startNY = new Date('2025-07-01T09:00:00-04:00');
|
|
7304
|
+
const endNY = new Date('2025-07-01T09:30:00-04:00');
|
|
7766
7305
|
const startUTC = startNY.toISOString();
|
|
7767
7306
|
const endUTC = endNY.toISOString();
|
|
7768
7307
|
const barsResponse = await marketDataAPI.getHistoricalBars({
|
|
7769
7308
|
symbols: [symbol],
|
|
7770
|
-
timeframe:
|
|
7309
|
+
timeframe: '1Min',
|
|
7771
7310
|
start: startUTC,
|
|
7772
7311
|
end: endUTC,
|
|
7773
7312
|
limit: 1000,
|
|
@@ -7775,21 +7314,19 @@ async function testPreMarketData() {
|
|
|
7775
7314
|
const bars = barsResponse.bars[symbol] || [];
|
|
7776
7315
|
log(`Fetched ${bars.length} 1-min bars for SPY from 9:00am to 9:30am (NY) on 2025-07-01.`);
|
|
7777
7316
|
if (bars.length === 0) {
|
|
7778
|
-
log(
|
|
7317
|
+
log('No pre-market bars returned.');
|
|
7779
7318
|
return;
|
|
7780
7319
|
}
|
|
7781
7320
|
// Print each bar
|
|
7782
7321
|
bars.forEach((bar, i) => {
|
|
7783
|
-
const barTime = new Date(bar.t).toLocaleString(
|
|
7784
|
-
timeZone: nyTimeZone,
|
|
7785
|
-
});
|
|
7322
|
+
const barTime = new Date(bar.t).toLocaleString('en-US', { timeZone: nyTimeZone });
|
|
7786
7323
|
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}`);
|
|
7787
7324
|
});
|
|
7788
7325
|
// Print summary
|
|
7789
7326
|
const summary = AlpacaMarketDataAPI.analyzeBars(bars);
|
|
7790
7327
|
if (summary)
|
|
7791
7328
|
log(`Summary: ${summary}`);
|
|
7792
|
-
log(
|
|
7329
|
+
log('Pre-market data test complete.');
|
|
7793
7330
|
}
|
|
7794
7331
|
catch (error) {
|
|
7795
7332
|
log(`❌ Error in testPreMarketData: ${error instanceof Error ? error.message : error}`);
|