@adaptic/utils 0.1.44 → 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/dist/index.cjs +4558 -59031
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +4554 -58772
- package/dist/index.mjs.map +1 -1
- package/dist/test.js +218 -563
- package/dist/test.js.map +1 -1
- 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 +10 -12
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/alpaca-trading-api.d.ts +2 -2
- package/dist/types/alpaca-trading-api.d.ts.map +1 -1
- package/dist/types/alphavantage.d.ts.map +1 -1
- package/dist/types/asset-allocation-algorithm.d.ts +0 -6
- package/dist/types/asset-allocation-algorithm.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/examples/asset-allocation-example.d.ts +6 -7
- package/dist/types/examples/asset-allocation-example.d.ts.map +1 -1
- package/dist/types/index.d.ts +37 -373
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/market-hours.d.ts.map +1 -1
- package/dist/types/market-time.d.ts +11 -73
- 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 +0 -3
- package/dist/types/misc-utils.d.ts.map +1 -1
- package/dist/types/performance-metrics.d.ts +5 -5
- package/dist/types/performance-metrics.d.ts.map +1 -1
- package/dist/types/polygon-indices.d.ts +2 -2
- package/dist/types/polygon-indices.d.ts.map +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.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/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 +17 -91
- package/dist/types/types/alpaca-types.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/metrics-types.d.ts +1 -1
- package/dist/types/types/metrics-types.d.ts.map +1 -1
- package/package.json +10 -25
- 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__/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__/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
|
@@ -620,80 +620,6 @@ function log$2(message, options = { source: "Server", type: "info" }) {
|
|
|
620
620
|
displayManager.log(message, options);
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
-
/**
|
|
624
|
-
* Centralized API Endpoints Configuration
|
|
625
|
-
*
|
|
626
|
-
* This file defines all Alpaca API base URLs to ensure consistency
|
|
627
|
-
* across the codebase and make updates easier.
|
|
628
|
-
*
|
|
629
|
-
* API Version Guidelines:
|
|
630
|
-
* - Trading API: v2 (stable, production-ready)
|
|
631
|
-
* - Market Data (stocks): v2 (stable)
|
|
632
|
-
* - Market Data (crypto): v1beta3 (latest beta)
|
|
633
|
-
* - Market Data (options): v1beta1 (latest beta)
|
|
634
|
-
* - News API: v1beta1 (latest beta)
|
|
635
|
-
*/
|
|
636
|
-
/**
|
|
637
|
-
* Trading API base URLs (v2)
|
|
638
|
-
* Used for orders, positions, account management
|
|
639
|
-
*/
|
|
640
|
-
const TRADING_API = {
|
|
641
|
-
PAPER: "https://paper-api.alpaca.markets/v2",
|
|
642
|
-
LIVE: "https://api.alpaca.markets/v2",
|
|
643
|
-
};
|
|
644
|
-
/**
|
|
645
|
-
* Get trading API base URL for account type
|
|
646
|
-
*/
|
|
647
|
-
function getTradingApiUrl(accountType) {
|
|
648
|
-
return TRADING_API[accountType];
|
|
649
|
-
}
|
|
650
|
-
/**
|
|
651
|
-
* Market Data API base URLs
|
|
652
|
-
*/
|
|
653
|
-
const MARKET_DATA_API = {
|
|
654
|
-
/** Stock market data (v2) - bars, quotes, trades */
|
|
655
|
-
STOCKS: "https://data.alpaca.markets/v2",
|
|
656
|
-
/** Options market data (v1beta1) */
|
|
657
|
-
OPTIONS: "https://data.alpaca.markets/v1beta1"};
|
|
658
|
-
/**
|
|
659
|
-
* WebSocket stream URLs
|
|
660
|
-
*/
|
|
661
|
-
const WEBSOCKET_STREAMS = {
|
|
662
|
-
/** Stock market data stream (v2) */
|
|
663
|
-
STOCKS: {
|
|
664
|
-
PRODUCTION: "wss://stream.data.alpaca.markets/v2/sip",
|
|
665
|
-
TEST: "wss://stream.data.alpaca.markets/v2/test",
|
|
666
|
-
},
|
|
667
|
-
/** Options market data stream (v1beta3) */
|
|
668
|
-
OPTIONS: {
|
|
669
|
-
PRODUCTION: "wss://stream.data.alpaca.markets/v1beta3/options",
|
|
670
|
-
SANDBOX: "wss://stream.data.sandbox.alpaca.markets/v1beta3/options",
|
|
671
|
-
},
|
|
672
|
-
/** Crypto market data stream (v1beta3) */
|
|
673
|
-
CRYPTO: {
|
|
674
|
-
PRODUCTION: "wss://stream.data.alpaca.markets/v1beta3/crypto/us",
|
|
675
|
-
SANDBOX: "wss://stream.data.sandbox.alpaca.markets/v1beta3/crypto/us",
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
|
-
/**
|
|
679
|
-
* Get stock stream WebSocket URL
|
|
680
|
-
*/
|
|
681
|
-
function getStockStreamUrl(mode = "PRODUCTION") {
|
|
682
|
-
return WEBSOCKET_STREAMS.STOCKS[mode];
|
|
683
|
-
}
|
|
684
|
-
/**
|
|
685
|
-
* Get options stream WebSocket URL
|
|
686
|
-
*/
|
|
687
|
-
function getOptionsStreamUrl(mode = "PRODUCTION") {
|
|
688
|
-
return WEBSOCKET_STREAMS.OPTIONS[mode];
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Get crypto stream WebSocket URL
|
|
692
|
-
*/
|
|
693
|
-
function getCryptoStreamUrl(mode = "PRODUCTION") {
|
|
694
|
-
return WEBSOCKET_STREAMS.CRYPTO[mode];
|
|
695
|
-
}
|
|
696
|
-
|
|
697
623
|
// market-hours.ts
|
|
698
624
|
const marketHolidays = {
|
|
699
625
|
2024: {
|
|
@@ -733,18 +659,6 @@ const marketHolidays = {
|
|
|
733
659
|
"Thanksgiving Day": { date: "2026-11-26" },
|
|
734
660
|
"Christmas Day": { date: "2026-12-25" },
|
|
735
661
|
},
|
|
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
|
-
},
|
|
748
662
|
};
|
|
749
663
|
const marketEarlyCloses = {
|
|
750
664
|
2024: {
|
|
@@ -807,14 +721,6 @@ const marketEarlyCloses = {
|
|
|
807
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.",
|
|
808
722
|
},
|
|
809
723
|
},
|
|
810
|
-
2027: {
|
|
811
|
-
"2027-11-26": {
|
|
812
|
-
date: "2027-11-26",
|
|
813
|
-
time: "13:00",
|
|
814
|
-
optionsTime: "13:15",
|
|
815
|
-
notes: "Market closes early on Friday, November 26, 2027 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE National, and NYSE Texas late trading sessions will close at 5:00 p.m. Eastern Time.",
|
|
816
|
-
},
|
|
817
|
-
},
|
|
818
724
|
};
|
|
819
725
|
|
|
820
726
|
// market-time.ts
|
|
@@ -879,25 +785,13 @@ class MarketTimeUtil {
|
|
|
879
785
|
return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
880
786
|
}
|
|
881
787
|
}
|
|
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();
|
|
788
|
+
isWeekend(date) {
|
|
789
|
+
const day = date.getDay();
|
|
890
790
|
return day === 0 || day === 6;
|
|
891
791
|
}
|
|
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()];
|
|
792
|
+
isHoliday(date) {
|
|
793
|
+
const formattedDate = format(date, "yyyy-MM-dd");
|
|
794
|
+
const yearHolidays = marketHolidays[date.getFullYear()];
|
|
901
795
|
for (const holiday in yearHolidays) {
|
|
902
796
|
if (yearHolidays[holiday].date === formattedDate) {
|
|
903
797
|
return true;
|
|
@@ -905,26 +799,19 @@ class MarketTimeUtil {
|
|
|
905
799
|
}
|
|
906
800
|
return false;
|
|
907
801
|
}
|
|
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()];
|
|
802
|
+
isEarlyCloseDay(date) {
|
|
803
|
+
const formattedDate = format(date, "yyyy-MM-dd");
|
|
804
|
+
const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
|
|
917
805
|
return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
|
|
918
806
|
}
|
|
919
807
|
/**
|
|
920
|
-
*
|
|
921
|
-
*
|
|
922
|
-
* @
|
|
923
|
-
* @returns The early close time in minutes from midnight, or null if not an early close day
|
|
808
|
+
* Get the early close time for a given date
|
|
809
|
+
* @param date - The date to get the early close time for
|
|
810
|
+
* @returns The early close time in minutes from midnight, or null if there is no early close
|
|
924
811
|
*/
|
|
925
|
-
|
|
926
|
-
const formattedDate = format(
|
|
927
|
-
const yearEarlyCloses = marketEarlyCloses[
|
|
812
|
+
getEarlyCloseTime(date) {
|
|
813
|
+
const formattedDate = format(date, "yyyy-MM-dd");
|
|
814
|
+
const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
|
|
928
815
|
if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
|
|
929
816
|
const [hours, minutes] = yearEarlyCloses[formattedDate].time
|
|
930
817
|
.split(":")
|
|
@@ -934,69 +821,30 @@ class MarketTimeUtil {
|
|
|
934
821
|
return null;
|
|
935
822
|
}
|
|
936
823
|
/**
|
|
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)
|
|
824
|
+
* Check if a given date is a market day
|
|
825
|
+
* @param date - The date to check
|
|
969
826
|
* @returns true if the date is a market day, false otherwise
|
|
970
827
|
*/
|
|
971
828
|
isMarketDay(date) {
|
|
972
|
-
const
|
|
973
|
-
|
|
829
|
+
const isWeekendDay = this.isWeekend(date);
|
|
830
|
+
const isHolidayDay = this.isHoliday(date);
|
|
831
|
+
const returner = !isWeekendDay && !isHolidayDay;
|
|
832
|
+
return returner;
|
|
974
833
|
}
|
|
975
834
|
/**
|
|
976
|
-
* Check if a given date is within market hours
|
|
977
|
-
*
|
|
978
|
-
* @param date - The date to check (any timezone)
|
|
835
|
+
* Check if a given date is within market hours
|
|
836
|
+
* @param date - The date to check
|
|
979
837
|
* @returns true if the date is within market hours, false otherwise
|
|
980
838
|
*/
|
|
981
839
|
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)) {
|
|
840
|
+
// Check for holidays first
|
|
841
|
+
if (this.isHoliday(date)) {
|
|
994
842
|
return false;
|
|
995
843
|
}
|
|
996
|
-
const timeInMinutes =
|
|
844
|
+
const timeInMinutes = date.getHours() * 60 + date.getMinutes();
|
|
997
845
|
// Check for early closure
|
|
998
|
-
if (this.
|
|
999
|
-
const earlyCloseMinutes = this.
|
|
846
|
+
if (this.isEarlyCloseDay(date)) {
|
|
847
|
+
const earlyCloseMinutes = this.getEarlyCloseTime(date);
|
|
1000
848
|
if (earlyCloseMinutes !== null && timeInMinutes > earlyCloseMinutes) {
|
|
1001
849
|
return false;
|
|
1002
850
|
}
|
|
@@ -1010,10 +858,8 @@ class MarketTimeUtil {
|
|
|
1010
858
|
const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 +
|
|
1011
859
|
MARKET_TIMES.EXTENDED.END.MINUTE;
|
|
1012
860
|
// Comprehensive handling of times crossing midnight
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
: nyDate;
|
|
1016
|
-
const adjustedTimeInMinutes = adjustedNyDate.getHours() * 60 + adjustedNyDate.getMinutes();
|
|
861
|
+
const adjustedDate = timeInMinutes < extendedStartMinutes ? sub(date, { days: 1 }) : date;
|
|
862
|
+
const adjustedTimeInMinutes = adjustedDate.getHours() * 60 + adjustedDate.getMinutes();
|
|
1017
863
|
returner =
|
|
1018
864
|
adjustedTimeInMinutes >= extendedStartMinutes &&
|
|
1019
865
|
adjustedTimeInMinutes <= extendedEndMinutes;
|
|
@@ -1036,13 +882,12 @@ class MarketTimeUtil {
|
|
|
1036
882
|
return returner;
|
|
1037
883
|
}
|
|
1038
884
|
/**
|
|
1039
|
-
* Check if a
|
|
1040
|
-
*
|
|
1041
|
-
* @param nyDate - Date in market timezone representation
|
|
885
|
+
* Check if a given date is before market hours
|
|
886
|
+
* @param date - The date to check
|
|
1042
887
|
* @returns true if the date is before market hours, false otherwise
|
|
1043
888
|
*/
|
|
1044
|
-
|
|
1045
|
-
const timeInMinutes =
|
|
889
|
+
isBeforeMarketHours(date) {
|
|
890
|
+
const timeInMinutes = date.getHours() * 60 + date.getMinutes();
|
|
1046
891
|
const startMinutes = this.intradayReporting === "extended_hours"
|
|
1047
892
|
? MARKET_TIMES.EXTENDED.START.HOUR * 60 +
|
|
1048
893
|
MARKET_TIMES.EXTENDED.START.MINUTE
|
|
@@ -1057,7 +902,7 @@ class MarketTimeUtil {
|
|
|
1057
902
|
*/
|
|
1058
903
|
getLastTradingDate(currentDate = new Date()) {
|
|
1059
904
|
const nowET = toZonedTime(currentDate, this.timezone);
|
|
1060
|
-
const isMarketDayToday = this.
|
|
905
|
+
const isMarketDayToday = this.isMarketDay(nowET);
|
|
1061
906
|
const currentMinutes = nowET.getHours() * 60 + nowET.getMinutes();
|
|
1062
907
|
const marketOpenMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
|
|
1063
908
|
if (isMarketDayToday && currentMinutes >= marketOpenMinutes) {
|
|
@@ -1067,7 +912,7 @@ class MarketTimeUtil {
|
|
|
1067
912
|
else {
|
|
1068
913
|
// Before market open, or not a market day, return previous trading day
|
|
1069
914
|
let lastTradingDate = sub(nowET, { days: 1 });
|
|
1070
|
-
while (!this.
|
|
915
|
+
while (!this.isMarketDay(lastTradingDate)) {
|
|
1071
916
|
lastTradingDate = sub(lastTradingDate, { days: 1 });
|
|
1072
917
|
}
|
|
1073
918
|
return lastTradingDate;
|
|
@@ -1075,7 +920,7 @@ class MarketTimeUtil {
|
|
|
1075
920
|
}
|
|
1076
921
|
getLastMarketDay(date) {
|
|
1077
922
|
let currentDate = sub(date, { days: 1 });
|
|
1078
|
-
while (!this.
|
|
923
|
+
while (!this.isMarketDay(currentDate)) {
|
|
1079
924
|
currentDate = sub(currentDate, { days: 1 });
|
|
1080
925
|
}
|
|
1081
926
|
return currentDate;
|
|
@@ -1084,7 +929,7 @@ class MarketTimeUtil {
|
|
|
1084
929
|
const nowET = toZonedTime(currentDate, this.timezone);
|
|
1085
930
|
// If today is a market day and we're after extended hours close
|
|
1086
931
|
// then return today since it's a completed trading day
|
|
1087
|
-
if (this.
|
|
932
|
+
if (this.isMarketDay(nowET)) {
|
|
1088
933
|
const timeInMinutes = nowET.getHours() * 60 + nowET.getMinutes();
|
|
1089
934
|
const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
|
|
1090
935
|
// Check if we're after market close (including extended hours)
|
|
@@ -1108,28 +953,13 @@ class MarketTimeUtil {
|
|
|
1108
953
|
* @property {string} yyyymmdd - The date in YYYY-MM-DD format
|
|
1109
954
|
* @property {string} dateISOString - Full ISO date string
|
|
1110
955
|
*/
|
|
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)) {
|
|
956
|
+
getNextMarketDay(date) {
|
|
957
|
+
let currentDate = add(date, { days: 1 });
|
|
958
|
+
while (!this.isMarketDay(currentDate)) {
|
|
1119
959
|
currentDate = add(currentDate, { days: 1 });
|
|
1120
960
|
}
|
|
1121
961
|
return currentDate;
|
|
1122
962
|
}
|
|
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
963
|
getDayBoundaries(date) {
|
|
1134
964
|
let start;
|
|
1135
965
|
let end;
|
|
@@ -1162,9 +992,9 @@ class MarketTimeUtil {
|
|
|
1162
992
|
seconds: 0,
|
|
1163
993
|
milliseconds: 0,
|
|
1164
994
|
});
|
|
1165
|
-
// Check for early close
|
|
1166
|
-
if (this.
|
|
1167
|
-
const earlyCloseMinutes = this.
|
|
995
|
+
// Check for early close
|
|
996
|
+
if (this.isEarlyCloseDay(date)) {
|
|
997
|
+
const earlyCloseMinutes = this.getEarlyCloseTime(date);
|
|
1168
998
|
if (earlyCloseMinutes !== null) {
|
|
1169
999
|
const earlyCloseHours = Math.floor(earlyCloseMinutes / 60);
|
|
1170
1000
|
const earlyCloseMinutesRemainder = earlyCloseMinutes % 60;
|
|
@@ -1221,8 +1051,8 @@ class MarketTimeUtil {
|
|
|
1221
1051
|
default:
|
|
1222
1052
|
throw new Error(`Invalid period: ${period}`);
|
|
1223
1053
|
}
|
|
1224
|
-
while (!this.
|
|
1225
|
-
startDate = this.
|
|
1054
|
+
while (!this.isMarketDay(startDate)) {
|
|
1055
|
+
startDate = this.getNextMarketDay(startDate);
|
|
1226
1056
|
}
|
|
1227
1057
|
return startDate;
|
|
1228
1058
|
}
|
|
@@ -1237,9 +1067,9 @@ class MarketTimeUtil {
|
|
|
1237
1067
|
const zonedEndDate = toZonedTime(end, this.timezone);
|
|
1238
1068
|
let startDate;
|
|
1239
1069
|
let endDate;
|
|
1240
|
-
const isCurrentMarketDay = this.
|
|
1241
|
-
const isWithinHours = this.
|
|
1242
|
-
const isBeforeHours = this.
|
|
1070
|
+
const isCurrentMarketDay = this.isMarketDay(zonedEndDate);
|
|
1071
|
+
const isWithinHours = this.isWithinMarketHours(zonedEndDate);
|
|
1072
|
+
const isBeforeHours = this.isBeforeMarketHours(zonedEndDate);
|
|
1243
1073
|
// First determine the end date based on current market conditions
|
|
1244
1074
|
if (isCurrentMarketDay) {
|
|
1245
1075
|
if (isBeforeHours) {
|
|
@@ -1284,7 +1114,7 @@ class MarketTimeUtil {
|
|
|
1284
1114
|
const { date = new Date() } = options;
|
|
1285
1115
|
const zonedDate = toZonedTime(date, this.timezone);
|
|
1286
1116
|
// Check if market is closed for the day
|
|
1287
|
-
if (this.
|
|
1117
|
+
if (this.isWeekend(zonedDate) || this.isHoliday(zonedDate)) {
|
|
1288
1118
|
return {
|
|
1289
1119
|
marketOpen: false,
|
|
1290
1120
|
open: null,
|
|
@@ -1298,10 +1128,10 @@ class MarketTimeUtil {
|
|
|
1298
1128
|
let regularCloseTime = MARKET_TIMES.REGULAR.END;
|
|
1299
1129
|
const extendedOpenTime = MARKET_TIMES.EXTENDED.START;
|
|
1300
1130
|
let extendedCloseTime = MARKET_TIMES.EXTENDED.END;
|
|
1301
|
-
// Check for early close
|
|
1302
|
-
const isEarlyClose = this.
|
|
1131
|
+
// Check for early close
|
|
1132
|
+
const isEarlyClose = this.isEarlyCloseDay(zonedDate);
|
|
1303
1133
|
if (isEarlyClose) {
|
|
1304
|
-
const earlyCloseMinutes = this.
|
|
1134
|
+
const earlyCloseMinutes = this.getEarlyCloseTime(zonedDate);
|
|
1305
1135
|
if (earlyCloseMinutes !== null) {
|
|
1306
1136
|
// For regular hours, use the early close time
|
|
1307
1137
|
regularCloseTime = {
|
|
@@ -6329,82 +6159,13 @@ function requireWebsocketServer () {
|
|
|
6329
6159
|
|
|
6330
6160
|
requireWebsocketServer();
|
|
6331
6161
|
|
|
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" });
|
|
6162
|
+
const log$1 = (message, options = { type: 'info' }) => {
|
|
6163
|
+
log$2(message, { ...options, source: 'AlpacaMarketDataAPI' });
|
|
6403
6164
|
};
|
|
6404
6165
|
// Default settings for market data API
|
|
6405
|
-
const DEFAULT_ADJUSTMENT =
|
|
6406
|
-
const DEFAULT_FEED =
|
|
6407
|
-
const DEFAULT_CURRENCY =
|
|
6166
|
+
const DEFAULT_ADJUSTMENT = 'all';
|
|
6167
|
+
const DEFAULT_FEED = 'sip';
|
|
6168
|
+
const DEFAULT_CURRENCY = 'USD';
|
|
6408
6169
|
/**
|
|
6409
6170
|
* Singleton class for interacting with Alpaca Market Data API
|
|
6410
6171
|
* Provides methods for fetching historical bars, latest bars, last trades, latest trades, latest quotes, and latest quote for a single symbol
|
|
@@ -6415,84 +6176,56 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6415
6176
|
dataURL;
|
|
6416
6177
|
apiURL;
|
|
6417
6178
|
v1beta1url;
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
optionStreamUrl = getOptionsStreamUrl("PRODUCTION"); // production values
|
|
6422
|
-
cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // production values
|
|
6179
|
+
stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip'; // production values
|
|
6180
|
+
optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // production values
|
|
6181
|
+
cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us'; // production values
|
|
6423
6182
|
stockWs = null;
|
|
6424
6183
|
optionWs = null;
|
|
6425
6184
|
cryptoWs = null;
|
|
6426
|
-
stockSubscriptions = {
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
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");
|
|
6185
|
+
stockSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6186
|
+
optionSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6187
|
+
cryptoSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
6188
|
+
setMode(mode = 'production') {
|
|
6189
|
+
if (mode === 'sandbox') { // sandbox mode
|
|
6190
|
+
this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
|
|
6191
|
+
this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
|
|
6192
|
+
this.cryptoStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/crypto/us';
|
|
6447
6193
|
}
|
|
6448
|
-
else if (mode ===
|
|
6449
|
-
|
|
6450
|
-
this.
|
|
6451
|
-
this.
|
|
6452
|
-
this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION"); // there's no test mode for crypto
|
|
6194
|
+
else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
|
|
6195
|
+
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
|
|
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
|
|
6453
6198
|
}
|
|
6454
|
-
else {
|
|
6455
|
-
|
|
6456
|
-
this.
|
|
6457
|
-
this.
|
|
6458
|
-
this.cryptoStreamUrl = getCryptoStreamUrl("PRODUCTION");
|
|
6199
|
+
else { // production
|
|
6200
|
+
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
|
|
6201
|
+
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
|
|
6202
|
+
this.cryptoStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/crypto/us';
|
|
6459
6203
|
}
|
|
6460
6204
|
}
|
|
6461
6205
|
getMode() {
|
|
6462
|
-
if (this.stockStreamUrl.includes(
|
|
6463
|
-
return
|
|
6206
|
+
if (this.stockStreamUrl.includes('sandbox')) {
|
|
6207
|
+
return 'sandbox';
|
|
6464
6208
|
}
|
|
6465
|
-
else if (this.stockStreamUrl.includes(
|
|
6466
|
-
return
|
|
6209
|
+
else if (this.stockStreamUrl.includes('test')) {
|
|
6210
|
+
return 'test';
|
|
6467
6211
|
}
|
|
6468
6212
|
else {
|
|
6469
|
-
return
|
|
6213
|
+
return 'production';
|
|
6470
6214
|
}
|
|
6471
6215
|
}
|
|
6472
6216
|
constructor() {
|
|
6473
6217
|
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;
|
|
6218
|
+
this.dataURL = 'https://data.alpaca.markets/v2';
|
|
6486
6219
|
this.apiURL =
|
|
6487
|
-
process.env.ALPACA_ACCOUNT_TYPE ===
|
|
6488
|
-
?
|
|
6489
|
-
:
|
|
6490
|
-
this.v1beta1url =
|
|
6491
|
-
this.setMode(
|
|
6220
|
+
process.env.ALPACA_ACCOUNT_TYPE === 'PAPER'
|
|
6221
|
+
? 'https://paper-api.alpaca.markets/v2'
|
|
6222
|
+
: 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
|
|
6223
|
+
this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
|
|
6224
|
+
this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
|
|
6492
6225
|
this.headers = {
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6226
|
+
'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
|
|
6227
|
+
'APCA-API-SECRET-KEY': process.env.ALPACA_SECRET_KEY,
|
|
6228
|
+
'Content-Type': 'application/json',
|
|
6496
6229
|
};
|
|
6497
6230
|
}
|
|
6498
6231
|
static getInstance() {
|
|
@@ -6509,75 +6242,56 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6509
6242
|
}
|
|
6510
6243
|
connect(streamType) {
|
|
6511
6244
|
let url;
|
|
6512
|
-
if (streamType ===
|
|
6245
|
+
if (streamType === 'stock') {
|
|
6513
6246
|
url = this.stockStreamUrl;
|
|
6514
6247
|
}
|
|
6515
|
-
else if (streamType ===
|
|
6248
|
+
else if (streamType === 'option') {
|
|
6516
6249
|
url = this.optionStreamUrl;
|
|
6517
6250
|
}
|
|
6518
6251
|
else {
|
|
6519
6252
|
url = this.cryptoStreamUrl;
|
|
6520
6253
|
}
|
|
6521
6254
|
const ws = new WebSocket(url);
|
|
6522
|
-
if (streamType ===
|
|
6255
|
+
if (streamType === 'stock') {
|
|
6523
6256
|
this.stockWs = ws;
|
|
6524
6257
|
}
|
|
6525
|
-
else if (streamType ===
|
|
6258
|
+
else if (streamType === 'option') {
|
|
6526
6259
|
this.optionWs = ws;
|
|
6527
6260
|
}
|
|
6528
6261
|
else {
|
|
6529
6262
|
this.cryptoWs = ws;
|
|
6530
6263
|
}
|
|
6531
|
-
ws.on(
|
|
6532
|
-
log$1(`${streamType} stream connected`, { type:
|
|
6264
|
+
ws.on('open', () => {
|
|
6265
|
+
log$1(`${streamType} stream connected`, { type: 'info' });
|
|
6533
6266
|
const authMessage = {
|
|
6534
|
-
action:
|
|
6267
|
+
action: 'auth',
|
|
6535
6268
|
key: process.env.ALPACA_API_KEY,
|
|
6536
6269
|
secret: process.env.ALPACA_SECRET_KEY,
|
|
6537
6270
|
};
|
|
6538
6271
|
ws.send(JSON.stringify(authMessage));
|
|
6539
6272
|
});
|
|
6540
|
-
ws.on(
|
|
6541
|
-
const
|
|
6542
|
-
let messages;
|
|
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
|
-
}
|
|
6273
|
+
ws.on('message', (data) => {
|
|
6274
|
+
const messages = JSON.parse(data.toString());
|
|
6550
6275
|
for (const message of messages) {
|
|
6551
|
-
if (message.T ===
|
|
6552
|
-
log$1(`${streamType} stream authenticated`, { type:
|
|
6276
|
+
if (message.T === 'success' && message.msg === 'authenticated') {
|
|
6277
|
+
log$1(`${streamType} stream authenticated`, { type: 'info' });
|
|
6553
6278
|
this.sendSubscription(streamType);
|
|
6554
6279
|
}
|
|
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" });
|
|
6280
|
+
else if (message.T === 'error') {
|
|
6281
|
+
log$1(`${streamType} stream error: ${message.msg} (code: ${message.code})`, { type: 'error' });
|
|
6565
6282
|
}
|
|
6566
6283
|
else if (message.S) {
|
|
6567
6284
|
super.emit(`${streamType}-${message.T}`, message);
|
|
6568
6285
|
super.emit(`${streamType}-data`, message);
|
|
6569
6286
|
}
|
|
6570
|
-
else {
|
|
6571
|
-
log$1(`${streamType} received unknown message type: ${JSON.stringify(message)}`, { type: "debug" });
|
|
6572
|
-
}
|
|
6573
6287
|
}
|
|
6574
6288
|
});
|
|
6575
|
-
ws.on(
|
|
6576
|
-
log$1(`${streamType} stream disconnected`, { type:
|
|
6577
|
-
if (streamType ===
|
|
6289
|
+
ws.on('close', () => {
|
|
6290
|
+
log$1(`${streamType} stream disconnected`, { type: 'warn' });
|
|
6291
|
+
if (streamType === 'stock') {
|
|
6578
6292
|
this.stockWs = null;
|
|
6579
6293
|
}
|
|
6580
|
-
else if (streamType ===
|
|
6294
|
+
else if (streamType === 'option') {
|
|
6581
6295
|
this.optionWs = null;
|
|
6582
6296
|
}
|
|
6583
6297
|
else {
|
|
@@ -6585,18 +6299,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6585
6299
|
}
|
|
6586
6300
|
// Optional: implement reconnect logic
|
|
6587
6301
|
});
|
|
6588
|
-
ws.on(
|
|
6589
|
-
log$1(`${streamType} stream error: ${error.message}`, { type:
|
|
6302
|
+
ws.on('error', (error) => {
|
|
6303
|
+
log$1(`${streamType} stream error: ${error.message}`, { type: 'error' });
|
|
6590
6304
|
});
|
|
6591
6305
|
}
|
|
6592
6306
|
sendSubscription(streamType) {
|
|
6593
6307
|
let ws;
|
|
6594
6308
|
let subscriptions;
|
|
6595
|
-
if (streamType ===
|
|
6309
|
+
if (streamType === 'stock') {
|
|
6596
6310
|
ws = this.stockWs;
|
|
6597
6311
|
subscriptions = this.stockSubscriptions;
|
|
6598
6312
|
}
|
|
6599
|
-
else if (streamType ===
|
|
6313
|
+
else if (streamType === 'option') {
|
|
6600
6314
|
ws = this.optionWs;
|
|
6601
6315
|
subscriptions = this.optionSubscriptions;
|
|
6602
6316
|
}
|
|
@@ -6604,9 +6318,6 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6604
6318
|
ws = this.cryptoWs;
|
|
6605
6319
|
subscriptions = this.cryptoSubscriptions;
|
|
6606
6320
|
}
|
|
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
|
-
});
|
|
6610
6321
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
6611
6322
|
const subMessagePayload = {};
|
|
6612
6323
|
if (subscriptions.trades.length > 0) {
|
|
@@ -6620,40 +6331,26 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6620
6331
|
}
|
|
6621
6332
|
if (Object.keys(subMessagePayload).length > 0) {
|
|
6622
6333
|
const subMessage = {
|
|
6623
|
-
action:
|
|
6334
|
+
action: 'subscribe',
|
|
6624
6335
|
...subMessagePayload,
|
|
6625
6336
|
};
|
|
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
|
-
});
|
|
6337
|
+
ws.send(JSON.stringify(subMessage));
|
|
6636
6338
|
}
|
|
6637
6339
|
}
|
|
6638
|
-
else {
|
|
6639
|
-
log$1(`Cannot send ${streamType} subscription: WebSocket not ready`, {
|
|
6640
|
-
type: "warn",
|
|
6641
|
-
});
|
|
6642
|
-
}
|
|
6643
6340
|
}
|
|
6644
6341
|
connectStockStream() {
|
|
6645
6342
|
if (!this.stockWs) {
|
|
6646
|
-
this.connect(
|
|
6343
|
+
this.connect('stock');
|
|
6647
6344
|
}
|
|
6648
6345
|
}
|
|
6649
6346
|
connectOptionStream() {
|
|
6650
6347
|
if (!this.optionWs) {
|
|
6651
|
-
this.connect(
|
|
6348
|
+
this.connect('option');
|
|
6652
6349
|
}
|
|
6653
6350
|
}
|
|
6654
6351
|
connectCryptoStream() {
|
|
6655
6352
|
if (!this.cryptoWs) {
|
|
6656
|
-
this.connect(
|
|
6353
|
+
this.connect('crypto');
|
|
6657
6354
|
}
|
|
6658
6355
|
}
|
|
6659
6356
|
disconnectStockStream() {
|
|
@@ -6677,22 +6374,22 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6677
6374
|
* @returns True if the stream is connected
|
|
6678
6375
|
*/
|
|
6679
6376
|
isStreamConnected(streamType) {
|
|
6680
|
-
if (streamType ===
|
|
6681
|
-
return
|
|
6377
|
+
if (streamType === 'stock') {
|
|
6378
|
+
return this.stockWs !== null && this.stockWs.readyState === WebSocket.OPEN;
|
|
6682
6379
|
}
|
|
6683
|
-
else if (streamType ===
|
|
6684
|
-
return
|
|
6380
|
+
else if (streamType === 'option') {
|
|
6381
|
+
return this.optionWs !== null && this.optionWs.readyState === WebSocket.OPEN;
|
|
6685
6382
|
}
|
|
6686
6383
|
else {
|
|
6687
|
-
return
|
|
6384
|
+
return this.cryptoWs !== null && this.cryptoWs.readyState === WebSocket.OPEN;
|
|
6688
6385
|
}
|
|
6689
6386
|
}
|
|
6690
6387
|
subscribe(streamType, subscriptions) {
|
|
6691
6388
|
let currentSubscriptions;
|
|
6692
|
-
if (streamType ===
|
|
6389
|
+
if (streamType === 'stock') {
|
|
6693
6390
|
currentSubscriptions = this.stockSubscriptions;
|
|
6694
6391
|
}
|
|
6695
|
-
else if (streamType ===
|
|
6392
|
+
else if (streamType === 'option') {
|
|
6696
6393
|
currentSubscriptions = this.optionSubscriptions;
|
|
6697
6394
|
}
|
|
6698
6395
|
else {
|
|
@@ -6700,19 +6397,17 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6700
6397
|
}
|
|
6701
6398
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6702
6399
|
if (value) {
|
|
6703
|
-
currentSubscriptions[key] = [
|
|
6704
|
-
...new Set([...(currentSubscriptions[key] || []), ...value]),
|
|
6705
|
-
];
|
|
6400
|
+
currentSubscriptions[key] = [...new Set([...(currentSubscriptions[key] || []), ...value])];
|
|
6706
6401
|
}
|
|
6707
6402
|
});
|
|
6708
6403
|
this.sendSubscription(streamType);
|
|
6709
6404
|
}
|
|
6710
6405
|
unsubscribe(streamType, subscriptions) {
|
|
6711
6406
|
let currentSubscriptions;
|
|
6712
|
-
if (streamType ===
|
|
6407
|
+
if (streamType === 'stock') {
|
|
6713
6408
|
currentSubscriptions = this.stockSubscriptions;
|
|
6714
6409
|
}
|
|
6715
|
-
else if (streamType ===
|
|
6410
|
+
else if (streamType === 'option') {
|
|
6716
6411
|
currentSubscriptions = this.optionSubscriptions;
|
|
6717
6412
|
}
|
|
6718
6413
|
else {
|
|
@@ -6720,18 +6415,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6720
6415
|
}
|
|
6721
6416
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
6722
6417
|
if (value) {
|
|
6723
|
-
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(
|
|
6418
|
+
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
|
|
6724
6419
|
}
|
|
6725
6420
|
});
|
|
6726
6421
|
const unsubMessage = {
|
|
6727
|
-
action:
|
|
6422
|
+
action: 'unsubscribe',
|
|
6728
6423
|
...subscriptions,
|
|
6729
6424
|
};
|
|
6730
6425
|
let ws;
|
|
6731
|
-
if (streamType ===
|
|
6426
|
+
if (streamType === 'stock') {
|
|
6732
6427
|
ws = this.stockWs;
|
|
6733
6428
|
}
|
|
6734
|
-
else if (streamType ===
|
|
6429
|
+
else if (streamType === 'option') {
|
|
6735
6430
|
ws = this.optionWs;
|
|
6736
6431
|
}
|
|
6737
6432
|
else {
|
|
@@ -6741,20 +6436,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6741
6436
|
ws.send(JSON.stringify(unsubMessage));
|
|
6742
6437
|
}
|
|
6743
6438
|
}
|
|
6744
|
-
async makeRequest(endpoint, method =
|
|
6745
|
-
const baseUrl = baseUrlName ===
|
|
6746
|
-
? this.dataURL
|
|
6747
|
-
: baseUrlName === "api"
|
|
6748
|
-
? this.apiURL
|
|
6749
|
-
: this.v1beta1url;
|
|
6439
|
+
async makeRequest(endpoint, method = 'GET', params, baseUrlName = 'data') {
|
|
6440
|
+
const baseUrl = baseUrlName === 'data' ? this.dataURL : baseUrlName === 'api' ? this.apiURL : this.v1beta1url;
|
|
6750
6441
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
6751
6442
|
try {
|
|
6752
6443
|
if (params) {
|
|
6753
6444
|
Object.entries(params).forEach(([key, value]) => {
|
|
6754
6445
|
if (Array.isArray(value)) {
|
|
6755
|
-
url.searchParams.append(key, value.join(
|
|
6446
|
+
url.searchParams.append(key, value.join(','));
|
|
6756
6447
|
}
|
|
6757
|
-
else if (value !== undefined
|
|
6448
|
+
else if (value !== undefined) {
|
|
6758
6449
|
url.searchParams.append(key, value.toString());
|
|
6759
6450
|
}
|
|
6760
6451
|
});
|
|
@@ -6762,13 +6453,10 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6762
6453
|
const response = await fetch(url.toString(), {
|
|
6763
6454
|
method,
|
|
6764
6455
|
headers: this.headers,
|
|
6765
|
-
signal: createTimeoutSignal(DEFAULT_TIMEOUTS.ALPACA_API),
|
|
6766
6456
|
});
|
|
6767
6457
|
if (!response.ok) {
|
|
6768
6458
|
const errorText = await response.text();
|
|
6769
|
-
log$1(`Market Data API error (${response.status}): ${errorText}`, {
|
|
6770
|
-
type: "error",
|
|
6771
|
-
});
|
|
6459
|
+
log$1(`Market Data API error (${response.status}): ${errorText}`, { type: 'error' });
|
|
6772
6460
|
throw new Error(`Market Data API error (${response.status}): ${errorText}`);
|
|
6773
6461
|
}
|
|
6774
6462
|
const data = await response.json();
|
|
@@ -6776,9 +6464,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6776
6464
|
}
|
|
6777
6465
|
catch (err) {
|
|
6778
6466
|
const error = err;
|
|
6779
|
-
log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type:
|
|
6467
|
+
log$1(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: 'error' });
|
|
6780
6468
|
if (error instanceof TypeError) {
|
|
6781
|
-
log$1(`Network error details: ${error.stack}`, { type:
|
|
6469
|
+
log$1(`Network error details: ${error.stack}`, { type: 'error' });
|
|
6782
6470
|
}
|
|
6783
6471
|
throw error;
|
|
6784
6472
|
}
|
|
@@ -6791,19 +6479,19 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6791
6479
|
*/
|
|
6792
6480
|
async getHistoricalBars(params) {
|
|
6793
6481
|
const symbols = params.symbols;
|
|
6794
|
-
const symbolsStr = symbols.join(
|
|
6482
|
+
const symbolsStr = symbols.join(',');
|
|
6795
6483
|
let allBars = {};
|
|
6796
6484
|
let pageToken = null;
|
|
6797
6485
|
let hasMorePages = true;
|
|
6798
6486
|
let totalBarsCount = 0;
|
|
6799
6487
|
let pageCount = 0;
|
|
6800
|
-
let currency =
|
|
6488
|
+
let currency = '';
|
|
6801
6489
|
// Initialize bar arrays for each symbol
|
|
6802
|
-
symbols.forEach(
|
|
6490
|
+
symbols.forEach(symbol => {
|
|
6803
6491
|
allBars[symbol] = [];
|
|
6804
6492
|
});
|
|
6805
|
-
log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start ||
|
|
6806
|
-
type:
|
|
6493
|
+
log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
6494
|
+
type: 'info'
|
|
6807
6495
|
});
|
|
6808
6496
|
while (hasMorePages) {
|
|
6809
6497
|
pageCount++;
|
|
@@ -6813,11 +6501,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6813
6501
|
feed: DEFAULT_FEED,
|
|
6814
6502
|
...(pageToken && { page_token: pageToken }),
|
|
6815
6503
|
};
|
|
6816
|
-
const response = await this.makeRequest(
|
|
6504
|
+
const response = await this.makeRequest('/stocks/bars', 'GET', requestParams);
|
|
6817
6505
|
if (!response.bars) {
|
|
6818
|
-
log$1(`No bars data found in response for ${symbolsStr}`, {
|
|
6819
|
-
type: "warn",
|
|
6820
|
-
});
|
|
6506
|
+
log$1(`No bars data found in response for ${symbolsStr}`, { type: 'warn' });
|
|
6821
6507
|
break;
|
|
6822
6508
|
}
|
|
6823
6509
|
// Track currency from first response
|
|
@@ -6833,7 +6519,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6833
6519
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
6834
6520
|
pageBarsCount += bars.length;
|
|
6835
6521
|
// Track date range for this page
|
|
6836
|
-
bars.forEach(
|
|
6522
|
+
bars.forEach(bar => {
|
|
6837
6523
|
const barDate = new Date(bar.t);
|
|
6838
6524
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
6839
6525
|
earliestTimestamp = barDate;
|
|
@@ -6849,23 +6535,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6849
6535
|
hasMorePages = !!pageToken;
|
|
6850
6536
|
// Enhanced logging with date range and progress info
|
|
6851
6537
|
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:
|
|
6538
|
+
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
6539
|
+
: 'unknown range';
|
|
6540
|
+
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
6541
|
+
type: 'info'
|
|
6856
6542
|
});
|
|
6857
6543
|
// Prevent infinite loops
|
|
6858
6544
|
if (pageCount > 1000) {
|
|
6859
|
-
log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6545
|
+
log$1(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
6860
6546
|
break;
|
|
6861
6547
|
}
|
|
6862
6548
|
}
|
|
6863
6549
|
// Final summary
|
|
6864
|
-
const symbolCounts = Object.entries(allBars)
|
|
6865
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
6866
|
-
.join(", ");
|
|
6550
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
6867
6551
|
log$1(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
6868
|
-
type:
|
|
6552
|
+
type: 'info'
|
|
6869
6553
|
});
|
|
6870
6554
|
return {
|
|
6871
6555
|
bars: allBars,
|
|
@@ -6881,7 +6565,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6881
6565
|
|
|
6882
6566
|
*/
|
|
6883
6567
|
async getLatestBars(symbols, currency) {
|
|
6884
|
-
return this.makeRequest(
|
|
6568
|
+
return this.makeRequest('/stocks/bars/latest', 'GET', {
|
|
6885
6569
|
symbols,
|
|
6886
6570
|
feed: DEFAULT_FEED,
|
|
6887
6571
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6893,7 +6577,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6893
6577
|
* @returns Last trade details including price, size, exchange, and conditions
|
|
6894
6578
|
*/
|
|
6895
6579
|
async getLastTrade(symbol) {
|
|
6896
|
-
return this.makeRequest(`/v1/last/stocks/${symbol}`,
|
|
6580
|
+
return this.makeRequest(`/v1/last/stocks/${symbol}`, 'GET');
|
|
6897
6581
|
}
|
|
6898
6582
|
/**
|
|
6899
6583
|
* Get the most recent trades for requested symbols
|
|
@@ -6904,7 +6588,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6904
6588
|
|
|
6905
6589
|
*/
|
|
6906
6590
|
async getLatestTrades(symbols, feed, currency) {
|
|
6907
|
-
return this.makeRequest(
|
|
6591
|
+
return this.makeRequest('/stocks/trades/latest', 'GET', {
|
|
6908
6592
|
symbols,
|
|
6909
6593
|
feed: feed || DEFAULT_FEED,
|
|
6910
6594
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6920,15 +6604,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6920
6604
|
async getLatestQuotes(symbols, feed, currency) {
|
|
6921
6605
|
// Return empty response if symbols array is empty to avoid API error
|
|
6922
6606
|
if (!symbols || symbols.length === 0) {
|
|
6923
|
-
log$1(
|
|
6924
|
-
type: "warn",
|
|
6925
|
-
});
|
|
6607
|
+
log$1('No symbols provided to getLatestQuotes, returning empty response', { type: 'warn' });
|
|
6926
6608
|
return {
|
|
6927
6609
|
quotes: {},
|
|
6928
6610
|
currency: currency || DEFAULT_CURRENCY,
|
|
6929
6611
|
};
|
|
6930
6612
|
}
|
|
6931
|
-
return this.makeRequest(
|
|
6613
|
+
return this.makeRequest('/stocks/quotes/latest', 'GET', {
|
|
6932
6614
|
symbols,
|
|
6933
6615
|
feed: feed || DEFAULT_FEED,
|
|
6934
6616
|
currency: currency || DEFAULT_CURRENCY,
|
|
@@ -6942,7 +6624,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6942
6624
|
* @returns Latest quote data with symbol and currency information
|
|
6943
6625
|
*/
|
|
6944
6626
|
async getLatestQuote(symbol, feed, currency) {
|
|
6945
|
-
return this.makeRequest(`/stocks/${symbol}/quotes/latest`,
|
|
6627
|
+
return this.makeRequest(`/stocks/${symbol}/quotes/latest`, 'GET', {
|
|
6946
6628
|
feed: feed || DEFAULT_FEED,
|
|
6947
6629
|
currency,
|
|
6948
6630
|
});
|
|
@@ -6958,16 +6640,13 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6958
6640
|
const prevMarketDate = getLastFullTradingDate(date);
|
|
6959
6641
|
const response = await this.getHistoricalBars({
|
|
6960
6642
|
symbols: [symbol],
|
|
6961
|
-
timeframe:
|
|
6643
|
+
timeframe: '1Day',
|
|
6962
6644
|
start: prevMarketDate.date.toISOString(),
|
|
6963
6645
|
end: prevMarketDate.date.toISOString(),
|
|
6964
6646
|
limit: 1,
|
|
6965
6647
|
});
|
|
6966
6648
|
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
|
-
});
|
|
6649
|
+
log$1(`No previous close data available for ${symbol}`, { type: 'error', symbol });
|
|
6971
6650
|
return null;
|
|
6972
6651
|
}
|
|
6973
6652
|
return response.bars[symbol][0];
|
|
@@ -6982,7 +6661,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6982
6661
|
async getHourlyPrices(symbol, start, end) {
|
|
6983
6662
|
const response = await this.getHistoricalBars({
|
|
6984
6663
|
symbols: [symbol],
|
|
6985
|
-
timeframe:
|
|
6664
|
+
timeframe: '1Hour',
|
|
6986
6665
|
start: new Date(start).toISOString(),
|
|
6987
6666
|
end: new Date(end).toISOString(),
|
|
6988
6667
|
limit: 96, // Last 96 hours (4 days)
|
|
@@ -6999,7 +6678,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
6999
6678
|
async getHalfHourlyPrices(symbol, start, end) {
|
|
7000
6679
|
const response = await this.getHistoricalBars({
|
|
7001
6680
|
symbols: [symbol],
|
|
7002
|
-
timeframe:
|
|
6681
|
+
timeframe: '30Min',
|
|
7003
6682
|
start: new Date(start).toISOString(),
|
|
7004
6683
|
end: new Date(end).toISOString(),
|
|
7005
6684
|
limit: 16 * 2 * 4, // last 4 days, 16 hours per day, 2 bars per hour
|
|
@@ -7016,7 +6695,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7016
6695
|
async getDailyPrices(symbol, start, end) {
|
|
7017
6696
|
const response = await this.getHistoricalBars({
|
|
7018
6697
|
symbols: [symbol],
|
|
7019
|
-
timeframe:
|
|
6698
|
+
timeframe: '1Day',
|
|
7020
6699
|
start: new Date(start).toISOString(),
|
|
7021
6700
|
end: new Date(end).toISOString(),
|
|
7022
6701
|
limit: 100, // Last 100 days
|
|
@@ -7048,7 +6727,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7048
6727
|
*/
|
|
7049
6728
|
static analyzeBars(bars) {
|
|
7050
6729
|
if (!bars || bars.length === 0) {
|
|
7051
|
-
return
|
|
6730
|
+
return 'No price data available';
|
|
7052
6731
|
}
|
|
7053
6732
|
const firstBar = bars[0];
|
|
7054
6733
|
const lastBar = bars[bars.length - 1];
|
|
@@ -7073,7 +6752,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7073
6752
|
*/
|
|
7074
6753
|
async getAssets(params) {
|
|
7075
6754
|
// Endpoint: GET /v2/assets
|
|
7076
|
-
return this.makeRequest(
|
|
6755
|
+
return this.makeRequest('/assets', 'GET', params, 'api'); // use apiURL
|
|
7077
6756
|
}
|
|
7078
6757
|
/**
|
|
7079
6758
|
* Get a single asset by symbol or asset_id
|
|
@@ -7083,7 +6762,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7083
6762
|
*/
|
|
7084
6763
|
async getAsset(symbolOrAssetId) {
|
|
7085
6764
|
// Endpoint: GET /v2/assets/{symbol_or_asset_id}
|
|
7086
|
-
return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`,
|
|
6765
|
+
return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, 'GET', undefined, 'api');
|
|
7087
6766
|
}
|
|
7088
6767
|
// ===== OPTIONS MARKET DATA METHODS =====
|
|
7089
6768
|
/**
|
|
@@ -7095,7 +6774,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7095
6774
|
*/
|
|
7096
6775
|
async getOptionsChain(params) {
|
|
7097
6776
|
const { underlying_symbol, ...queryParams } = params;
|
|
7098
|
-
return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`,
|
|
6777
|
+
return this.makeRequest(`/options/snapshots/${encodeURIComponent(underlying_symbol)}`, 'GET', queryParams, 'v1beta1');
|
|
7099
6778
|
}
|
|
7100
6779
|
/**
|
|
7101
6780
|
* Get the most recent trades for requested option contract symbols
|
|
@@ -7107,7 +6786,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7107
6786
|
async getLatestOptionsTrades(params) {
|
|
7108
6787
|
// Remove limit and page_token as they're not supported by this endpoint
|
|
7109
6788
|
const { limit, page_token, ...requestParams } = params;
|
|
7110
|
-
return this.makeRequest(
|
|
6789
|
+
return this.makeRequest('/options/trades/latest', 'GET', requestParams, 'v1beta1');
|
|
7111
6790
|
}
|
|
7112
6791
|
/**
|
|
7113
6792
|
* Get the most recent quotes for requested option contract symbols
|
|
@@ -7119,7 +6798,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7119
6798
|
async getLatestOptionsQuotes(params) {
|
|
7120
6799
|
// Remove limit and page_token as they're not supported by this endpoint
|
|
7121
6800
|
const { limit, page_token, ...requestParams } = params;
|
|
7122
|
-
return this.makeRequest(
|
|
6801
|
+
return this.makeRequest('/options/quotes/latest', 'GET', requestParams, 'v1beta1');
|
|
7123
6802
|
}
|
|
7124
6803
|
/**
|
|
7125
6804
|
* Get historical OHLCV bars for option contract symbols
|
|
@@ -7131,18 +6810,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7131
6810
|
*/
|
|
7132
6811
|
async getHistoricalOptionsBars(params) {
|
|
7133
6812
|
const symbols = params.symbols;
|
|
7134
|
-
const symbolsStr = symbols.join(
|
|
6813
|
+
const symbolsStr = symbols.join(',');
|
|
7135
6814
|
let allBars = {};
|
|
7136
6815
|
let pageToken = null;
|
|
7137
6816
|
let hasMorePages = true;
|
|
7138
6817
|
let totalBarsCount = 0;
|
|
7139
6818
|
let pageCount = 0;
|
|
7140
6819
|
// Initialize bar arrays for each symbol
|
|
7141
|
-
symbols.forEach(
|
|
6820
|
+
symbols.forEach(symbol => {
|
|
7142
6821
|
allBars[symbol] = [];
|
|
7143
6822
|
});
|
|
7144
|
-
log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start ||
|
|
7145
|
-
type:
|
|
6823
|
+
log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
6824
|
+
type: 'info'
|
|
7146
6825
|
});
|
|
7147
6826
|
while (hasMorePages) {
|
|
7148
6827
|
pageCount++;
|
|
@@ -7150,11 +6829,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7150
6829
|
...params,
|
|
7151
6830
|
...(pageToken && { page_token: pageToken }),
|
|
7152
6831
|
};
|
|
7153
|
-
const response = await this.makeRequest(
|
|
6832
|
+
const response = await this.makeRequest('/options/bars', 'GET', requestParams, 'v1beta1');
|
|
7154
6833
|
if (!response.bars) {
|
|
7155
|
-
log$1(`No options bars data found in response for ${symbolsStr}`, {
|
|
7156
|
-
type: "warn",
|
|
7157
|
-
});
|
|
6834
|
+
log$1(`No options bars data found in response for ${symbolsStr}`, { type: 'warn' });
|
|
7158
6835
|
break;
|
|
7159
6836
|
}
|
|
7160
6837
|
// Combine bars for each symbol
|
|
@@ -7166,7 +6843,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7166
6843
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
7167
6844
|
pageBarsCount += bars.length;
|
|
7168
6845
|
// Track date range for this page
|
|
7169
|
-
bars.forEach(
|
|
6846
|
+
bars.forEach(bar => {
|
|
7170
6847
|
const barDate = new Date(bar.t);
|
|
7171
6848
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
7172
6849
|
earliestTimestamp = barDate;
|
|
@@ -7182,23 +6859,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7182
6859
|
hasMorePages = !!pageToken;
|
|
7183
6860
|
// Enhanced logging with date range and progress info
|
|
7184
6861
|
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:
|
|
6862
|
+
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
6863
|
+
: 'unknown range';
|
|
6864
|
+
log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
6865
|
+
type: 'info'
|
|
7189
6866
|
});
|
|
7190
6867
|
// Prevent infinite loops
|
|
7191
6868
|
if (pageCount > 1000) {
|
|
7192
|
-
log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6869
|
+
log$1(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
7193
6870
|
break;
|
|
7194
6871
|
}
|
|
7195
6872
|
}
|
|
7196
6873
|
// Final summary
|
|
7197
|
-
const symbolCounts = Object.entries(allBars)
|
|
7198
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
7199
|
-
.join(", ");
|
|
6874
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
7200
6875
|
log$1(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
7201
|
-
type:
|
|
6876
|
+
type: 'info'
|
|
7202
6877
|
});
|
|
7203
6878
|
return {
|
|
7204
6879
|
bars: allBars,
|
|
@@ -7215,18 +6890,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7215
6890
|
*/
|
|
7216
6891
|
async getHistoricalOptionsTrades(params) {
|
|
7217
6892
|
const symbols = params.symbols;
|
|
7218
|
-
const symbolsStr = symbols.join(
|
|
6893
|
+
const symbolsStr = symbols.join(',');
|
|
7219
6894
|
let allTrades = {};
|
|
7220
6895
|
let pageToken = null;
|
|
7221
6896
|
let hasMorePages = true;
|
|
7222
6897
|
let totalTradesCount = 0;
|
|
7223
6898
|
let pageCount = 0;
|
|
7224
6899
|
// Initialize trades arrays for each symbol
|
|
7225
|
-
symbols.forEach(
|
|
6900
|
+
symbols.forEach(symbol => {
|
|
7226
6901
|
allTrades[symbol] = [];
|
|
7227
6902
|
});
|
|
7228
|
-
log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start ||
|
|
7229
|
-
type:
|
|
6903
|
+
log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
6904
|
+
type: 'info'
|
|
7230
6905
|
});
|
|
7231
6906
|
while (hasMorePages) {
|
|
7232
6907
|
pageCount++;
|
|
@@ -7234,11 +6909,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7234
6909
|
...params,
|
|
7235
6910
|
...(pageToken && { page_token: pageToken }),
|
|
7236
6911
|
};
|
|
7237
|
-
const response = await this.makeRequest(
|
|
6912
|
+
const response = await this.makeRequest('/options/trades', 'GET', requestParams, 'v1beta1');
|
|
7238
6913
|
if (!response.trades) {
|
|
7239
|
-
log$1(`No options trades data found in response for ${symbolsStr}`, {
|
|
7240
|
-
type: "warn",
|
|
7241
|
-
});
|
|
6914
|
+
log$1(`No options trades data found in response for ${symbolsStr}`, { type: 'warn' });
|
|
7242
6915
|
break;
|
|
7243
6916
|
}
|
|
7244
6917
|
// Combine trades for each symbol
|
|
@@ -7250,7 +6923,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7250
6923
|
allTrades[symbol] = [...allTrades[symbol], ...trades];
|
|
7251
6924
|
pageTradesCount += trades.length;
|
|
7252
6925
|
// Track date range for this page
|
|
7253
|
-
trades.forEach(
|
|
6926
|
+
trades.forEach(trade => {
|
|
7254
6927
|
const tradeDate = new Date(trade.t);
|
|
7255
6928
|
if (!earliestTimestamp || tradeDate < earliestTimestamp) {
|
|
7256
6929
|
earliestTimestamp = tradeDate;
|
|
@@ -7266,23 +6939,21 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7266
6939
|
hasMorePages = !!pageToken;
|
|
7267
6940
|
// Enhanced logging with date range and progress info
|
|
7268
6941
|
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:
|
|
6942
|
+
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
6943
|
+
: 'unknown range';
|
|
6944
|
+
log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
6945
|
+
type: 'info'
|
|
7273
6946
|
});
|
|
7274
6947
|
// Prevent infinite loops
|
|
7275
6948
|
if (pageCount > 1000) {
|
|
7276
|
-
log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type:
|
|
6949
|
+
log$1(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
7277
6950
|
break;
|
|
7278
6951
|
}
|
|
7279
6952
|
}
|
|
7280
6953
|
// Final summary
|
|
7281
|
-
const symbolCounts = Object.entries(allTrades)
|
|
7282
|
-
.map(([symbol, trades]) => `${symbol}: ${trades.length}`)
|
|
7283
|
-
.join(", ");
|
|
6954
|
+
const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
|
|
7284
6955
|
log$1(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
|
|
7285
|
-
type:
|
|
6956
|
+
type: 'info'
|
|
7286
6957
|
});
|
|
7287
6958
|
return {
|
|
7288
6959
|
trades: allTrades,
|
|
@@ -7300,7 +6971,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7300
6971
|
async getOptionsSnapshot(params) {
|
|
7301
6972
|
// Remove limit and page_token as they may not be supported by this endpoint
|
|
7302
6973
|
const { limit, page_token, ...requestParams } = params;
|
|
7303
|
-
return this.makeRequest(
|
|
6974
|
+
return this.makeRequest('/options/snapshots', 'GET', requestParams, 'v1beta1');
|
|
7304
6975
|
}
|
|
7305
6976
|
/**
|
|
7306
6977
|
* Get condition codes for options trades or quotes
|
|
@@ -7311,7 +6982,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7311
6982
|
* @see https://docs.alpaca.markets/reference/optionmetaconditions
|
|
7312
6983
|
*/
|
|
7313
6984
|
async getOptionsConditionCodes(tickType) {
|
|
7314
|
-
return this.makeRequest(`/options/meta/conditions/${tickType}`,
|
|
6985
|
+
return this.makeRequest(`/options/meta/conditions/${tickType}`, 'GET', undefined, 'v1beta1');
|
|
7315
6986
|
}
|
|
7316
6987
|
/**
|
|
7317
6988
|
* Get exchange codes for options
|
|
@@ -7321,7 +6992,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7321
6992
|
* @see https://docs.alpaca.markets/reference/optionmetaexchanges
|
|
7322
6993
|
*/
|
|
7323
6994
|
async getOptionsExchangeCodes() {
|
|
7324
|
-
return this.makeRequest(
|
|
6995
|
+
return this.makeRequest('/options/meta/exchanges', 'GET', undefined, 'v1beta1');
|
|
7325
6996
|
}
|
|
7326
6997
|
/**
|
|
7327
6998
|
* Analyzes an array of option bars and returns a summary string
|
|
@@ -7330,7 +7001,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7330
7001
|
*/
|
|
7331
7002
|
static analyzeOptionBars(bars) {
|
|
7332
7003
|
if (!bars || bars.length === 0) {
|
|
7333
|
-
return
|
|
7004
|
+
return 'No option price data available';
|
|
7334
7005
|
}
|
|
7335
7006
|
const firstBar = bars[0];
|
|
7336
7007
|
const lastBar = bars[bars.length - 1];
|
|
@@ -7354,7 +7025,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7354
7025
|
*/
|
|
7355
7026
|
static formatOptionGreeks(greeks) {
|
|
7356
7027
|
if (!greeks) {
|
|
7357
|
-
return
|
|
7028
|
+
return 'No greeks data available';
|
|
7358
7029
|
}
|
|
7359
7030
|
const parts = [];
|
|
7360
7031
|
if (greeks.delta !== undefined)
|
|
@@ -7367,7 +7038,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7367
7038
|
parts.push(`Vega: ${greeks.vega.toFixed(4)}`);
|
|
7368
7039
|
if (greeks.rho !== undefined)
|
|
7369
7040
|
parts.push(`Rho: ${greeks.rho.toFixed(4)}`);
|
|
7370
|
-
return parts.length > 0 ? parts.join(
|
|
7041
|
+
return parts.length > 0 ? parts.join(', ') : 'No greeks data available';
|
|
7371
7042
|
}
|
|
7372
7043
|
/**
|
|
7373
7044
|
* Interprets condition codes using the provided condition codes mapping
|
|
@@ -7377,14 +7048,12 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7377
7048
|
*/
|
|
7378
7049
|
static interpretConditionCodes(conditionCodes, conditionCodesMap) {
|
|
7379
7050
|
if (!conditionCodes || conditionCodes.length === 0) {
|
|
7380
|
-
return
|
|
7051
|
+
return 'No conditions';
|
|
7381
7052
|
}
|
|
7382
7053
|
const descriptions = conditionCodes
|
|
7383
7054
|
.map((code) => conditionCodesMap[code] || `Unknown (${code})`)
|
|
7384
7055
|
.filter((desc) => desc !== undefined);
|
|
7385
|
-
return descriptions.length > 0
|
|
7386
|
-
? descriptions.join(", ")
|
|
7387
|
-
: "No condition descriptions available";
|
|
7056
|
+
return descriptions.length > 0 ? descriptions.join(', ') : 'No condition descriptions available';
|
|
7388
7057
|
}
|
|
7389
7058
|
/**
|
|
7390
7059
|
* Gets the exchange name from exchange code using the provided exchange codes mapping
|
|
@@ -7393,7 +7062,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7393
7062
|
* @returns Exchange name or formatted unknown exchange
|
|
7394
7063
|
*/
|
|
7395
7064
|
static getExchangeName(exchangeCode, exchangeCodesMap) {
|
|
7396
|
-
return
|
|
7065
|
+
return exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`;
|
|
7397
7066
|
}
|
|
7398
7067
|
/**
|
|
7399
7068
|
* Fetches news articles from Alpaca API for a symbol, paginating through all results.
|
|
@@ -7406,7 +7075,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7406
7075
|
start: new Date(Date.now() - 24 * 60 * 60 * 1000),
|
|
7407
7076
|
end: new Date(),
|
|
7408
7077
|
limit: 10,
|
|
7409
|
-
sort:
|
|
7078
|
+
sort: 'desc',
|
|
7410
7079
|
include_content: true,
|
|
7411
7080
|
};
|
|
7412
7081
|
const mergedParams = { ...defaultParams, ...params };
|
|
@@ -7420,52 +7089,38 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7420
7089
|
if (!content)
|
|
7421
7090
|
return undefined;
|
|
7422
7091
|
// Remove excessive whitespace, newlines, and trim
|
|
7423
|
-
return content.replace(/\s+/g,
|
|
7092
|
+
return content.replace(/\s+/g, ' ').trim();
|
|
7424
7093
|
}
|
|
7425
7094
|
while (hasMorePages) {
|
|
7426
7095
|
const queryParams = new URLSearchParams({
|
|
7427
|
-
...(mergedParams.start && {
|
|
7428
|
-
|
|
7429
|
-
}),
|
|
7430
|
-
...(mergedParams.end && {
|
|
7431
|
-
end: new Date(mergedParams.end).toISOString(),
|
|
7432
|
-
}),
|
|
7096
|
+
...(mergedParams.start && { start: new Date(mergedParams.start).toISOString() }),
|
|
7097
|
+
...(mergedParams.end && { end: new Date(mergedParams.end).toISOString() }),
|
|
7433
7098
|
...(symbol && { symbols: symbol }),
|
|
7434
|
-
...(mergedParams.limit && {
|
|
7435
|
-
limit: Math.min(50, maxLimit - fetchedCount).toString(),
|
|
7436
|
-
}),
|
|
7099
|
+
...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
|
|
7437
7100
|
...(mergedParams.sort && { sort: mergedParams.sort }),
|
|
7438
|
-
...(mergedParams.include_content !== undefined
|
|
7439
|
-
? { include_content: mergedParams.include_content.toString() }
|
|
7440
|
-
: {}),
|
|
7101
|
+
...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
|
|
7441
7102
|
...(pageToken && { page_token: pageToken }),
|
|
7442
7103
|
});
|
|
7443
7104
|
const url = `${this.v1beta1url}/news?${queryParams}`;
|
|
7444
|
-
log$1(`Fetching news from: ${url}`, { type:
|
|
7105
|
+
log$1(`Fetching news from: ${url}`, { type: 'debug', symbol });
|
|
7445
7106
|
const response = await fetch(url, {
|
|
7446
|
-
method:
|
|
7107
|
+
method: 'GET',
|
|
7447
7108
|
headers: this.headers,
|
|
7448
7109
|
});
|
|
7449
7110
|
if (!response.ok) {
|
|
7450
7111
|
const errorText = await response.text();
|
|
7451
|
-
log$1(`Alpaca news API error (${response.status}): ${errorText}`, {
|
|
7452
|
-
type: "error",
|
|
7453
|
-
symbol,
|
|
7454
|
-
});
|
|
7112
|
+
log$1(`Alpaca news API error (${response.status}): ${errorText}`, { type: 'error', symbol });
|
|
7455
7113
|
throw new Error(`Alpaca news API error (${response.status}): ${errorText}`);
|
|
7456
7114
|
}
|
|
7457
7115
|
const data = await response.json();
|
|
7458
7116
|
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
|
-
});
|
|
7117
|
+
log$1(`No news data found in Alpaca response for ${symbol}`, { type: 'warn', symbol });
|
|
7463
7118
|
break;
|
|
7464
7119
|
}
|
|
7465
7120
|
const transformedNews = data.news.map((article) => ({
|
|
7466
7121
|
symbols: article.symbols,
|
|
7467
7122
|
title: article.headline,
|
|
7468
|
-
summary: cleanContent(article.summary) ??
|
|
7123
|
+
summary: cleanContent(article.summary) ?? '',
|
|
7469
7124
|
content: article.content ? cleanContent(article.content) : undefined,
|
|
7470
7125
|
url: article.url,
|
|
7471
7126
|
source: article.source,
|
|
@@ -7478,7 +7133,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
7478
7133
|
fetchedCount = newsArticles.length;
|
|
7479
7134
|
pageToken = data.next_page_token || null;
|
|
7480
7135
|
hasMorePages = !!pageToken && (!maxLimit || fetchedCount < maxLimit);
|
|
7481
|
-
log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type:
|
|
7136
|
+
log$1(`Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`, { type: 'debug', symbol });
|
|
7482
7137
|
if (maxLimit && fetchedCount >= maxLimit) {
|
|
7483
7138
|
newsArticles = newsArticles.slice(0, maxLimit);
|
|
7484
7139
|
break;
|