@adaptic/utils 0.1.45 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -61
- package/dist/index.cjs +1929 -1443
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1925 -1445
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +288 -170
- package/dist/test.js.map +1 -1
- package/dist/types/__tests__/financial-regression.test.d.ts +2 -0
- package/dist/types/__tests__/financial-regression.test.d.ts.map +1 -0
- package/dist/types/__tests__/price-utils.test.d.ts +2 -0
- package/dist/types/__tests__/price-utils.test.d.ts.map +1 -0
- package/dist/types/adaptic.d.ts +2 -2
- package/dist/types/adaptic.d.ts.map +1 -1
- package/dist/types/alpaca-functions.d.ts +21 -21
- package/dist/types/alpaca-functions.d.ts.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +16 -3
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/alpaca-trading-api.d.ts +11 -11
- 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 -1
- package/dist/types/asset-allocation-algorithm.d.ts.map +1 -1
- package/dist/types/cache/stampede-protected-cache.d.ts.map +1 -1
- package/dist/types/crypto.d.ts +2 -2
- package/dist/types/crypto.d.ts.map +1 -1
- package/dist/types/display-manager.d.ts +1 -1
- package/dist/types/display-manager.d.ts.map +1 -1
- package/dist/types/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 +23 -21
- 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 +2 -2
- 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 -1
- package/dist/types/misc-utils.d.ts.map +1 -1
- package/dist/types/performance-metrics.d.ts +2 -2
- package/dist/types/performance-metrics.d.ts.map +1 -1
- package/dist/types/polygon-indices.d.ts +1 -1
- 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/trading-policy/defaults.d.ts +46 -0
- package/dist/types/trading-policy/defaults.d.ts.map +1 -0
- package/dist/types/trading-policy/index.d.ts +8 -0
- package/dist/types/trading-policy/index.d.ts.map +1 -0
- package/dist/types/types/alpaca-types.d.ts +177 -88
- 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 +1 -1
- 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 -3
package/dist/test.js
CHANGED
|
@@ -493,7 +493,7 @@ createChalk({level: stderrColor ? stderrColor.level : 0});
|
|
|
493
493
|
|
|
494
494
|
class DisplayManager {
|
|
495
495
|
static instance;
|
|
496
|
-
promptText =
|
|
496
|
+
promptText = "";
|
|
497
497
|
constructor() { }
|
|
498
498
|
static getInstance() {
|
|
499
499
|
if (!DisplayManager.instance) {
|
|
@@ -513,20 +513,22 @@ class DisplayManager {
|
|
|
513
513
|
cursorTo(process.stdout, 0);
|
|
514
514
|
// Format the timestamp
|
|
515
515
|
const date = new Date();
|
|
516
|
-
const timestamp = date.toLocaleString(
|
|
516
|
+
const timestamp = date.toLocaleString("en-US", {
|
|
517
|
+
timeZone: "America/New_York",
|
|
518
|
+
});
|
|
517
519
|
const account = options?.account;
|
|
518
520
|
const symbol = options?.symbol;
|
|
519
521
|
// Build the log message
|
|
520
|
-
let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` :
|
|
522
|
+
let logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ""}${account ? ` [${account}] ` : ""}${symbol ? ` [${symbol}] ` : ""}${message}`;
|
|
521
523
|
// Add color based on type
|
|
522
|
-
if (options?.type ===
|
|
524
|
+
if (options?.type === "error") {
|
|
523
525
|
logMessage = chalk.red(logMessage);
|
|
524
526
|
}
|
|
525
|
-
else if (options?.type ===
|
|
527
|
+
else if (options?.type === "warn") {
|
|
526
528
|
logMessage = chalk.yellow(logMessage);
|
|
527
529
|
}
|
|
528
530
|
// Write the log message
|
|
529
|
-
process.stdout.write(logMessage +
|
|
531
|
+
process.stdout.write(logMessage + "\n");
|
|
530
532
|
// Log to file
|
|
531
533
|
if (symbol) {
|
|
532
534
|
// Log to symbol-specific file if symbol is provided
|
|
@@ -545,20 +547,20 @@ class DisplayManager {
|
|
|
545
547
|
writeSymbolLog(symbol, date, logMessage, options) {
|
|
546
548
|
try {
|
|
547
549
|
// Create logs directory if it doesn't exist
|
|
548
|
-
if (!fs.existsSync(
|
|
549
|
-
fs.mkdirSync(
|
|
550
|
+
if (!fs.existsSync("logs")) {
|
|
551
|
+
fs.mkdirSync("logs", { recursive: true });
|
|
550
552
|
}
|
|
551
553
|
// Format date for filename: YYYY-MM-DD
|
|
552
554
|
const year = date.getFullYear();
|
|
553
|
-
const month = String(date.getMonth() + 1).padStart(2,
|
|
554
|
-
const day = String(date.getDate()).padStart(2,
|
|
555
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
556
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
555
557
|
// Create filename: SYM-YYYY-MM-DD.log
|
|
556
558
|
const filename = `${symbol}-${year}-${month}-${day}.log`;
|
|
557
|
-
const filePath = path.join(
|
|
559
|
+
const filePath = path.join("logs", filename);
|
|
558
560
|
// Strip ANSI color codes from log message
|
|
559
|
-
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g,
|
|
561
|
+
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, "");
|
|
560
562
|
// Write to file (append if exists, create if not)
|
|
561
|
-
fs.appendFileSync(filePath, plainLogMessage +
|
|
563
|
+
fs.appendFileSync(filePath, plainLogMessage + "\n");
|
|
562
564
|
}
|
|
563
565
|
catch (error) {
|
|
564
566
|
// Only log to console - don't try to log to file again to avoid potential infinite loop
|
|
@@ -571,21 +573,21 @@ class DisplayManager {
|
|
|
571
573
|
writeGenericLog(date, logMessage, options) {
|
|
572
574
|
try {
|
|
573
575
|
// Create logs directory if it doesn't exist
|
|
574
|
-
if (!fs.existsSync(
|
|
575
|
-
fs.mkdirSync(
|
|
576
|
+
if (!fs.existsSync("logs")) {
|
|
577
|
+
fs.mkdirSync("logs", { recursive: true });
|
|
576
578
|
}
|
|
577
579
|
// Format date for filename: YYYY-MM-DD
|
|
578
580
|
const year = date.getFullYear();
|
|
579
|
-
const month = String(date.getMonth() + 1).padStart(2,
|
|
580
|
-
const day = String(date.getDate()).padStart(2,
|
|
581
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
582
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
581
583
|
// Create filename: system-YYYY-MM-DD.log
|
|
582
|
-
const source = options?.source?.toLowerCase().replace(/\s+/g,
|
|
584
|
+
const source = options?.source?.toLowerCase().replace(/\s+/g, "-") || "system";
|
|
583
585
|
const filename = `${source}-${year}-${month}-${day}.log`;
|
|
584
|
-
const filePath = path.join(
|
|
586
|
+
const filePath = path.join("logs", filename);
|
|
585
587
|
// Strip ANSI color codes from log message
|
|
586
|
-
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g,
|
|
588
|
+
const plainLogMessage = logMessage.replace(/\x1B\[\d+m/g, "");
|
|
587
589
|
// Write to file (append if exists, create if not)
|
|
588
|
-
fs.appendFileSync(filePath, plainLogMessage +
|
|
590
|
+
fs.appendFileSync(filePath, plainLogMessage + "\n");
|
|
589
591
|
}
|
|
590
592
|
catch (error) {
|
|
591
593
|
// Only log to console - don't try to log to file again to avoid potential infinite loop
|
|
@@ -613,7 +615,7 @@ class DisplayManager {
|
|
|
613
615
|
* @param options.symbol The trading symbol associated with this log.
|
|
614
616
|
* @param options.logToFile Force logging to a file even when no symbol is provided.
|
|
615
617
|
*/
|
|
616
|
-
function log$2(message, options = { source:
|
|
618
|
+
function log$2(message, options = { source: "Server", type: "info" }) {
|
|
617
619
|
const displayManager = DisplayManager.getInstance();
|
|
618
620
|
displayManager.log(message, options);
|
|
619
621
|
}
|
|
@@ -621,104 +623,104 @@ function log$2(message, options = { source: 'Server', type: 'info' }) {
|
|
|
621
623
|
// market-hours.ts
|
|
622
624
|
const marketHolidays = {
|
|
623
625
|
2024: {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
626
|
+
"New Year's Day": { date: "2024-01-01" },
|
|
627
|
+
"Martin Luther King, Jr. Day": { date: "2024-01-15" },
|
|
628
|
+
"Washington's Birthday": { date: "2024-02-19" },
|
|
629
|
+
"Good Friday": { date: "2024-03-29" },
|
|
630
|
+
"Memorial Day": { date: "2024-05-27" },
|
|
631
|
+
"Juneteenth National Independence Day": { date: "2024-06-19" },
|
|
632
|
+
"Independence Day": { date: "2024-07-04" },
|
|
633
|
+
"Labor Day": { date: "2024-09-02" },
|
|
634
|
+
"Thanksgiving Day": { date: "2024-11-28" },
|
|
635
|
+
"Christmas Day": { date: "2024-12-25" },
|
|
634
636
|
},
|
|
635
637
|
2025: {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
638
|
+
"New Year's Day": { date: "2025-01-01" },
|
|
639
|
+
"Jimmy Carter Memorial Day": { date: "2025-01-09" },
|
|
640
|
+
"Martin Luther King, Jr. Day": { date: "2025-01-20" },
|
|
641
|
+
"Washington's Birthday": { date: "2025-02-17" },
|
|
642
|
+
"Good Friday": { date: "2025-04-18" },
|
|
643
|
+
"Memorial Day": { date: "2025-05-26" },
|
|
644
|
+
"Juneteenth National Independence Day": { date: "2025-06-19" },
|
|
645
|
+
"Independence Day": { date: "2025-07-04" },
|
|
646
|
+
"Labor Day": { date: "2025-09-01" },
|
|
647
|
+
"Thanksgiving Day": { date: "2025-11-27" },
|
|
648
|
+
"Christmas Day": { date: "2025-12-25" },
|
|
647
649
|
},
|
|
648
650
|
2026: {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
651
|
+
"New Year's Day": { date: "2026-01-01" },
|
|
652
|
+
"Martin Luther King, Jr. Day": { date: "2026-01-19" },
|
|
653
|
+
"Washington's Birthday": { date: "2026-02-16" },
|
|
654
|
+
"Good Friday": { date: "2026-04-03" },
|
|
655
|
+
"Memorial Day": { date: "2026-05-25" },
|
|
656
|
+
"Juneteenth National Independence Day": { date: "2026-06-19" },
|
|
657
|
+
"Independence Day": { date: "2026-07-03" },
|
|
658
|
+
"Labor Day": { date: "2026-09-07" },
|
|
659
|
+
"Thanksgiving Day": { date: "2026-11-26" },
|
|
660
|
+
"Christmas Day": { date: "2026-12-25" },
|
|
661
|
+
},
|
|
660
662
|
};
|
|
661
663
|
const marketEarlyCloses = {
|
|
662
664
|
2024: {
|
|
663
|
-
|
|
664
|
-
date:
|
|
665
|
-
time:
|
|
666
|
-
optionsTime:
|
|
667
|
-
notes:
|
|
665
|
+
"2024-07-03": {
|
|
666
|
+
date: "2024-07-03",
|
|
667
|
+
time: "13:00",
|
|
668
|
+
optionsTime: "13:15",
|
|
669
|
+
notes: "Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
668
670
|
},
|
|
669
|
-
|
|
670
|
-
date:
|
|
671
|
-
time:
|
|
672
|
-
optionsTime:
|
|
673
|
-
notes:
|
|
671
|
+
"2024-11-29": {
|
|
672
|
+
date: "2024-11-29",
|
|
673
|
+
time: "13:00",
|
|
674
|
+
optionsTime: "13:15",
|
|
675
|
+
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.",
|
|
676
|
+
},
|
|
677
|
+
"2024-12-24": {
|
|
678
|
+
date: "2024-12-24",
|
|
679
|
+
time: "13:00",
|
|
680
|
+
optionsTime: "13:15",
|
|
681
|
+
notes: "Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
674
682
|
},
|
|
675
|
-
'2024-12-24': {
|
|
676
|
-
date: '2024-12-24',
|
|
677
|
-
time: '13:00',
|
|
678
|
-
optionsTime: '13:15',
|
|
679
|
-
notes: 'Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
680
|
-
}
|
|
681
683
|
},
|
|
682
684
|
2025: {
|
|
683
|
-
|
|
684
|
-
date:
|
|
685
|
-
time:
|
|
686
|
-
optionsTime:
|
|
687
|
-
notes:
|
|
685
|
+
"2025-07-03": {
|
|
686
|
+
date: "2025-07-03",
|
|
687
|
+
time: "13:00",
|
|
688
|
+
optionsTime: "13:15",
|
|
689
|
+
notes: "Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
688
690
|
},
|
|
689
|
-
|
|
690
|
-
date:
|
|
691
|
-
time:
|
|
692
|
-
optionsTime:
|
|
693
|
-
notes:
|
|
691
|
+
"2025-11-28": {
|
|
692
|
+
date: "2025-11-28",
|
|
693
|
+
time: "13:00",
|
|
694
|
+
optionsTime: "13:15",
|
|
695
|
+
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.",
|
|
696
|
+
},
|
|
697
|
+
"2025-12-24": {
|
|
698
|
+
date: "2025-12-24",
|
|
699
|
+
time: "13:00",
|
|
700
|
+
optionsTime: "13:15",
|
|
701
|
+
notes: "Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
694
702
|
},
|
|
695
|
-
'2025-12-24': {
|
|
696
|
-
date: '2025-12-24',
|
|
697
|
-
time: '13:00',
|
|
698
|
-
optionsTime: '13:15',
|
|
699
|
-
notes: 'Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.'
|
|
700
|
-
}
|
|
701
703
|
},
|
|
702
704
|
2026: {
|
|
703
|
-
|
|
704
|
-
date:
|
|
705
|
-
time:
|
|
706
|
-
optionsTime:
|
|
707
|
-
notes:
|
|
705
|
+
"2026-07-02": {
|
|
706
|
+
date: "2026-07-02",
|
|
707
|
+
time: "13:00",
|
|
708
|
+
optionsTime: "13:15",
|
|
709
|
+
notes: "Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
708
710
|
},
|
|
709
|
-
|
|
710
|
-
date:
|
|
711
|
-
time:
|
|
712
|
-
optionsTime:
|
|
713
|
-
notes:
|
|
711
|
+
"2026-11-27": {
|
|
712
|
+
date: "2026-11-27",
|
|
713
|
+
time: "13:00",
|
|
714
|
+
optionsTime: "13:15",
|
|
715
|
+
notes: "Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
714
716
|
},
|
|
715
|
-
|
|
716
|
-
date:
|
|
717
|
-
time:
|
|
718
|
-
optionsTime:
|
|
719
|
-
notes:
|
|
720
|
-
}
|
|
721
|
-
}
|
|
717
|
+
"2026-12-24": {
|
|
718
|
+
date: "2026-12-24",
|
|
719
|
+
time: "13:00",
|
|
720
|
+
optionsTime: "13:15",
|
|
721
|
+
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.",
|
|
722
|
+
},
|
|
723
|
+
},
|
|
722
724
|
};
|
|
723
725
|
|
|
724
726
|
// market-time.ts
|
|
@@ -731,9 +733,15 @@ const marketEarlyCloses = {
|
|
|
731
733
|
* Early extended market hours are 1:00pm-5:00pm on early close days
|
|
732
734
|
*/
|
|
733
735
|
const MARKET_TIMES = {
|
|
734
|
-
TIMEZONE:
|
|
735
|
-
REGULAR: {
|
|
736
|
-
|
|
736
|
+
TIMEZONE: "America/New_York",
|
|
737
|
+
REGULAR: {
|
|
738
|
+
START: { HOUR: 9, MINUTE: 30},
|
|
739
|
+
END: { HOUR: 16, MINUTE: 0},
|
|
740
|
+
},
|
|
741
|
+
EXTENDED: {
|
|
742
|
+
START: { HOUR: 4, MINUTE: 0},
|
|
743
|
+
END: { HOUR: 20, MINUTE: 0},
|
|
744
|
+
},
|
|
737
745
|
};
|
|
738
746
|
/**
|
|
739
747
|
* Utility class for handling market time-related operations
|
|
@@ -746,7 +754,7 @@ class MarketTimeUtil {
|
|
|
746
754
|
* @param {string} [timezone='America/New_York'] - The timezone to use for market time calculations
|
|
747
755
|
* @param {IntradayReporting} [intradayReporting='market_hours'] - The intraday reporting mode
|
|
748
756
|
*/
|
|
749
|
-
constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting =
|
|
757
|
+
constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting = "market_hours") {
|
|
750
758
|
this.validateTimezone(timezone);
|
|
751
759
|
this.timezone = timezone;
|
|
752
760
|
this.intradayReporting = intradayReporting;
|
|
@@ -765,13 +773,13 @@ class MarketTimeUtil {
|
|
|
765
773
|
throw new Error(`Invalid timezone: ${timezone}`);
|
|
766
774
|
}
|
|
767
775
|
}
|
|
768
|
-
formatDate(date, outputFormat =
|
|
776
|
+
formatDate(date, outputFormat = "iso") {
|
|
769
777
|
switch (outputFormat) {
|
|
770
|
-
case
|
|
778
|
+
case "unix-seconds":
|
|
771
779
|
return Math.floor(date.getTime() / 1000);
|
|
772
|
-
case
|
|
780
|
+
case "unix-ms":
|
|
773
781
|
return date.getTime();
|
|
774
|
-
case
|
|
782
|
+
case "iso":
|
|
775
783
|
default:
|
|
776
784
|
// return with timezone offset
|
|
777
785
|
return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
@@ -782,7 +790,7 @@ class MarketTimeUtil {
|
|
|
782
790
|
return day === 0 || day === 6;
|
|
783
791
|
}
|
|
784
792
|
isHoliday(date) {
|
|
785
|
-
const formattedDate = format(date,
|
|
793
|
+
const formattedDate = format(date, "yyyy-MM-dd");
|
|
786
794
|
const yearHolidays = marketHolidays[date.getFullYear()];
|
|
787
795
|
for (const holiday in yearHolidays) {
|
|
788
796
|
if (yearHolidays[holiday].date === formattedDate) {
|
|
@@ -792,7 +800,7 @@ class MarketTimeUtil {
|
|
|
792
800
|
return false;
|
|
793
801
|
}
|
|
794
802
|
isEarlyCloseDay(date) {
|
|
795
|
-
const formattedDate = format(date,
|
|
803
|
+
const formattedDate = format(date, "yyyy-MM-dd");
|
|
796
804
|
const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
|
|
797
805
|
return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
|
|
798
806
|
}
|
|
@@ -802,10 +810,12 @@ class MarketTimeUtil {
|
|
|
802
810
|
* @returns The early close time in minutes from midnight, or null if there is no early close
|
|
803
811
|
*/
|
|
804
812
|
getEarlyCloseTime(date) {
|
|
805
|
-
const formattedDate = format(date,
|
|
813
|
+
const formattedDate = format(date, "yyyy-MM-dd");
|
|
806
814
|
const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
|
|
807
815
|
if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
|
|
808
|
-
const [hours, minutes] = yearEarlyCloses[formattedDate].time
|
|
816
|
+
const [hours, minutes] = yearEarlyCloses[formattedDate].time
|
|
817
|
+
.split(":")
|
|
818
|
+
.map(Number);
|
|
809
819
|
return hours * 60 + minutes;
|
|
810
820
|
}
|
|
811
821
|
return null;
|
|
@@ -842,23 +852,30 @@ class MarketTimeUtil {
|
|
|
842
852
|
// Regular market hours logic
|
|
843
853
|
let returner;
|
|
844
854
|
switch (this.intradayReporting) {
|
|
845
|
-
case
|
|
846
|
-
const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
847
|
-
|
|
855
|
+
case "extended_hours": {
|
|
856
|
+
const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
857
|
+
MARKET_TIMES.EXTENDED.START.MINUTE;
|
|
858
|
+
const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 +
|
|
859
|
+
MARKET_TIMES.EXTENDED.END.MINUTE;
|
|
848
860
|
// Comprehensive handling of times crossing midnight
|
|
849
861
|
const adjustedDate = timeInMinutes < extendedStartMinutes ? sub(date, { days: 1 }) : date;
|
|
850
862
|
const adjustedTimeInMinutes = adjustedDate.getHours() * 60 + adjustedDate.getMinutes();
|
|
851
|
-
returner =
|
|
863
|
+
returner =
|
|
864
|
+
adjustedTimeInMinutes >= extendedStartMinutes &&
|
|
865
|
+
adjustedTimeInMinutes <= extendedEndMinutes;
|
|
852
866
|
break;
|
|
853
867
|
}
|
|
854
|
-
case
|
|
868
|
+
case "continuous":
|
|
855
869
|
returner = true;
|
|
856
870
|
break;
|
|
857
871
|
default: {
|
|
858
872
|
// market_hours
|
|
859
|
-
const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 +
|
|
873
|
+
const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 +
|
|
874
|
+
MARKET_TIMES.REGULAR.START.MINUTE;
|
|
860
875
|
const regularEndMinutes = MARKET_TIMES.REGULAR.END.HOUR * 60 + MARKET_TIMES.REGULAR.END.MINUTE;
|
|
861
|
-
returner =
|
|
876
|
+
returner =
|
|
877
|
+
timeInMinutes >= regularStartMinutes &&
|
|
878
|
+
timeInMinutes <= regularEndMinutes;
|
|
862
879
|
break;
|
|
863
880
|
}
|
|
864
881
|
}
|
|
@@ -871,9 +888,11 @@ class MarketTimeUtil {
|
|
|
871
888
|
*/
|
|
872
889
|
isBeforeMarketHours(date) {
|
|
873
890
|
const timeInMinutes = date.getHours() * 60 + date.getMinutes();
|
|
874
|
-
const startMinutes = this.intradayReporting ===
|
|
875
|
-
? MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
876
|
-
|
|
891
|
+
const startMinutes = this.intradayReporting === "extended_hours"
|
|
892
|
+
? MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
893
|
+
MARKET_TIMES.EXTENDED.START.MINUTE
|
|
894
|
+
: MARKET_TIMES.REGULAR.START.HOUR * 60 +
|
|
895
|
+
MARKET_TIMES.REGULAR.START.MINUTE;
|
|
877
896
|
return timeInMinutes < startMinutes;
|
|
878
897
|
}
|
|
879
898
|
/**
|
|
@@ -945,7 +964,7 @@ class MarketTimeUtil {
|
|
|
945
964
|
let start;
|
|
946
965
|
let end;
|
|
947
966
|
switch (this.intradayReporting) {
|
|
948
|
-
case
|
|
967
|
+
case "extended_hours": {
|
|
949
968
|
start = set(date, {
|
|
950
969
|
hours: MARKET_TIMES.EXTENDED.START.HOUR,
|
|
951
970
|
minutes: MARKET_TIMES.EXTENDED.START.MINUTE,
|
|
@@ -960,7 +979,7 @@ class MarketTimeUtil {
|
|
|
960
979
|
});
|
|
961
980
|
break;
|
|
962
981
|
}
|
|
963
|
-
case
|
|
982
|
+
case "continuous": {
|
|
964
983
|
start = startOfDay(date);
|
|
965
984
|
end = endOfDay(date);
|
|
966
985
|
break;
|
|
@@ -1002,31 +1021,31 @@ class MarketTimeUtil {
|
|
|
1002
1021
|
calculatePeriodStartDate(endDate, period) {
|
|
1003
1022
|
let startDate;
|
|
1004
1023
|
switch (period) {
|
|
1005
|
-
case
|
|
1024
|
+
case "YTD":
|
|
1006
1025
|
startDate = set(endDate, { month: 0, date: 1 });
|
|
1007
1026
|
break;
|
|
1008
|
-
case
|
|
1027
|
+
case "1D":
|
|
1009
1028
|
startDate = this.getLastMarketDay(endDate);
|
|
1010
1029
|
break;
|
|
1011
|
-
case
|
|
1030
|
+
case "3D":
|
|
1012
1031
|
startDate = sub(endDate, { days: 3 });
|
|
1013
1032
|
break;
|
|
1014
|
-
case
|
|
1033
|
+
case "1W":
|
|
1015
1034
|
startDate = sub(endDate, { weeks: 1 });
|
|
1016
1035
|
break;
|
|
1017
|
-
case
|
|
1036
|
+
case "2W":
|
|
1018
1037
|
startDate = sub(endDate, { weeks: 2 });
|
|
1019
1038
|
break;
|
|
1020
|
-
case
|
|
1039
|
+
case "1M":
|
|
1021
1040
|
startDate = sub(endDate, { months: 1 });
|
|
1022
1041
|
break;
|
|
1023
|
-
case
|
|
1042
|
+
case "3M":
|
|
1024
1043
|
startDate = sub(endDate, { months: 3 });
|
|
1025
1044
|
break;
|
|
1026
|
-
case
|
|
1045
|
+
case "6M":
|
|
1027
1046
|
startDate = sub(endDate, { months: 6 });
|
|
1028
1047
|
break;
|
|
1029
|
-
case
|
|
1048
|
+
case "1Y":
|
|
1030
1049
|
startDate = sub(endDate, { years: 1 });
|
|
1031
1050
|
break;
|
|
1032
1051
|
default:
|
|
@@ -1037,9 +1056,9 @@ class MarketTimeUtil {
|
|
|
1037
1056
|
}
|
|
1038
1057
|
return startDate;
|
|
1039
1058
|
}
|
|
1040
|
-
getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat =
|
|
1059
|
+
getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat = "iso", }) {
|
|
1041
1060
|
if (!period) {
|
|
1042
|
-
throw new Error(
|
|
1061
|
+
throw new Error("Period is required");
|
|
1043
1062
|
}
|
|
1044
1063
|
if (intraday_reporting) {
|
|
1045
1064
|
this.intradayReporting = intraday_reporting;
|
|
@@ -1084,7 +1103,7 @@ class MarketTimeUtil {
|
|
|
1084
1103
|
const utcEnd = fromZonedTime(endDate, this.timezone);
|
|
1085
1104
|
// Ensure start is not after end
|
|
1086
1105
|
if (isBefore(utcEnd, utcStart)) {
|
|
1087
|
-
throw new Error(
|
|
1106
|
+
throw new Error("Start date cannot be after end date");
|
|
1088
1107
|
}
|
|
1089
1108
|
return {
|
|
1090
1109
|
start: this.formatDate(utcStart, outputFormat),
|
|
@@ -1128,10 +1147,22 @@ class MarketTimeUtil {
|
|
|
1128
1147
|
};
|
|
1129
1148
|
}
|
|
1130
1149
|
}
|
|
1131
|
-
const open = fromZonedTime(set(dayStart, {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1150
|
+
const open = fromZonedTime(set(dayStart, {
|
|
1151
|
+
hours: regularOpenTime.HOUR,
|
|
1152
|
+
minutes: regularOpenTime.MINUTE,
|
|
1153
|
+
}), this.timezone);
|
|
1154
|
+
const close = fromZonedTime(set(dayStart, {
|
|
1155
|
+
hours: regularCloseTime.HOUR,
|
|
1156
|
+
minutes: regularCloseTime.MINUTE,
|
|
1157
|
+
}), this.timezone);
|
|
1158
|
+
const openExt = fromZonedTime(set(dayStart, {
|
|
1159
|
+
hours: extendedOpenTime.HOUR,
|
|
1160
|
+
minutes: extendedOpenTime.MINUTE,
|
|
1161
|
+
}), this.timezone);
|
|
1162
|
+
const closeExt = fromZonedTime(set(dayStart, {
|
|
1163
|
+
hours: extendedCloseTime.HOUR,
|
|
1164
|
+
minutes: extendedCloseTime.MINUTE,
|
|
1165
|
+
}), this.timezone);
|
|
1135
1166
|
return {
|
|
1136
1167
|
marketOpen: true,
|
|
1137
1168
|
open,
|
|
@@ -1154,7 +1185,7 @@ function getLastFullTradingDate(currentDate = new Date()) {
|
|
|
1154
1185
|
// Format the date in NY timezone to ensure consistency
|
|
1155
1186
|
return {
|
|
1156
1187
|
date,
|
|
1157
|
-
YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE,
|
|
1188
|
+
YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE, "yyyy-MM-dd"),
|
|
1158
1189
|
};
|
|
1159
1190
|
}
|
|
1160
1191
|
|
|
@@ -6147,22 +6178,28 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6147
6178
|
v1beta1url;
|
|
6148
6179
|
stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip'; // production values
|
|
6149
6180
|
optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // production values
|
|
6181
|
+
cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us'; // production values
|
|
6150
6182
|
stockWs = null;
|
|
6151
6183
|
optionWs = null;
|
|
6184
|
+
cryptoWs = null;
|
|
6152
6185
|
stockSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6153
6186
|
optionSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6187
|
+
cryptoSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6154
6188
|
setMode(mode = 'production') {
|
|
6155
6189
|
if (mode === 'sandbox') { // sandbox mode
|
|
6156
6190
|
this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
|
|
6157
6191
|
this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
|
|
6192
|
+
this.cryptoStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/crypto/us';
|
|
6158
6193
|
}
|
|
6159
6194
|
else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
|
|
6160
6195
|
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
|
|
6161
6196
|
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // there's no test mode for options
|
|
6197
|
+
this.cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us'; // there's no test mode for crypto
|
|
6162
6198
|
}
|
|
6163
6199
|
else { // production
|
|
6164
6200
|
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
|
|
6165
6201
|
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
|
|
6202
|
+
this.cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us';
|
|
6166
6203
|
}
|
|
6167
6204
|
}
|
|
6168
6205
|
getMode() {
|
|
@@ -6204,14 +6241,26 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6204
6241
|
return super.emit(event, ...args);
|
|
6205
6242
|
}
|
|
6206
6243
|
connect(streamType) {
|
|
6207
|
-
|
|
6244
|
+
let url;
|
|
6245
|
+
if (streamType === 'stock') {
|
|
6246
|
+
url = this.stockStreamUrl;
|
|
6247
|
+
}
|
|
6248
|
+
else if (streamType === 'option') {
|
|
6249
|
+
url = this.optionStreamUrl;
|
|
6250
|
+
}
|
|
6251
|
+
else {
|
|
6252
|
+
url = this.cryptoStreamUrl;
|
|
6253
|
+
}
|
|
6208
6254
|
const ws = new WebSocket(url);
|
|
6209
6255
|
if (streamType === 'stock') {
|
|
6210
6256
|
this.stockWs = ws;
|
|
6211
6257
|
}
|
|
6212
|
-
else {
|
|
6258
|
+
else if (streamType === 'option') {
|
|
6213
6259
|
this.optionWs = ws;
|
|
6214
6260
|
}
|
|
6261
|
+
else {
|
|
6262
|
+
this.cryptoWs = ws;
|
|
6263
|
+
}
|
|
6215
6264
|
ws.on('open', () => {
|
|
6216
6265
|
log$1(`${streamType} stream connected`, { type: 'info' });
|
|
6217
6266
|
const authMessage = {
|
|
@@ -6222,7 +6271,6 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6222
6271
|
ws.send(JSON.stringify(authMessage));
|
|
6223
6272
|
});
|
|
6224
6273
|
ws.on('message', (data) => {
|
|
6225
|
-
//log(`RAW MESSASGE: ${data.toString()}`);
|
|
6226
6274
|
const messages = JSON.parse(data.toString());
|
|
6227
6275
|
for (const message of messages) {
|
|
6228
6276
|
if (message.T === 'success' && message.msg === 'authenticated') {
|
|
@@ -6230,7 +6278,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6230
6278
|
this.sendSubscription(streamType);
|
|
6231
6279
|
}
|
|
6232
6280
|
else if (message.T === 'error') {
|
|
6233
|
-
log$1(`${streamType} stream error: ${message.msg}`, { type: 'error' });
|
|
6281
|
+
log$1(`${streamType} stream error: ${message.msg} (code: ${message.code})`, { type: 'error' });
|
|
6234
6282
|
}
|
|
6235
6283
|
else if (message.S) {
|
|
6236
6284
|
super.emit(`${streamType}-${message.T}`, message);
|
|
@@ -6243,9 +6291,12 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6243
6291
|
if (streamType === 'stock') {
|
|
6244
6292
|
this.stockWs = null;
|
|
6245
6293
|
}
|
|
6246
|
-
else {
|
|
6294
|
+
else if (streamType === 'option') {
|
|
6247
6295
|
this.optionWs = null;
|
|
6248
6296
|
}
|
|
6297
|
+
else {
|
|
6298
|
+
this.cryptoWs = null;
|
|
6299
|
+
}
|
|
6249
6300
|
// Optional: implement reconnect logic
|
|
6250
6301
|
});
|
|
6251
6302
|
ws.on('error', (error) => {
|
|
@@ -6253,8 +6304,20 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6253
6304
|
});
|
|
6254
6305
|
}
|
|
6255
6306
|
sendSubscription(streamType) {
|
|
6256
|
-
|
|
6257
|
-
|
|
6307
|
+
let ws;
|
|
6308
|
+
let subscriptions;
|
|
6309
|
+
if (streamType === 'stock') {
|
|
6310
|
+
ws = this.stockWs;
|
|
6311
|
+
subscriptions = this.stockSubscriptions;
|
|
6312
|
+
}
|
|
6313
|
+
else if (streamType === 'option') {
|
|
6314
|
+
ws = this.optionWs;
|
|
6315
|
+
subscriptions = this.optionSubscriptions;
|
|
6316
|
+
}
|
|
6317
|
+
else {
|
|
6318
|
+
ws = this.cryptoWs;
|
|
6319
|
+
subscriptions = this.cryptoSubscriptions;
|
|
6320
|
+
}
|
|
6258
6321
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
6259
6322
|
const subMessagePayload = {};
|
|
6260
6323
|
if (subscriptions.trades.length > 0) {
|
|
@@ -6285,6 +6348,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6285
6348
|
this.connect('option');
|
|
6286
6349
|
}
|
|
6287
6350
|
}
|
|
6351
|
+
connectCryptoStream() {
|
|
6352
|
+
if (!this.cryptoWs) {
|
|
6353
|
+
this.connect('crypto');
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6288
6356
|
disconnectStockStream() {
|
|
6289
6357
|
if (this.stockWs) {
|
|
6290
6358
|
this.stockWs.close();
|
|
@@ -6295,8 +6363,38 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6295
6363
|
this.optionWs.close();
|
|
6296
6364
|
}
|
|
6297
6365
|
}
|
|
6366
|
+
disconnectCryptoStream() {
|
|
6367
|
+
if (this.cryptoWs) {
|
|
6368
|
+
this.cryptoWs.close();
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
/**
|
|
6372
|
+
* Check if a specific stream is connected
|
|
6373
|
+
* @param streamType - The type of stream to check
|
|
6374
|
+
* @returns True if the stream is connected
|
|
6375
|
+
*/
|
|
6376
|
+
isStreamConnected(streamType) {
|
|
6377
|
+
if (streamType === 'stock') {
|
|
6378
|
+
return this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN;
|
|
6379
|
+
}
|
|
6380
|
+
else if (streamType === 'option') {
|
|
6381
|
+
return this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN;
|
|
6382
|
+
}
|
|
6383
|
+
else {
|
|
6384
|
+
return this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN;
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6298
6387
|
subscribe(streamType, subscriptions) {
|
|
6299
|
-
|
|
6388
|
+
let currentSubscriptions;
|
|
6389
|
+
if (streamType === 'stock') {
|
|
6390
|
+
currentSubscriptions = this.stockSubscriptions;
|
|
6391
|
+
}
|
|
6392
|
+
else if (streamType === 'option') {
|
|
6393
|
+
currentSubscriptions = this.optionSubscriptions;
|
|
6394
|
+
}
|
|
6395
|
+
else {
|
|
6396
|
+
currentSubscriptions = this.cryptoSubscriptions;
|
|
6397
|
+
}
|
|
6300
6398
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6301
6399
|
if (value) {
|
|
6302
6400
|
currentSubscriptions[key] = [...new Set([...(currentSubscriptions[key] || []), ...value])];
|
|
@@ -6305,7 +6403,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6305
6403
|
this.sendSubscription(streamType);
|
|
6306
6404
|
}
|
|
6307
6405
|
unsubscribe(streamType, subscriptions) {
|
|
6308
|
-
|
|
6406
|
+
let currentSubscriptions;
|
|
6407
|
+
if (streamType === 'stock') {
|
|
6408
|
+
currentSubscriptions = this.stockSubscriptions;
|
|
6409
|
+
}
|
|
6410
|
+
else if (streamType === 'option') {
|
|
6411
|
+
currentSubscriptions = this.optionSubscriptions;
|
|
6412
|
+
}
|
|
6413
|
+
else {
|
|
6414
|
+
currentSubscriptions = this.cryptoSubscriptions;
|
|
6415
|
+
}
|
|
6309
6416
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6310
6417
|
if (value) {
|
|
6311
6418
|
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
|
|
@@ -6315,7 +6422,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6315
6422
|
action: 'unsubscribe',
|
|
6316
6423
|
...subscriptions,
|
|
6317
6424
|
};
|
|
6318
|
-
|
|
6425
|
+
let ws;
|
|
6426
|
+
if (streamType === 'stock') {
|
|
6427
|
+
ws = this.stockWs;
|
|
6428
|
+
}
|
|
6429
|
+
else if (streamType === 'option') {
|
|
6430
|
+
ws = this.optionWs;
|
|
6431
|
+
}
|
|
6432
|
+
else {
|
|
6433
|
+
ws = this.cryptoWs;
|
|
6434
|
+
}
|
|
6319
6435
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
6320
6436
|
ws.send(JSON.stringify(unsubMessage));
|
|
6321
6437
|
}
|
|
@@ -7048,11 +7164,11 @@ const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
|
7048
7164
|
*/
|
|
7049
7165
|
function formatCurrency(value) {
|
|
7050
7166
|
if (isNaN(value)) {
|
|
7051
|
-
return
|
|
7167
|
+
return "$0.00";
|
|
7052
7168
|
}
|
|
7053
|
-
return new Intl.NumberFormat(
|
|
7054
|
-
style:
|
|
7055
|
-
currency:
|
|
7169
|
+
return new Intl.NumberFormat("en-US", {
|
|
7170
|
+
style: "currency",
|
|
7171
|
+
currency: "USD",
|
|
7056
7172
|
}).format(value);
|
|
7057
7173
|
}
|
|
7058
7174
|
/**
|
|
@@ -7065,13 +7181,13 @@ function formatCurrency(value) {
|
|
|
7065
7181
|
*/
|
|
7066
7182
|
function formatNumber(value) {
|
|
7067
7183
|
if (isNaN(value)) {
|
|
7068
|
-
return
|
|
7184
|
+
return "0";
|
|
7069
7185
|
}
|
|
7070
|
-
return new Intl.NumberFormat(
|
|
7186
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
7071
7187
|
}
|
|
7072
7188
|
|
|
7073
7189
|
const log = (message) => {
|
|
7074
|
-
console.log(`[${new Date().toLocaleString(
|
|
7190
|
+
console.log(`[${new Date().toLocaleString("en-US", { timeZone: "America/New_York" })}] ${message}`);
|
|
7075
7191
|
};
|
|
7076
7192
|
// async function testCreateEquitiesTrade() {
|
|
7077
7193
|
// try {
|
|
@@ -7295,18 +7411,18 @@ const log = (message) => {
|
|
|
7295
7411
|
// testing retrieving pre-market data (just 9:00am to 9:30am on 1 july 2025 for SPY) using the market data api
|
|
7296
7412
|
async function testPreMarketData() {
|
|
7297
7413
|
try {
|
|
7298
|
-
log(
|
|
7414
|
+
log("Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...");
|
|
7299
7415
|
// Set up the time range in America/New_York, convert to UTC ISO strings
|
|
7300
|
-
const symbol =
|
|
7301
|
-
const nyTimeZone =
|
|
7416
|
+
const symbol = "SPY";
|
|
7417
|
+
const nyTimeZone = "America/New_York";
|
|
7302
7418
|
// 9:00am and 9:30am in NY time
|
|
7303
|
-
const startNY = new Date(
|
|
7304
|
-
const endNY = new Date(
|
|
7419
|
+
const startNY = new Date("2025-07-01T09:00:00-04:00");
|
|
7420
|
+
const endNY = new Date("2025-07-01T09:30:00-04:00");
|
|
7305
7421
|
const startUTC = startNY.toISOString();
|
|
7306
7422
|
const endUTC = endNY.toISOString();
|
|
7307
7423
|
const barsResponse = await marketDataAPI.getHistoricalBars({
|
|
7308
7424
|
symbols: [symbol],
|
|
7309
|
-
timeframe:
|
|
7425
|
+
timeframe: "1Min",
|
|
7310
7426
|
start: startUTC,
|
|
7311
7427
|
end: endUTC,
|
|
7312
7428
|
limit: 1000,
|
|
@@ -7314,19 +7430,21 @@ async function testPreMarketData() {
|
|
|
7314
7430
|
const bars = barsResponse.bars[symbol] || [];
|
|
7315
7431
|
log(`Fetched ${bars.length} 1-min bars for SPY from 9:00am to 9:30am (NY) on 2025-07-01.`);
|
|
7316
7432
|
if (bars.length === 0) {
|
|
7317
|
-
log(
|
|
7433
|
+
log("No pre-market bars returned.");
|
|
7318
7434
|
return;
|
|
7319
7435
|
}
|
|
7320
7436
|
// Print each bar
|
|
7321
7437
|
bars.forEach((bar, i) => {
|
|
7322
|
-
const barTime = new Date(bar.t).toLocaleString(
|
|
7438
|
+
const barTime = new Date(bar.t).toLocaleString("en-US", {
|
|
7439
|
+
timeZone: nyTimeZone,
|
|
7440
|
+
});
|
|
7323
7441
|
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}`);
|
|
7324
7442
|
});
|
|
7325
7443
|
// Print summary
|
|
7326
7444
|
const summary = AlpacaMarketDataAPI.analyzeBars(bars);
|
|
7327
7445
|
if (summary)
|
|
7328
7446
|
log(`Summary: ${summary}`);
|
|
7329
|
-
log(
|
|
7447
|
+
log("Pre-market data test complete.");
|
|
7330
7448
|
}
|
|
7331
7449
|
catch (error) {
|
|
7332
7450
|
log(`❌ Error in testPreMarketData: ${error instanceof Error ? error.message : error}`);
|