@discomedia/utils 1.0.5 → 1.0.7
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 +95 -3
- package/dist/index-frontend.cjs +16027 -0
- package/dist/index-frontend.cjs.map +1 -0
- package/dist/index-frontend.mjs +16023 -0
- package/dist/index-frontend.mjs.map +1 -0
- package/dist/index.cjs +1188 -921
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1190 -921
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +8 -2
- package/dist/test.js +835 -731
- package/dist/test.js.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +3 -15
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/alpaca-trading-api.d.ts +3 -6
- package/dist/types/alpaca-trading-api.d.ts.map +1 -1
- package/dist/types/index-frontend.d.ts +15 -0
- package/dist/types/index-frontend.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -28
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/market-time.d.ts +187 -117
- package/dist/types/market-time.d.ts.map +1 -1
- package/dist/types/old-test.d.ts +2 -0
- package/dist/types/old-test.d.ts.map +1 -0
- package/dist/types/testing/market-time-refactor-test.d.ts +1 -0
- package/dist/types/testing/market-time-refactor-test.d.ts.map +1 -0
- package/dist/types-frontend/alpaca-market-data-api.d.ts +372 -0
- package/dist/types-frontend/alpaca-market-data-api.d.ts.map +1 -0
- package/dist/types-frontend/alpaca-trading-api.d.ts +315 -0
- package/dist/types-frontend/alpaca-trading-api.d.ts.map +1 -0
- package/dist/types-frontend/format-tools.d.ts +46 -0
- package/dist/types-frontend/format-tools.d.ts.map +1 -0
- package/dist/types-frontend/index-frontend.d.ts +15 -0
- package/dist/types-frontend/index-frontend.d.ts.map +1 -0
- package/dist/types-frontend/index.d.ts +125 -0
- package/dist/types-frontend/index.d.ts.map +1 -0
- package/dist/types-frontend/json-tools.d.ts +33 -0
- package/dist/types-frontend/json-tools.d.ts.map +1 -0
- package/dist/types-frontend/llm-config.d.ts +36 -0
- package/dist/types-frontend/llm-config.d.ts.map +1 -0
- package/dist/types-frontend/llm-deepseek.d.ts +12 -0
- package/dist/types-frontend/llm-deepseek.d.ts.map +1 -0
- package/dist/types-frontend/llm-images.d.ts +49 -0
- package/dist/types-frontend/llm-images.d.ts.map +1 -0
- package/dist/types-frontend/llm-openai.d.ts +64 -0
- package/dist/types-frontend/llm-openai.d.ts.map +1 -0
- package/dist/types-frontend/llm-utils.d.ts +16 -0
- package/dist/types-frontend/llm-utils.d.ts.map +1 -0
- package/dist/types-frontend/logging.d.ts +12 -0
- package/dist/types-frontend/logging.d.ts.map +1 -0
- package/dist/types-frontend/market-hours.d.ts +24 -0
- package/dist/types-frontend/market-hours.d.ts.map +1 -0
- package/dist/types-frontend/market-time.d.ts +254 -0
- package/dist/types-frontend/market-time.d.ts.map +1 -0
- package/dist/types-frontend/misc-utils.d.ts +49 -0
- package/dist/types-frontend/misc-utils.d.ts.map +1 -0
- package/dist/types-frontend/old-test.d.ts +2 -0
- package/dist/types-frontend/old-test.d.ts.map +1 -0
- package/dist/types-frontend/polygon-indices.d.ts +85 -0
- package/dist/types-frontend/polygon-indices.d.ts.map +1 -0
- package/dist/types-frontend/polygon.d.ts +126 -0
- package/dist/types-frontend/polygon.d.ts.map +1 -0
- package/dist/types-frontend/technical-analysis.d.ts +90 -0
- package/dist/types-frontend/technical-analysis.d.ts.map +1 -0
- package/dist/types-frontend/test.d.ts +2 -0
- package/dist/types-frontend/test.d.ts.map +1 -0
- package/dist/types-frontend/testing/market-time-refactor-test.d.ts +1 -0
- package/dist/types-frontend/testing/market-time-refactor-test.d.ts.map +1 -0
- package/dist/types-frontend/types/alpaca-types.d.ts +962 -0
- package/dist/types-frontend/types/alpaca-types.d.ts.map +1 -0
- package/dist/types-frontend/types/index.d.ts +7 -0
- package/dist/types-frontend/types/index.d.ts.map +1 -0
- package/dist/types-frontend/types/llm-types.d.ts +82 -0
- package/dist/types-frontend/types/llm-types.d.ts.map +1 -0
- package/dist/types-frontend/types/logging-types.d.ts +10 -0
- package/dist/types-frontend/types/logging-types.d.ts.map +1 -0
- package/dist/types-frontend/types/market-time-types.d.ts +59 -0
- package/dist/types-frontend/types/market-time-types.d.ts.map +1 -0
- package/dist/types-frontend/types/polygon-indices-types.d.ts +190 -0
- package/dist/types-frontend/types/polygon-indices-types.d.ts.map +1 -0
- package/dist/types-frontend/types/polygon-types.d.ts +204 -0
- package/dist/types-frontend/types/polygon-types.d.ts.map +1 -0
- package/dist/types-frontend/types/ta-types.d.ts +89 -0
- package/dist/types-frontend/types/ta-types.d.ts.map +1 -0
- package/package.json +8 -2
- package/dist/types/time-utils.d.ts +0 -17
- package/dist/types/time-utils.d.ts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,181 +1,47 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { fromZonedTime, formatInTimeZone
|
|
3
|
-
import { set, format, differenceInMilliseconds, startOfDay, sub, add, endOfDay, isBefore } from 'date-fns';
|
|
1
|
+
import { startOfDay, set, endOfDay, add, sub, format, differenceInMilliseconds, isBefore } from 'date-fns';
|
|
2
|
+
import { toZonedTime, fromZonedTime, formatInTimeZone } from 'date-fns-tz';
|
|
4
3
|
import require$$0$3, { EventEmitter } from 'events';
|
|
5
|
-
import require$$1
|
|
4
|
+
import require$$1 from 'https';
|
|
6
5
|
import require$$2 from 'http';
|
|
7
|
-
import require$$3 from 'net';
|
|
8
|
-
import require$$4 from 'tls';
|
|
9
|
-
import require$$
|
|
6
|
+
import require$$3$1 from 'net';
|
|
7
|
+
import require$$4$1 from 'tls';
|
|
8
|
+
import require$$3 from 'crypto';
|
|
10
9
|
import require$$0$2 from 'stream';
|
|
11
10
|
import require$$7 from 'url';
|
|
12
11
|
import require$$0 from 'zlib';
|
|
13
12
|
import require$$0$1 from 'buffer';
|
|
13
|
+
import require$$0$4 from 'fs';
|
|
14
|
+
import require$$1$1 from 'path';
|
|
15
|
+
import require$$2$1 from 'os';
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const date = new Date(
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (years > 0) {
|
|
36
|
-
return years === 1 ? '1 year ago' : `${years} years ago`;
|
|
37
|
-
}
|
|
38
|
-
else if (months > 0) {
|
|
39
|
-
return months === 1 ? '1 month ago' : `${months} months ago`;
|
|
40
|
-
}
|
|
41
|
-
else if (days > 0) {
|
|
42
|
-
return days === 1 ? '1 day ago' : `${days} days ago`;
|
|
43
|
-
}
|
|
44
|
-
else if (hours > 0) {
|
|
45
|
-
return hours === 1 ? '1 hr ago' : `${hours} hrs ago`;
|
|
46
|
-
}
|
|
47
|
-
else if (minutes > 0) {
|
|
48
|
-
return minutes === 1 ? '1 min ago' : `${minutes} mins ago`;
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
return 'A few seconds ago';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function normalizeDate(timestamp) {
|
|
55
|
-
const date = new Date(timestamp);
|
|
56
|
-
return date.toISOString().split('T')[0]; // Returns 'YYYY-MM-DD'
|
|
57
|
-
}
|
|
58
|
-
// the function formerly known as CalculateRange, like a camel with two humps. Gross
|
|
59
|
-
function calculateTimeRange(range) {
|
|
60
|
-
const currentDate = new Date();
|
|
61
|
-
switch (range) {
|
|
62
|
-
case '1d':
|
|
63
|
-
currentDate.setDate(currentDate.getDate() - 1);
|
|
64
|
-
break;
|
|
65
|
-
case '3d':
|
|
66
|
-
currentDate.setDate(currentDate.getDate() - 3);
|
|
67
|
-
break;
|
|
68
|
-
case '1w':
|
|
69
|
-
currentDate.setDate(currentDate.getDate() - 7);
|
|
70
|
-
break;
|
|
71
|
-
case '1m':
|
|
72
|
-
currentDate.setMonth(currentDate.getMonth() - 1);
|
|
73
|
-
break;
|
|
74
|
-
case '3m':
|
|
75
|
-
currentDate.setMonth(currentDate.getMonth() - 3);
|
|
76
|
-
break;
|
|
77
|
-
case '1y':
|
|
78
|
-
currentDate.setFullYear(currentDate.getFullYear() - 1);
|
|
79
|
-
break;
|
|
80
|
-
default:
|
|
81
|
-
throw new Error(`Invalid range: ${range}`);
|
|
17
|
+
/**
|
|
18
|
+
* Logs a message to the console.
|
|
19
|
+
* @param message The message to log.
|
|
20
|
+
* @param options Optional options.
|
|
21
|
+
* @param options.source The source of the message.
|
|
22
|
+
* @param options.type The type of message to log.
|
|
23
|
+
* @param options.symbol The trading symbol associated with this log.
|
|
24
|
+
* @param options.account The account associated with this log.
|
|
25
|
+
*/
|
|
26
|
+
function log$1(message, options = { source: 'App', type: 'info' }) {
|
|
27
|
+
// Format the timestamp
|
|
28
|
+
const date = new Date();
|
|
29
|
+
const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
30
|
+
const account = options?.account;
|
|
31
|
+
const symbol = options?.symbol;
|
|
32
|
+
// Build the log message
|
|
33
|
+
const logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
|
|
34
|
+
// Use appropriate console method based on type
|
|
35
|
+
if (options?.type === 'error') {
|
|
36
|
+
console.error(logMessage);
|
|
82
37
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const daysLeft = (accountCreationDate, maxDays) => {
|
|
86
|
-
const now = new Date();
|
|
87
|
-
const endPeriodDate = new Date(accountCreationDate);
|
|
88
|
-
endPeriodDate.setDate(accountCreationDate.getDate() + maxDays);
|
|
89
|
-
const diffInMilliseconds = endPeriodDate.getTime() - now.getTime();
|
|
90
|
-
// Convert milliseconds to days and return
|
|
91
|
-
return Math.ceil(diffInMilliseconds / (1000 * 60 * 60 * 24));
|
|
92
|
-
};
|
|
93
|
-
const cutoffDate = new Date('2023-10-17T00:00:00.000Z');
|
|
94
|
-
const calculateDaysLeft = (accountCreationDate) => {
|
|
95
|
-
let maxDays;
|
|
96
|
-
if (accountCreationDate < cutoffDate) {
|
|
97
|
-
maxDays = 30;
|
|
98
|
-
accountCreationDate = new Date('2023-10-01T00:00:00.000Z');
|
|
38
|
+
else if (options?.type === 'warn') {
|
|
39
|
+
console.warn(logMessage);
|
|
99
40
|
}
|
|
100
41
|
else {
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return daysLeft(accountCreationDate, maxDays);
|
|
104
|
-
};
|
|
105
|
-
const timeAgo = (timestamp) => {
|
|
106
|
-
if (!timestamp)
|
|
107
|
-
return 'Just now';
|
|
108
|
-
const diff = Date.now() - new Date(timestamp).getTime();
|
|
109
|
-
if (diff < 60000) {
|
|
110
|
-
// less than 1 second
|
|
111
|
-
return 'Just now';
|
|
112
|
-
}
|
|
113
|
-
else if (diff > 82800000) {
|
|
114
|
-
// more than 23 hours – similar to how Twitter displays timestamps
|
|
115
|
-
return new Date(timestamp).toLocaleDateString('en-US', {
|
|
116
|
-
month: 'short',
|
|
117
|
-
day: 'numeric',
|
|
118
|
-
year: new Date(timestamp).getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return `${ms(diff)} ago`;
|
|
122
|
-
};
|
|
123
|
-
// returns date utc
|
|
124
|
-
const formatDate = (dateString, updateDate) => {
|
|
125
|
-
return new Date(dateString).toLocaleDateString('en-US', {
|
|
126
|
-
day: 'numeric',
|
|
127
|
-
month: 'long',
|
|
128
|
-
year: updateDate && new Date(dateString).getFullYear() === new Date().getFullYear() ? undefined : 'numeric',
|
|
129
|
-
timeZone: 'UTC',
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
const parseETDateFromAV = (dateString) => {
|
|
133
|
-
// Time zone identifier for Eastern Time
|
|
134
|
-
const timeZone = 'America/New_York';
|
|
135
|
-
// Split the input string into date and time components
|
|
136
|
-
const [datePart, timePart] = dateString.split(' ');
|
|
137
|
-
// Construct a full date-time string in ISO format
|
|
138
|
-
const fullString = `${datePart}T${timePart}`;
|
|
139
|
-
// Convert the string to a UTC Date object using date-fns-tz
|
|
140
|
-
const utcDate = fromZonedTime(fullString, timeZone); // Convert to UTC
|
|
141
|
-
return utcDate;
|
|
142
|
-
};
|
|
143
|
-
const formatToUSEastern = (date, justDate) => {
|
|
144
|
-
const options = {
|
|
145
|
-
timeZone: 'America/New_York',
|
|
146
|
-
month: 'short',
|
|
147
|
-
day: 'numeric',
|
|
148
|
-
year: 'numeric',
|
|
149
|
-
};
|
|
150
|
-
if (!justDate) {
|
|
151
|
-
options.hour = 'numeric';
|
|
152
|
-
options.minute = '2-digit';
|
|
153
|
-
options.hour12 = true;
|
|
42
|
+
console.log(logMessage);
|
|
154
43
|
}
|
|
155
|
-
|
|
156
|
-
};
|
|
157
|
-
const unixTimetoUSEastern = (timestamp) => {
|
|
158
|
-
const date = new Date(timestamp);
|
|
159
|
-
const timeString = formatToUSEastern(date);
|
|
160
|
-
const dateString = formatToUSEastern(date, true);
|
|
161
|
-
return { date, timeString, dateString };
|
|
162
|
-
};
|
|
163
|
-
const timeDiffString = (milliseconds) => {
|
|
164
|
-
const seconds = Math.floor(milliseconds / 1000);
|
|
165
|
-
const minutes = Math.floor(seconds / 60);
|
|
166
|
-
const hours = Math.floor(minutes / 60);
|
|
167
|
-
const days = Math.floor(hours / 24);
|
|
168
|
-
const remainingHours = hours % 24;
|
|
169
|
-
const remainingMinutes = minutes % 60;
|
|
170
|
-
const parts = [];
|
|
171
|
-
if (days > 0)
|
|
172
|
-
parts.push(`${days} day${days > 1 ? 's' : ''}`);
|
|
173
|
-
if (remainingHours > 0)
|
|
174
|
-
parts.push(`${remainingHours} hour${remainingHours > 1 ? 's' : ''}`);
|
|
175
|
-
if (remainingMinutes > 0)
|
|
176
|
-
parts.push(`${remainingMinutes} minute${remainingMinutes > 1 ? 's' : ''}`);
|
|
177
|
-
return parts.join(', ');
|
|
178
|
-
};
|
|
44
|
+
}
|
|
179
45
|
|
|
180
46
|
// market-hours.ts
|
|
181
47
|
const marketHolidays = {
|
|
@@ -280,99 +146,66 @@ const marketEarlyCloses = {
|
|
|
280
146
|
},
|
|
281
147
|
};
|
|
282
148
|
|
|
283
|
-
// market-time.ts
|
|
149
|
+
// market-time.ts - Refactored for better organization and usability
|
|
150
|
+
// ===== CONFIGURATION =====
|
|
284
151
|
/**
|
|
285
|
-
* Market
|
|
286
|
-
* Regular market hours are 9:30am-4:00pm
|
|
287
|
-
* Early market hours are 9:30am-10:00am (first 30 minutes)
|
|
288
|
-
* Extended market hours are 4:00am to 9:30am and 4:00pm-8:00pm
|
|
289
|
-
* On days before some holidays, the market closes early at 1:00pm
|
|
290
|
-
* Early extended market hours are 1:00pm-5:00pm on early close days
|
|
152
|
+
* Market configuration constants
|
|
291
153
|
*/
|
|
292
|
-
const
|
|
154
|
+
const MARKET_CONFIG = {
|
|
293
155
|
TIMEZONE: 'America/New_York',
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}, // extended hours trading on early close days
|
|
304
|
-
REGULAR: { START: { HOUR: 9, MINUTE: 30, MINUTES: 570 }, END: { HOUR: 16, MINUTE: 0, MINUTES: 960 } },
|
|
305
|
-
EXTENDED: { START: { HOUR: 4, MINUTE: 0, MINUTES: 240 }, END: { HOUR: 20, MINUTE: 0, MINUTES: 1200 } },
|
|
156
|
+
TIMES: {
|
|
157
|
+
EXTENDED_START: { hour: 4, minute: 0 },
|
|
158
|
+
MARKET_OPEN: { hour: 9, minute: 30 },
|
|
159
|
+
EARLY_MARKET_END: { hour: 10, minute: 0 },
|
|
160
|
+
MARKET_CLOSE: { hour: 16, minute: 0 },
|
|
161
|
+
EARLY_CLOSE: { hour: 13, minute: 0 },
|
|
162
|
+
EXTENDED_END: { hour: 20, minute: 0 },
|
|
163
|
+
EARLY_EXTENDED_END: { hour: 17, minute: 0 },
|
|
164
|
+
},
|
|
306
165
|
};
|
|
166
|
+
// ===== MARKET CALENDAR SERVICE =====
|
|
307
167
|
/**
|
|
308
|
-
*
|
|
168
|
+
* Service for handling market calendar operations (holidays, early closes, market days)
|
|
309
169
|
*/
|
|
310
|
-
class
|
|
170
|
+
class MarketCalendar {
|
|
311
171
|
timezone;
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Creates a new MarketTimeUtil instance
|
|
315
|
-
* @param {string} [timezone='America/New_York'] - The timezone to use for market time calculations
|
|
316
|
-
* @param {IntradayReporting} [intradayReporting='market_hours'] - The intraday reporting mode
|
|
317
|
-
*/
|
|
318
|
-
constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting = 'market_hours') {
|
|
319
|
-
this.validateTimezone(timezone);
|
|
172
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
320
173
|
this.timezone = timezone;
|
|
321
|
-
this.intradayReporting = intradayReporting;
|
|
322
174
|
}
|
|
323
175
|
/**
|
|
324
|
-
*
|
|
325
|
-
* @private
|
|
326
|
-
* @param {string} timezone - The timezone to validate
|
|
327
|
-
* @throws {Error} If the timezone is invalid
|
|
176
|
+
* Check if a date is a weekend
|
|
328
177
|
*/
|
|
329
|
-
validateTimezone(timezone) {
|
|
330
|
-
try {
|
|
331
|
-
Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
throw new Error(`Invalid timezone: ${timezone}`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
formatDate(date, outputFormat = 'iso') {
|
|
338
|
-
switch (outputFormat) {
|
|
339
|
-
case 'unix-seconds':
|
|
340
|
-
return Math.floor(date.getTime() / 1000);
|
|
341
|
-
case 'unix-ms':
|
|
342
|
-
return date.getTime();
|
|
343
|
-
case 'iso':
|
|
344
|
-
default:
|
|
345
|
-
// return with timezone offset
|
|
346
|
-
return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
178
|
isWeekend(date) {
|
|
350
179
|
const day = date.getDay();
|
|
351
|
-
return day === 0 || day === 6;
|
|
180
|
+
return day === 0 || day === 6; // Sunday or Saturday
|
|
352
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if a date is a market holiday
|
|
184
|
+
*/
|
|
353
185
|
isHoliday(date) {
|
|
354
|
-
const formattedDate =
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
return false;
|
|
186
|
+
const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
187
|
+
const year = toZonedTime(date, this.timezone).getFullYear();
|
|
188
|
+
const yearHolidays = marketHolidays[year];
|
|
189
|
+
if (!yearHolidays)
|
|
190
|
+
return false;
|
|
191
|
+
return Object.values(yearHolidays).some(holiday => holiday.date === formattedDate);
|
|
362
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Check if a date is an early close day
|
|
195
|
+
*/
|
|
363
196
|
isEarlyCloseDay(date) {
|
|
364
|
-
const formattedDate =
|
|
365
|
-
const
|
|
197
|
+
const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
198
|
+
const year = toZonedTime(date, this.timezone).getFullYear();
|
|
199
|
+
const yearEarlyCloses = marketEarlyCloses[year];
|
|
366
200
|
return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
|
|
367
201
|
}
|
|
368
202
|
/**
|
|
369
|
-
* Get the early close time for a
|
|
370
|
-
* @param date - The date to get the early close time for
|
|
371
|
-
* @returns The early close time in minutes from midnight, or null if there is no early close
|
|
203
|
+
* Get the early close time for a date (in minutes from midnight)
|
|
372
204
|
*/
|
|
373
205
|
getEarlyCloseTime(date) {
|
|
374
|
-
const formattedDate =
|
|
375
|
-
const
|
|
206
|
+
const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
207
|
+
const year = toZonedTime(date, this.timezone).getFullYear();
|
|
208
|
+
const yearEarlyCloses = marketEarlyCloses[year];
|
|
376
209
|
if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
|
|
377
210
|
const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(':').map(Number);
|
|
378
211
|
return hours * 60 + minutes;
|
|
@@ -380,194 +213,283 @@ class MarketTimeUtil {
|
|
|
380
213
|
return null;
|
|
381
214
|
}
|
|
382
215
|
/**
|
|
383
|
-
* Check if a
|
|
384
|
-
* @param date - The date to check
|
|
385
|
-
* @returns true if the date is a market day, false otherwise
|
|
216
|
+
* Check if a date is a market day (not weekend or holiday)
|
|
386
217
|
*/
|
|
387
218
|
isMarketDay(date) {
|
|
388
|
-
|
|
389
|
-
const isHolidayDay = this.isHoliday(date);
|
|
390
|
-
const returner = !isWeekendDay && !isHolidayDay;
|
|
391
|
-
return returner;
|
|
219
|
+
return !this.isWeekend(date) && !this.isHoliday(date);
|
|
392
220
|
}
|
|
393
221
|
/**
|
|
394
|
-
*
|
|
395
|
-
* @param date - The date to check
|
|
396
|
-
* @returns true if the date is within market hours, false otherwise
|
|
222
|
+
* Get the next market day from a given date
|
|
397
223
|
*/
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
224
|
+
getNextMarketDay(date) {
|
|
225
|
+
let nextDay = add(date, { days: 1 });
|
|
226
|
+
while (!this.isMarketDay(nextDay)) {
|
|
227
|
+
nextDay = add(nextDay, { days: 1 });
|
|
402
228
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
229
|
+
return nextDay;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get the previous market day from a given date
|
|
233
|
+
*/
|
|
234
|
+
getPreviousMarketDay(date) {
|
|
235
|
+
let prevDay = sub(date, { days: 1 });
|
|
236
|
+
while (!this.isMarketDay(prevDay)) {
|
|
237
|
+
prevDay = sub(prevDay, { days: 1 });
|
|
410
238
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
239
|
+
return prevDay;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// ===== TIME FORMATTER SERVICE =====
|
|
243
|
+
/**
|
|
244
|
+
* Service for formatting time outputs
|
|
245
|
+
*/
|
|
246
|
+
class TimeFormatter {
|
|
247
|
+
timezone;
|
|
248
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
249
|
+
this.timezone = timezone;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Format a date based on the output format
|
|
253
|
+
*/
|
|
254
|
+
formatDate(date, outputFormat = 'iso') {
|
|
255
|
+
switch (outputFormat) {
|
|
256
|
+
case 'unix-seconds':
|
|
257
|
+
return Math.floor(date.getTime() / 1000);
|
|
258
|
+
case 'unix-ms':
|
|
259
|
+
return date.getTime();
|
|
260
|
+
case 'iso':
|
|
261
|
+
default:
|
|
262
|
+
return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
433
263
|
}
|
|
434
|
-
return returner;
|
|
435
264
|
}
|
|
436
265
|
/**
|
|
437
|
-
*
|
|
438
|
-
* @param date - The date to check
|
|
439
|
-
* @returns true if the date is before market hours, false otherwise
|
|
266
|
+
* Get New York timezone offset
|
|
440
267
|
*/
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
268
|
+
getNYTimeZone(date = new Date()) {
|
|
269
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
270
|
+
timeZone: this.timezone,
|
|
271
|
+
timeZoneName: 'shortOffset',
|
|
272
|
+
});
|
|
273
|
+
const parts = dtf.formatToParts(date);
|
|
274
|
+
const tz = parts.find(p => p.type === 'timeZoneName')?.value;
|
|
275
|
+
if (!tz) {
|
|
276
|
+
throw new Error('Could not determine New York offset');
|
|
277
|
+
}
|
|
278
|
+
const shortOffset = tz.replace('GMT', '');
|
|
279
|
+
if (shortOffset === '-4') {
|
|
280
|
+
return '-04:00';
|
|
281
|
+
}
|
|
282
|
+
else if (shortOffset === '-5') {
|
|
283
|
+
return '-05:00';
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
throw new Error(`Unexpected timezone offset: ${shortOffset}`);
|
|
287
|
+
}
|
|
447
288
|
}
|
|
448
289
|
/**
|
|
449
|
-
* Get
|
|
450
|
-
* @param currentDate - The current date
|
|
451
|
-
* @returns The last trading date
|
|
290
|
+
* Get trading date in YYYY-MM-DD format
|
|
452
291
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
return nowET;
|
|
292
|
+
getTradingDate(time) {
|
|
293
|
+
let date;
|
|
294
|
+
if (typeof time === 'number') {
|
|
295
|
+
date = new Date(time);
|
|
296
|
+
}
|
|
297
|
+
else if (typeof time === 'string') {
|
|
298
|
+
date = new Date(time);
|
|
461
299
|
}
|
|
462
300
|
else {
|
|
463
|
-
|
|
464
|
-
let lastTradingDate = sub(nowET, { days: 1 });
|
|
465
|
-
while (!this.isMarketDay(lastTradingDate)) {
|
|
466
|
-
lastTradingDate = sub(lastTradingDate, { days: 1 });
|
|
467
|
-
}
|
|
468
|
-
return lastTradingDate;
|
|
301
|
+
date = time;
|
|
469
302
|
}
|
|
303
|
+
return formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
470
304
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
305
|
+
}
|
|
306
|
+
// ===== MARKET TIME CALCULATOR =====
|
|
307
|
+
/**
|
|
308
|
+
* Service for core market time calculations
|
|
309
|
+
*/
|
|
310
|
+
class MarketTimeCalculator {
|
|
311
|
+
calendar;
|
|
312
|
+
formatter;
|
|
313
|
+
timezone;
|
|
314
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
315
|
+
this.timezone = timezone;
|
|
316
|
+
this.calendar = new MarketCalendar(timezone);
|
|
317
|
+
this.formatter = new TimeFormatter(timezone);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get market open/close times for a date
|
|
321
|
+
*/
|
|
322
|
+
getMarketTimes(date) {
|
|
323
|
+
const zonedDate = toZonedTime(date, this.timezone);
|
|
324
|
+
// Market closed on weekends and holidays
|
|
325
|
+
if (!this.calendar.isMarketDay(zonedDate)) {
|
|
326
|
+
return {
|
|
327
|
+
marketOpen: false,
|
|
328
|
+
open: null,
|
|
329
|
+
close: null,
|
|
330
|
+
openExt: null,
|
|
331
|
+
closeExt: null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const dayStart = startOfDay(zonedDate);
|
|
335
|
+
// Regular market times
|
|
336
|
+
const open = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour, minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute }), this.timezone);
|
|
337
|
+
let close = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, minutes: MARKET_CONFIG.TIMES.MARKET_CLOSE.minute }), this.timezone);
|
|
338
|
+
// Extended hours
|
|
339
|
+
const openExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour, minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute }), this.timezone);
|
|
340
|
+
let closeExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EXTENDED_END.hour, minutes: MARKET_CONFIG.TIMES.EXTENDED_END.minute }), this.timezone);
|
|
341
|
+
// Handle early close days
|
|
342
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
343
|
+
close = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, minutes: MARKET_CONFIG.TIMES.EARLY_CLOSE.minute }), this.timezone);
|
|
344
|
+
closeExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, minutes: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute }), this.timezone);
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
marketOpen: true,
|
|
348
|
+
open,
|
|
349
|
+
close,
|
|
350
|
+
openExt,
|
|
351
|
+
closeExt,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Check if a time is within market hours based on intraday reporting mode
|
|
356
|
+
*/
|
|
357
|
+
isWithinMarketHours(date, intradayReporting = 'market_hours') {
|
|
358
|
+
const zonedDate = toZonedTime(date, this.timezone);
|
|
359
|
+
// Not a market day
|
|
360
|
+
if (!this.calendar.isMarketDay(zonedDate)) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const timeInMinutes = zonedDate.getHours() * 60 + zonedDate.getMinutes();
|
|
364
|
+
switch (intradayReporting) {
|
|
365
|
+
case 'extended_hours': {
|
|
366
|
+
const startMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
367
|
+
let endMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
368
|
+
// Handle early close
|
|
369
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
370
|
+
endMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
371
|
+
}
|
|
372
|
+
return timeInMinutes >= startMinutes && timeInMinutes <= endMinutes;
|
|
373
|
+
}
|
|
374
|
+
case 'continuous':
|
|
375
|
+
return true;
|
|
376
|
+
default: {
|
|
377
|
+
// 'market_hours'
|
|
378
|
+
const startMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
379
|
+
let endMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
380
|
+
// Handle early close
|
|
381
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
382
|
+
endMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
383
|
+
}
|
|
384
|
+
return timeInMinutes >= startMinutes && timeInMinutes <= endMinutes;
|
|
385
|
+
}
|
|
475
386
|
}
|
|
476
|
-
return currentDate;
|
|
477
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Get the last full trading date
|
|
390
|
+
*/
|
|
478
391
|
getLastFullTradingDate(currentDate = new Date()) {
|
|
479
392
|
const nowET = toZonedTime(currentDate, this.timezone);
|
|
480
|
-
// If today is a market day and we're after extended hours close
|
|
481
|
-
|
|
482
|
-
if (this.isMarketDay(nowET)) {
|
|
393
|
+
// If today is a market day and we're after extended hours close, return today
|
|
394
|
+
if (this.calendar.isMarketDay(nowET)) {
|
|
483
395
|
const timeInMinutes = nowET.getHours() * 60 + nowET.getMinutes();
|
|
484
|
-
|
|
485
|
-
|
|
396
|
+
let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
397
|
+
if (this.calendar.isEarlyCloseDay(nowET)) {
|
|
398
|
+
extendedEndMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
399
|
+
}
|
|
486
400
|
if (timeInMinutes >= extendedEndMinutes) {
|
|
487
|
-
// Set to midnight ET while preserving the date
|
|
488
401
|
return fromZonedTime(set(nowET, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
|
|
489
402
|
}
|
|
490
403
|
}
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// Set to midnight ET while preserving the date
|
|
495
|
-
return fromZonedTime(set(lastFullDate, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
|
|
404
|
+
// Return the last completed trading day
|
|
405
|
+
const lastMarketDay = this.calendar.getPreviousMarketDay(nowET);
|
|
406
|
+
return fromZonedTime(set(lastMarketDay, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
|
|
496
407
|
}
|
|
497
408
|
/**
|
|
498
|
-
*
|
|
499
|
-
* @param {Object} [options] - Options object
|
|
500
|
-
* @param {Date} [options.referenceDate] - The reference date (defaults to current date)
|
|
501
|
-
* @returns {Object} The next market day information
|
|
502
|
-
* @property {Date} date - The date object (start of day in NY time)
|
|
503
|
-
* @property {string} yyyymmdd - The date in YYYY-MM-DD format
|
|
504
|
-
* @property {string} dateISOString - Full ISO date string
|
|
409
|
+
* Get day boundaries based on intraday reporting mode
|
|
505
410
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
while (!this.isMarketDay(currentDate)) {
|
|
509
|
-
currentDate = add(currentDate, { days: 1 });
|
|
510
|
-
}
|
|
511
|
-
return currentDate;
|
|
512
|
-
}
|
|
513
|
-
getDayBoundaries(date) {
|
|
411
|
+
getDayBoundaries(date, intradayReporting = 'market_hours') {
|
|
412
|
+
const zonedDate = toZonedTime(date, this.timezone);
|
|
514
413
|
let start;
|
|
515
414
|
let end;
|
|
516
|
-
switch (
|
|
415
|
+
switch (intradayReporting) {
|
|
517
416
|
case 'extended_hours': {
|
|
518
|
-
start = set(
|
|
519
|
-
hours:
|
|
520
|
-
minutes:
|
|
417
|
+
start = set(zonedDate, {
|
|
418
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
419
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
521
420
|
seconds: 0,
|
|
522
421
|
milliseconds: 0,
|
|
523
422
|
});
|
|
524
|
-
end = set(
|
|
525
|
-
hours:
|
|
526
|
-
minutes:
|
|
423
|
+
end = set(zonedDate, {
|
|
424
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_END.hour,
|
|
425
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_END.minute,
|
|
527
426
|
seconds: 59,
|
|
528
427
|
milliseconds: 999,
|
|
529
428
|
});
|
|
429
|
+
// Handle early close
|
|
430
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
431
|
+
end = set(zonedDate, {
|
|
432
|
+
hours: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour,
|
|
433
|
+
minutes: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute,
|
|
434
|
+
seconds: 59,
|
|
435
|
+
milliseconds: 999,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
530
438
|
break;
|
|
531
439
|
}
|
|
532
440
|
case 'continuous': {
|
|
533
|
-
start = startOfDay(
|
|
534
|
-
end = endOfDay(
|
|
441
|
+
start = startOfDay(zonedDate);
|
|
442
|
+
end = endOfDay(zonedDate);
|
|
535
443
|
break;
|
|
536
444
|
}
|
|
537
445
|
default: {
|
|
538
|
-
// market_hours
|
|
539
|
-
start = set(
|
|
540
|
-
hours:
|
|
541
|
-
minutes:
|
|
446
|
+
// 'market_hours'
|
|
447
|
+
start = set(zonedDate, {
|
|
448
|
+
hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour,
|
|
449
|
+
minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute,
|
|
542
450
|
seconds: 0,
|
|
543
451
|
milliseconds: 0,
|
|
544
452
|
});
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (earlyCloseMinutes !== null) {
|
|
549
|
-
const earlyCloseHours = Math.floor(earlyCloseMinutes / 60);
|
|
550
|
-
const earlyCloseMinutesRemainder = earlyCloseMinutes % 60;
|
|
551
|
-
end = set(date, {
|
|
552
|
-
hours: earlyCloseHours,
|
|
553
|
-
minutes: earlyCloseMinutesRemainder,
|
|
554
|
-
seconds: 59,
|
|
555
|
-
milliseconds: 999,
|
|
556
|
-
});
|
|
557
|
-
break;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
end = set(date, {
|
|
561
|
-
hours: MARKET_TIMES.REGULAR.END.HOUR,
|
|
562
|
-
minutes: MARKET_TIMES.REGULAR.END.MINUTE,
|
|
453
|
+
end = set(zonedDate, {
|
|
454
|
+
hours: MARKET_CONFIG.TIMES.MARKET_CLOSE.hour,
|
|
455
|
+
minutes: MARKET_CONFIG.TIMES.MARKET_CLOSE.minute,
|
|
563
456
|
seconds: 59,
|
|
564
457
|
milliseconds: 999,
|
|
565
458
|
});
|
|
459
|
+
// Handle early close
|
|
460
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
461
|
+
end = set(zonedDate, {
|
|
462
|
+
hours: MARKET_CONFIG.TIMES.EARLY_CLOSE.hour,
|
|
463
|
+
minutes: MARKET_CONFIG.TIMES.EARLY_CLOSE.minute,
|
|
464
|
+
seconds: 59,
|
|
465
|
+
milliseconds: 999,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
566
468
|
break;
|
|
567
469
|
}
|
|
568
470
|
}
|
|
569
|
-
return {
|
|
471
|
+
return {
|
|
472
|
+
start: fromZonedTime(start, this.timezone),
|
|
473
|
+
end: fromZonedTime(end, this.timezone),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// ===== PERIOD CALCULATOR =====
|
|
478
|
+
/**
|
|
479
|
+
* Service for calculating time periods
|
|
480
|
+
*/
|
|
481
|
+
class PeriodCalculator {
|
|
482
|
+
calendar;
|
|
483
|
+
timeCalculator;
|
|
484
|
+
formatter;
|
|
485
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
486
|
+
this.calendar = new MarketCalendar(timezone);
|
|
487
|
+
this.timeCalculator = new MarketTimeCalculator(timezone);
|
|
488
|
+
this.formatter = new TimeFormatter(timezone);
|
|
570
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Calculate the start date for a given period
|
|
492
|
+
*/
|
|
571
493
|
calculatePeriodStartDate(endDate, period) {
|
|
572
494
|
let startDate;
|
|
573
495
|
switch (period) {
|
|
@@ -575,7 +497,7 @@ class MarketTimeUtil {
|
|
|
575
497
|
startDate = set(endDate, { month: 0, date: 1 });
|
|
576
498
|
break;
|
|
577
499
|
case '1D':
|
|
578
|
-
startDate = this.
|
|
500
|
+
startDate = this.calendar.getPreviousMarketDay(endDate);
|
|
579
501
|
break;
|
|
580
502
|
case '3D':
|
|
581
503
|
startDate = sub(endDate, { days: 3 });
|
|
@@ -601,405 +523,286 @@ class MarketTimeUtil {
|
|
|
601
523
|
default:
|
|
602
524
|
throw new Error(`Invalid period: ${period}`);
|
|
603
525
|
}
|
|
604
|
-
|
|
605
|
-
|
|
526
|
+
// Ensure start date is a market day
|
|
527
|
+
while (!this.calendar.isMarketDay(startDate)) {
|
|
528
|
+
startDate = this.calendar.getNextMarketDay(startDate);
|
|
606
529
|
}
|
|
607
530
|
return startDate;
|
|
608
531
|
}
|
|
609
|
-
|
|
532
|
+
/**
|
|
533
|
+
* Get period dates for market time calculations
|
|
534
|
+
*/
|
|
535
|
+
getMarketTimePeriod(params) {
|
|
536
|
+
const { period, end = new Date(), intraday_reporting = 'market_hours', outputFormat = 'iso', } = params;
|
|
610
537
|
if (!period) {
|
|
611
538
|
throw new Error('Period is required');
|
|
612
539
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
// Convert end date to specified timezone
|
|
617
|
-
const zonedEndDate = toZonedTime(end, this.timezone);
|
|
618
|
-
let startDate;
|
|
540
|
+
const zonedEndDate = toZonedTime(end, MARKET_CONFIG.TIMEZONE);
|
|
541
|
+
// Determine effective end date based on current market conditions
|
|
619
542
|
let endDate;
|
|
620
|
-
const isCurrentMarketDay = this.isMarketDay(zonedEndDate);
|
|
621
|
-
const isWithinHours = this.isWithinMarketHours(
|
|
622
|
-
const
|
|
623
|
-
|
|
543
|
+
const isCurrentMarketDay = this.calendar.isMarketDay(zonedEndDate);
|
|
544
|
+
const isWithinHours = this.timeCalculator.isWithinMarketHours(end, intraday_reporting);
|
|
545
|
+
const timeInMinutes = zonedEndDate.getHours() * 60 + zonedEndDate.getMinutes();
|
|
546
|
+
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
624
547
|
if (isCurrentMarketDay) {
|
|
625
|
-
if (
|
|
626
|
-
//
|
|
627
|
-
const lastMarketDay = this.
|
|
628
|
-
const { end: dayEnd } = this.getDayBoundaries(lastMarketDay);
|
|
548
|
+
if (timeInMinutes < marketStartMinutes) {
|
|
549
|
+
// Before market open - use previous day's close
|
|
550
|
+
const lastMarketDay = this.calendar.getPreviousMarketDay(zonedEndDate);
|
|
551
|
+
const { end: dayEnd } = this.timeCalculator.getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
629
552
|
endDate = dayEnd;
|
|
630
553
|
}
|
|
631
554
|
else if (isWithinHours) {
|
|
632
|
-
//
|
|
633
|
-
endDate =
|
|
555
|
+
// During market hours - use current time
|
|
556
|
+
endDate = end;
|
|
634
557
|
}
|
|
635
558
|
else {
|
|
636
|
-
//
|
|
637
|
-
const { end: dayEnd } = this.getDayBoundaries(zonedEndDate);
|
|
559
|
+
// After market close - use today's close
|
|
560
|
+
const { end: dayEnd } = this.timeCalculator.getDayBoundaries(zonedEndDate, intraday_reporting);
|
|
638
561
|
endDate = dayEnd;
|
|
639
562
|
}
|
|
640
563
|
}
|
|
641
564
|
else {
|
|
642
|
-
//
|
|
643
|
-
const lastMarketDay = this.
|
|
644
|
-
const { end: dayEnd } = this.getDayBoundaries(lastMarketDay);
|
|
565
|
+
// Not a market day - use previous market day's close
|
|
566
|
+
const lastMarketDay = this.calendar.getPreviousMarketDay(zonedEndDate);
|
|
567
|
+
const { end: dayEnd } = this.timeCalculator.getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
645
568
|
endDate = dayEnd;
|
|
646
569
|
}
|
|
647
|
-
//
|
|
570
|
+
// Calculate start date
|
|
648
571
|
const periodStartDate = this.calculatePeriodStartDate(endDate, period);
|
|
649
|
-
const { start: dayStart } = this.getDayBoundaries(periodStartDate);
|
|
650
|
-
startDate = dayStart;
|
|
651
|
-
// Convert boundaries back to UTC for final output
|
|
652
|
-
const utcStart = fromZonedTime(startDate, this.timezone);
|
|
653
|
-
const utcEnd = fromZonedTime(endDate, this.timezone);
|
|
572
|
+
const { start: dayStart } = this.timeCalculator.getDayBoundaries(periodStartDate, intraday_reporting);
|
|
654
573
|
// Ensure start is not after end
|
|
655
|
-
if (isBefore(
|
|
574
|
+
if (isBefore(endDate, dayStart)) {
|
|
656
575
|
throw new Error('Start date cannot be after end date');
|
|
657
576
|
}
|
|
658
577
|
return {
|
|
659
|
-
start: this.formatDate(
|
|
660
|
-
end: this.formatDate(
|
|
578
|
+
start: this.formatter.formatDate(dayStart, outputFormat),
|
|
579
|
+
end: this.formatter.formatDate(endDate, outputFormat),
|
|
661
580
|
};
|
|
662
581
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
582
|
+
}
|
|
583
|
+
// ===== MARKET STATUS SERVICE =====
|
|
584
|
+
/**
|
|
585
|
+
* Service for determining market status
|
|
586
|
+
*/
|
|
587
|
+
class MarketStatusService {
|
|
588
|
+
calendar;
|
|
589
|
+
timeCalculator;
|
|
590
|
+
timezone;
|
|
591
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
592
|
+
this.timezone = timezone;
|
|
593
|
+
this.calendar = new MarketCalendar(timezone);
|
|
594
|
+
this.timeCalculator = new MarketTimeCalculator(timezone);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Get current market status
|
|
598
|
+
*/
|
|
599
|
+
getMarketStatus(date = new Date()) {
|
|
600
|
+
const nyTime = toZonedTime(date, this.timezone);
|
|
601
|
+
const timeInMinutes = nyTime.getHours() * 60 + nyTime.getMinutes();
|
|
602
|
+
const isMarketDay = this.calendar.isMarketDay(nyTime);
|
|
603
|
+
const isEarlyCloseDay = this.calendar.isEarlyCloseDay(nyTime);
|
|
604
|
+
// Time boundaries
|
|
605
|
+
const extendedStartMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
606
|
+
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
607
|
+
const earlyMarketEndMinutes = MARKET_CONFIG.TIMES.EARLY_MARKET_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_MARKET_END.minute;
|
|
608
|
+
let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
609
|
+
let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
610
|
+
if (isEarlyCloseDay) {
|
|
611
|
+
marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
612
|
+
extendedEndMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
613
|
+
}
|
|
614
|
+
let status;
|
|
615
|
+
let nextStatus;
|
|
616
|
+
let nextStatusTime;
|
|
617
|
+
let marketPeriod;
|
|
618
|
+
if (!isMarketDay) {
|
|
619
|
+
// Market is closed (holiday/weekend)
|
|
620
|
+
status = 'closed';
|
|
621
|
+
nextStatus = 'extended hours';
|
|
622
|
+
marketPeriod = 'closed';
|
|
623
|
+
const nextMarketDay = this.calendar.getNextMarketDay(nyTime);
|
|
624
|
+
nextStatusTime = set(nextMarketDay, {
|
|
625
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
626
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
627
|
+
});
|
|
675
628
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (earlyCloseMinutes !== null) {
|
|
686
|
-
// For regular hours, use the early close time
|
|
687
|
-
regularCloseTime = {
|
|
688
|
-
HOUR: Math.floor(earlyCloseMinutes / 60),
|
|
689
|
-
MINUTE: earlyCloseMinutes % 60,
|
|
690
|
-
MINUTES: earlyCloseMinutes,
|
|
691
|
-
};
|
|
692
|
-
// For extended hours on early close days, close at 5:00 PM
|
|
693
|
-
extendedCloseTime = {
|
|
694
|
-
HOUR: 17,
|
|
695
|
-
MINUTE: 0,
|
|
696
|
-
MINUTES: 1020,
|
|
697
|
-
};
|
|
698
|
-
}
|
|
629
|
+
else if (timeInMinutes < extendedStartMinutes) {
|
|
630
|
+
// Before extended hours
|
|
631
|
+
status = 'closed';
|
|
632
|
+
nextStatus = 'extended hours';
|
|
633
|
+
marketPeriod = 'closed';
|
|
634
|
+
nextStatusTime = set(nyTime, {
|
|
635
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
636
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
637
|
+
});
|
|
699
638
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
639
|
+
else if (timeInMinutes < marketStartMinutes) {
|
|
640
|
+
// Pre-market extended hours
|
|
641
|
+
status = 'extended hours';
|
|
642
|
+
nextStatus = 'open';
|
|
643
|
+
marketPeriod = 'preMarket';
|
|
644
|
+
nextStatusTime = set(nyTime, {
|
|
645
|
+
hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour,
|
|
646
|
+
minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
else if (timeInMinutes < marketCloseMinutes) {
|
|
650
|
+
// Market is open
|
|
651
|
+
status = 'open';
|
|
652
|
+
nextStatus = 'extended hours';
|
|
653
|
+
marketPeriod = timeInMinutes < earlyMarketEndMinutes ? 'earlyMarket' : 'regularMarket';
|
|
654
|
+
nextStatusTime = set(nyTime, {
|
|
655
|
+
hours: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_CLOSE.hour : MARKET_CONFIG.TIMES.MARKET_CLOSE.hour,
|
|
656
|
+
minutes: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_CLOSE.minute : MARKET_CONFIG.TIMES.MARKET_CLOSE.minute,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
else if (timeInMinutes < extendedEndMinutes) {
|
|
660
|
+
// After-market extended hours
|
|
661
|
+
status = 'extended hours';
|
|
662
|
+
nextStatus = 'closed';
|
|
663
|
+
marketPeriod = 'afterMarket';
|
|
664
|
+
nextStatusTime = set(nyTime, {
|
|
665
|
+
hours: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour : MARKET_CONFIG.TIMES.EXTENDED_END.hour,
|
|
666
|
+
minutes: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute : MARKET_CONFIG.TIMES.EXTENDED_END.minute,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
// After extended hours
|
|
671
|
+
status = 'closed';
|
|
672
|
+
nextStatus = 'extended hours';
|
|
673
|
+
marketPeriod = 'closed';
|
|
674
|
+
const nextMarketDay = this.calendar.getNextMarketDay(nyTime);
|
|
675
|
+
nextStatusTime = set(nextMarketDay, {
|
|
676
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
677
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const nextStatusTimeUTC = fromZonedTime(nextStatusTime, this.timezone);
|
|
681
|
+
const dateFormat = 'MMMM dd, yyyy, HH:mm:ss a';
|
|
704
682
|
return {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
683
|
+
time: date,
|
|
684
|
+
timeString: format(nyTime, dateFormat),
|
|
685
|
+
status,
|
|
686
|
+
nextStatus,
|
|
687
|
+
marketPeriod,
|
|
688
|
+
nextStatusTime: nextStatusTimeUTC,
|
|
689
|
+
nextStatusTimeDifference: differenceInMilliseconds(nextStatusTime, nyTime),
|
|
690
|
+
nextStatusTimeString: format(nextStatusTime, dateFormat),
|
|
710
691
|
};
|
|
711
692
|
}
|
|
712
693
|
}
|
|
694
|
+
// ===== FUNCTIONAL API =====
|
|
713
695
|
/**
|
|
714
|
-
*
|
|
715
|
-
* @param {string} [timezone] - The timezone to use for market time calculations
|
|
716
|
-
* @param {IntradayReporting} [intraday_reporting] - The intraday reporting mode
|
|
717
|
-
* @returns {MarketTimeUtil} A new MarketTimeUtil instance
|
|
718
|
-
*/
|
|
719
|
-
function createMarketTimeUtil(timezone, intraday_reporting) {
|
|
720
|
-
return new MarketTimeUtil(timezone, intraday_reporting);
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Gets start and end timestamps for a given market time period
|
|
724
|
-
* @param {MarketTimeParams} [params] - The market time parameters
|
|
725
|
-
* @returns {PeriodDates} The start and end timestamps
|
|
696
|
+
* Simple functional API that uses the services above
|
|
726
697
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return util.getMarketTimePeriod(effectiveParams);
|
|
734
|
-
}
|
|
698
|
+
// Create service instances
|
|
699
|
+
const marketCalendar = new MarketCalendar();
|
|
700
|
+
const marketTimeCalculator = new MarketTimeCalculator();
|
|
701
|
+
const periodCalculator = new PeriodCalculator();
|
|
702
|
+
const marketStatusService = new MarketStatusService();
|
|
703
|
+
const timeFormatter = new TimeFormatter();
|
|
735
704
|
/**
|
|
736
|
-
*
|
|
737
|
-
* @param {Object} [options] - Options object
|
|
738
|
-
* @param {Date} [options.date] - The date to check (defaults to current date)
|
|
739
|
-
* @returns {MarketOpenCloseResult} The market open/close times
|
|
705
|
+
* Get market open/close times for a given date
|
|
740
706
|
*/
|
|
741
707
|
function getMarketOpenClose(options = {}) {
|
|
742
|
-
const
|
|
743
|
-
return
|
|
708
|
+
const { date = new Date() } = options;
|
|
709
|
+
return marketTimeCalculator.getMarketTimes(date);
|
|
744
710
|
}
|
|
745
711
|
/**
|
|
746
|
-
*
|
|
747
|
-
* @param {MarketTimeParams} [params] - The market time parameters
|
|
748
|
-
* @returns {Object} The start and end dates
|
|
749
|
-
* @property {Date} start - The start date
|
|
750
|
-
* @property {Date} end - The end date
|
|
712
|
+
* Get start and end dates for a given market time period
|
|
751
713
|
*/
|
|
752
714
|
function getStartAndEndDates(params = {}) {
|
|
753
|
-
const
|
|
754
|
-
const effectiveParams = {
|
|
755
|
-
...params,
|
|
756
|
-
end: params.referenceDate || params.end || new Date(),
|
|
757
|
-
};
|
|
758
|
-
const { start, end } = util.getMarketTimePeriod(effectiveParams);
|
|
759
|
-
// Ensure the returned values are Dates
|
|
715
|
+
const { start, end } = periodCalculator.getMarketTimePeriod(params);
|
|
760
716
|
return {
|
|
761
717
|
start: new Date(start),
|
|
762
718
|
end: new Date(end),
|
|
763
719
|
};
|
|
764
720
|
}
|
|
765
721
|
/**
|
|
766
|
-
*
|
|
767
|
-
* @returns {string} The last trading date in YYYY-MM-DD format
|
|
768
|
-
*/
|
|
769
|
-
function getLastTradingDateYYYYMMDD() {
|
|
770
|
-
const util = new MarketTimeUtil();
|
|
771
|
-
const lastTradingDate = util.getLastTradingDate();
|
|
772
|
-
return format(lastTradingDate, 'yyyy-MM-dd');
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Gets the last full trading date
|
|
776
|
-
* @param {Date} [currentDate] - The current date (defaults to now)
|
|
777
|
-
* @returns {Object} The last full trading date
|
|
778
|
-
* @property {Date} date - The date object
|
|
779
|
-
* @property {string} YYYYMMDD - The date in YYYY-MM-DD format
|
|
722
|
+
* Get the last full trading date
|
|
780
723
|
*/
|
|
781
724
|
function getLastFullTradingDate(currentDate = new Date()) {
|
|
782
|
-
const
|
|
783
|
-
const date = util.getLastFullTradingDate(currentDate);
|
|
784
|
-
// Format the date in NY timezone to ensure consistency
|
|
725
|
+
const date = marketTimeCalculator.getLastFullTradingDate(currentDate);
|
|
785
726
|
return {
|
|
786
727
|
date,
|
|
787
|
-
YYYYMMDD:
|
|
728
|
+
YYYYMMDD: timeFormatter.getTradingDate(date),
|
|
788
729
|
};
|
|
789
730
|
}
|
|
790
731
|
/**
|
|
791
|
-
*
|
|
792
|
-
* @param {Object} [options] - Options object
|
|
793
|
-
* @param {Date} [options.referenceDate] - The reference date (defaults to current date)
|
|
794
|
-
* @returns {Object} The next market day information
|
|
795
|
-
* @property {Date} date - The date object (start of day in NY time)
|
|
796
|
-
* @property {string} yyyymmdd - The date in YYYY-MM-DD format
|
|
797
|
-
* @property {string} dateISOString - Full ISO date string
|
|
732
|
+
* Get the next market day
|
|
798
733
|
*/
|
|
799
734
|
function getNextMarketDay({ referenceDate } = {}) {
|
|
800
|
-
const util = new MarketTimeUtil();
|
|
801
735
|
const startDate = referenceDate || new Date();
|
|
802
|
-
const nextDate =
|
|
736
|
+
const nextDate = marketCalendar.getNextMarketDay(startDate);
|
|
803
737
|
// Convert to start of day in NY time
|
|
804
|
-
const startOfDayNY = startOfDay(toZonedTime(nextDate,
|
|
805
|
-
const dateInET = fromZonedTime(startOfDayNY,
|
|
738
|
+
const startOfDayNY = startOfDay(toZonedTime(nextDate, MARKET_CONFIG.TIMEZONE));
|
|
739
|
+
const dateInET = fromZonedTime(startOfDayNY, MARKET_CONFIG.TIMEZONE);
|
|
806
740
|
return {
|
|
807
741
|
date: dateInET,
|
|
808
|
-
yyyymmdd:
|
|
742
|
+
yyyymmdd: timeFormatter.getTradingDate(dateInET),
|
|
809
743
|
dateISOString: dateInET.toISOString(),
|
|
810
744
|
};
|
|
811
745
|
}
|
|
812
746
|
/**
|
|
813
|
-
*
|
|
814
|
-
* @returns {Date} The current time in Eastern Time
|
|
747
|
+
* Get trading date in YYYY-MM-DD format
|
|
815
748
|
*/
|
|
816
|
-
|
|
817
|
-
return
|
|
818
|
-
}
|
|
749
|
+
function getTradingDate(time) {
|
|
750
|
+
return timeFormatter.getTradingDate(time);
|
|
751
|
+
}
|
|
819
752
|
/**
|
|
820
|
-
*
|
|
821
|
-
* @param {number|string|Date} time - The time to convert
|
|
822
|
-
* @returns {Date} The date in New York timezone
|
|
753
|
+
* Get New York timezone offset
|
|
823
754
|
*/
|
|
824
|
-
function
|
|
825
|
-
|
|
826
|
-
if (typeof time === 'number' || typeof time === 'string' || time instanceof Date) {
|
|
827
|
-
// Assuming Unix timestamp in epoch milliseconds, string date, or Date object
|
|
828
|
-
date = new Date(time);
|
|
829
|
-
}
|
|
830
|
-
else {
|
|
831
|
-
// Assuming object with year, month, and day
|
|
832
|
-
date = new Date(time.year, time.month - 1, time.day);
|
|
833
|
-
}
|
|
834
|
-
return toZonedTime(date, 'America/New_York');
|
|
755
|
+
function getNYTimeZone(date) {
|
|
756
|
+
return timeFormatter.getNYTimeZone(date);
|
|
835
757
|
}
|
|
836
758
|
/**
|
|
837
|
-
*
|
|
838
|
-
* @param {string|number|Date} time - The time to convert (string, unix timestamp in ms, or Date object)
|
|
839
|
-
* @returns {string} The trading date in YYYY-MM-DD format
|
|
759
|
+
* Get current market status
|
|
840
760
|
*/
|
|
841
|
-
function
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
// Assuming Unix timestamp in milliseconds
|
|
845
|
-
date = new Date(time);
|
|
846
|
-
}
|
|
847
|
-
else if (typeof time === 'string') {
|
|
848
|
-
date = new Date(time);
|
|
849
|
-
}
|
|
850
|
-
else {
|
|
851
|
-
date = time;
|
|
852
|
-
}
|
|
853
|
-
// Convert to NY timezone and format as YYYY-MM-DD
|
|
854
|
-
return formatInTimeZone(date, MARKET_TIMES.TIMEZONE, 'yyyy-MM-dd');
|
|
761
|
+
function getMarketStatus(options = {}) {
|
|
762
|
+
const { date = new Date() } = options;
|
|
763
|
+
return marketStatusService.getMarketStatus(date);
|
|
855
764
|
}
|
|
856
765
|
/**
|
|
857
|
-
*
|
|
858
|
-
* @param dateString - The date string to check
|
|
859
|
-
* @returns "-04:00" during daylight savings (EDT) or "-05:00" during standard time (EST)
|
|
766
|
+
* Check if a date is a market day
|
|
860
767
|
*/
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
866
|
-
timeZone: 'America/New_York',
|
|
867
|
-
timeZoneName: 'shortOffset',
|
|
868
|
-
});
|
|
869
|
-
const parts = dtf.formatToParts(date);
|
|
870
|
-
const tz = parts.find((p) => p.type === 'timeZoneName')?.value;
|
|
871
|
-
// tz will be "GMT-5" or "GMT-4"
|
|
872
|
-
if (!tz) {
|
|
873
|
-
throw new Error('Could not determine New York offset');
|
|
874
|
-
}
|
|
875
|
-
// extract the -4 or -5 from the string
|
|
876
|
-
const shortOffset = tz.replace('GMT', '');
|
|
877
|
-
// return the correct offset
|
|
878
|
-
if (shortOffset === '-4') {
|
|
879
|
-
console.log(`New York is on EDT; using -04:00. Full date: ${date.toLocaleString('en-US', {
|
|
880
|
-
timeZone: 'America/New_York',
|
|
881
|
-
})}, time zone part: ${tz}`);
|
|
882
|
-
return '-04:00';
|
|
883
|
-
}
|
|
884
|
-
else if (shortOffset === '-5') {
|
|
885
|
-
console.log(`New York is on EST; using -05:00. Full date: ${date.toLocaleString('en-US', {
|
|
886
|
-
timeZone: 'America/New_York',
|
|
887
|
-
})}, time zone part: ${tz}`);
|
|
888
|
-
return '-05:00';
|
|
889
|
-
}
|
|
890
|
-
else {
|
|
891
|
-
throw new Error('Could not determine New York offset');
|
|
892
|
-
}
|
|
893
|
-
};
|
|
768
|
+
function isMarketDay(date) {
|
|
769
|
+
return marketCalendar.isMarketDay(date);
|
|
770
|
+
}
|
|
894
771
|
/**
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
772
|
+
* Function to find complete trading date periods, starting at the beginning of one trading date and ending at the last.
|
|
773
|
+
* By default, it gets the last trading date, returning the beginning and end. But we can also a) define the end date
|
|
774
|
+
* on which to look (which is the end market date, e.g. for 8 july 2025 we'd return an end of 16:00 NY time on that date),
|
|
775
|
+
* and to go back x days before, e.g. 7 would just subtract raw 7 days, giving us 4-5 trading days (depending on holidays).
|
|
776
|
+
* @param options.endDate - The end date to use, defaults to today
|
|
777
|
+
* @param options.days - The number of days to go back, defaults to 1
|
|
778
|
+
* @returns The start and end dates with proper market open/close times
|
|
899
779
|
*/
|
|
900
|
-
function
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
? MARKET_TIMES.EARLY_CLOSE_BEFORE_HOLIDAY.END.MINUTES
|
|
911
|
-
: MARKET_TIMES.REGULAR.END.MINUTES;
|
|
912
|
-
const extendedEndMinutes = isEarlyCloseDay
|
|
913
|
-
? MARKET_TIMES.EARLY_EXTENDED_BEFORE_HOLIDAY.END.MINUTES
|
|
914
|
-
: MARKET_TIMES.EXTENDED.END.MINUTES;
|
|
915
|
-
let status;
|
|
916
|
-
let nextStatus;
|
|
917
|
-
let nextStatusTime;
|
|
918
|
-
let marketPeriod;
|
|
919
|
-
const nextMarketDay = util.getNextMarketDay(nyTime);
|
|
920
|
-
// Determine current status and market period
|
|
921
|
-
if (!util.isMarketDay(nyTime)) {
|
|
922
|
-
// Not a market day! market is closed
|
|
923
|
-
marketPeriod = 'closed';
|
|
924
|
-
status = 'closed';
|
|
925
|
-
nextStatus = 'extended hours';
|
|
926
|
-
// Find next market day and set to extended hours start time
|
|
927
|
-
nextStatusTime = set(nextMarketDay, {
|
|
928
|
-
hours: MARKET_TIMES.EXTENDED.START.HOUR,
|
|
929
|
-
minutes: MARKET_TIMES.EXTENDED.START.MINUTE,
|
|
930
|
-
});
|
|
931
|
-
} // check if the market isn't in extended hours yet
|
|
932
|
-
else if (timeInMinutes >= 0 && timeInMinutes < extendedStartMinutes) {
|
|
933
|
-
marketPeriod = 'closed';
|
|
934
|
-
status = 'closed';
|
|
935
|
-
nextStatus = 'extended hours';
|
|
936
|
-
nextStatusTime = set(nyTime, {
|
|
937
|
-
hours: MARKET_TIMES.EXTENDED.START.HOUR,
|
|
938
|
-
minutes: MARKET_TIMES.EXTENDED.START.MINUTE,
|
|
939
|
-
});
|
|
940
|
-
// check if we're in pre-market hours
|
|
941
|
-
}
|
|
942
|
-
else if (timeInMinutes >= extendedStartMinutes && timeInMinutes < marketStartMinutes) {
|
|
943
|
-
marketPeriod = 'preMarket';
|
|
944
|
-
status = 'extended hours';
|
|
945
|
-
nextStatus = 'open';
|
|
946
|
-
nextStatusTime = set(nyTime, {
|
|
947
|
-
hours: MARKET_TIMES.REGULAR.START.HOUR,
|
|
948
|
-
minutes: MARKET_TIMES.REGULAR.START.MINUTE,
|
|
949
|
-
});
|
|
950
|
-
// check if market is open
|
|
951
|
-
}
|
|
952
|
-
else if (timeInMinutes >= marketStartMinutes && timeInMinutes < marketRegularCloseMinutes) {
|
|
953
|
-
status = 'open';
|
|
954
|
-
nextStatus = 'extended hours';
|
|
955
|
-
// market is open, but just check the marketPeriod - could be earlyMarket or regularMarket
|
|
956
|
-
marketPeriod = timeInMinutes < MARKET_TIMES.EARLY_MORNING.END.MINUTES ? 'earlyMarket' : 'regularMarket';
|
|
957
|
-
nextStatusTime = isEarlyCloseDay
|
|
958
|
-
? set(nyTime, {
|
|
959
|
-
hours: MARKET_TIMES.EARLY_CLOSE_BEFORE_HOLIDAY.END.HOUR,
|
|
960
|
-
minutes: MARKET_TIMES.EARLY_CLOSE_BEFORE_HOLIDAY.END.MINUTE,
|
|
961
|
-
})
|
|
962
|
-
: set(nyTime, {
|
|
963
|
-
hours: MARKET_TIMES.REGULAR.END.HOUR,
|
|
964
|
-
minutes: MARKET_TIMES.REGULAR.END.MINUTE,
|
|
965
|
-
});
|
|
966
|
-
// check if it's after-market extended hours
|
|
967
|
-
}
|
|
968
|
-
else if (timeInMinutes >= marketRegularCloseMinutes && timeInMinutes < extendedEndMinutes) {
|
|
969
|
-
status = 'extended hours';
|
|
970
|
-
nextStatus = 'closed';
|
|
971
|
-
marketPeriod = 'afterMarket';
|
|
972
|
-
nextStatusTime = isEarlyCloseDay
|
|
973
|
-
? set(nyTime, {
|
|
974
|
-
hours: MARKET_TIMES.EARLY_EXTENDED_BEFORE_HOLIDAY.END.HOUR,
|
|
975
|
-
minutes: MARKET_TIMES.EARLY_EXTENDED_BEFORE_HOLIDAY.END.MINUTE,
|
|
976
|
-
})
|
|
977
|
-
: set(nyTime, {
|
|
978
|
-
hours: MARKET_TIMES.EXTENDED.END.HOUR,
|
|
979
|
-
minutes: MARKET_TIMES.EXTENDED.END.MINUTE,
|
|
980
|
-
});
|
|
981
|
-
// otherwise, the market is closed
|
|
780
|
+
function getTradingStartAndEndDates(options = {}) {
|
|
781
|
+
const { endDate = new Date(), days = 1 } = options;
|
|
782
|
+
// Get the last full trading date for the end date
|
|
783
|
+
const endTradingDate = getLastFullTradingDate(endDate).date;
|
|
784
|
+
// Get the market close time for the end date (4:00 PM ET or 1:00 PM on short days)
|
|
785
|
+
const endMarketClose = getMarketOpenClose({ date: endTradingDate }).close;
|
|
786
|
+
let startDate;
|
|
787
|
+
if (days <= 1) {
|
|
788
|
+
// For 1 day, start is market open of the same trading day as end
|
|
789
|
+
startDate = getMarketOpenClose({ date: endTradingDate }).open;
|
|
982
790
|
}
|
|
983
791
|
else {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
792
|
+
// For multiple days, go back the specified number of trading days
|
|
793
|
+
let currentDate = new Date(endTradingDate);
|
|
794
|
+
let tradingDaysBack = 0;
|
|
795
|
+
// Count back trading days
|
|
796
|
+
while (tradingDaysBack < days - 1) {
|
|
797
|
+
currentDate = sub(currentDate, { days: 1 });
|
|
798
|
+
if (isMarketDay(currentDate)) {
|
|
799
|
+
tradingDaysBack++;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
// Get the market open time for the start date
|
|
803
|
+
startDate = getMarketOpenClose({ date: currentDate }).open;
|
|
991
804
|
}
|
|
992
|
-
|
|
993
|
-
return {
|
|
994
|
-
time: now,
|
|
995
|
-
timeString: format(nyTime, dateFormat),
|
|
996
|
-
status,
|
|
997
|
-
nextStatus,
|
|
998
|
-
marketPeriod,
|
|
999
|
-
nextStatusTime: fromZonedTime(nextStatusTime, MARKET_TIMES.TIMEZONE),
|
|
1000
|
-
nextStatusTimeDifference: differenceInMilliseconds(nextStatusTime, nyTime),
|
|
1001
|
-
nextStatusTimeString: format(nextStatusTime, dateFormat),
|
|
1002
|
-
};
|
|
805
|
+
return { startDate, endDate: endMarketClose };
|
|
1003
806
|
}
|
|
1004
807
|
|
|
1005
808
|
// format-tools.ts
|
|
@@ -2429,7 +2232,7 @@ const safeJSON = (text) => {
|
|
|
2429
2232
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2430
2233
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2431
2234
|
|
|
2432
|
-
const VERSION = '5.8.
|
|
2235
|
+
const VERSION = '5.8.3'; // x-release-please-version
|
|
2433
2236
|
|
|
2434
2237
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2435
2238
|
const isRunningInBrowser = () => {
|
|
@@ -3181,13 +2984,95 @@ function findDoubleNewlineIndex(buffer) {
|
|
|
3181
2984
|
return -1;
|
|
3182
2985
|
}
|
|
3183
2986
|
|
|
2987
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2988
|
+
const levelNumbers = {
|
|
2989
|
+
off: 0,
|
|
2990
|
+
error: 200,
|
|
2991
|
+
warn: 300,
|
|
2992
|
+
info: 400,
|
|
2993
|
+
debug: 500,
|
|
2994
|
+
};
|
|
2995
|
+
const parseLogLevel = (maybeLevel, sourceName, client) => {
|
|
2996
|
+
if (!maybeLevel) {
|
|
2997
|
+
return undefined;
|
|
2998
|
+
}
|
|
2999
|
+
if (hasOwn(levelNumbers, maybeLevel)) {
|
|
3000
|
+
return maybeLevel;
|
|
3001
|
+
}
|
|
3002
|
+
loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
|
|
3003
|
+
return undefined;
|
|
3004
|
+
};
|
|
3005
|
+
function noop() { }
|
|
3006
|
+
function makeLogFn(fnLevel, logger, logLevel) {
|
|
3007
|
+
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
|
|
3008
|
+
return noop;
|
|
3009
|
+
}
|
|
3010
|
+
else {
|
|
3011
|
+
// Don't wrap logger functions, we want the stacktrace intact!
|
|
3012
|
+
return logger[fnLevel].bind(logger);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
const noopLogger = {
|
|
3016
|
+
error: noop,
|
|
3017
|
+
warn: noop,
|
|
3018
|
+
info: noop,
|
|
3019
|
+
debug: noop,
|
|
3020
|
+
};
|
|
3021
|
+
let cachedLoggers = /* @__PURE__ */ new WeakMap();
|
|
3022
|
+
function loggerFor(client) {
|
|
3023
|
+
const logger = client.logger;
|
|
3024
|
+
const logLevel = client.logLevel ?? 'off';
|
|
3025
|
+
if (!logger) {
|
|
3026
|
+
return noopLogger;
|
|
3027
|
+
}
|
|
3028
|
+
const cachedLogger = cachedLoggers.get(logger);
|
|
3029
|
+
if (cachedLogger && cachedLogger[0] === logLevel) {
|
|
3030
|
+
return cachedLogger[1];
|
|
3031
|
+
}
|
|
3032
|
+
const levelLogger = {
|
|
3033
|
+
error: makeLogFn('error', logger, logLevel),
|
|
3034
|
+
warn: makeLogFn('warn', logger, logLevel),
|
|
3035
|
+
info: makeLogFn('info', logger, logLevel),
|
|
3036
|
+
debug: makeLogFn('debug', logger, logLevel),
|
|
3037
|
+
};
|
|
3038
|
+
cachedLoggers.set(logger, [logLevel, levelLogger]);
|
|
3039
|
+
return levelLogger;
|
|
3040
|
+
}
|
|
3041
|
+
const formatRequestDetails = (details) => {
|
|
3042
|
+
if (details.options) {
|
|
3043
|
+
details.options = { ...details.options };
|
|
3044
|
+
delete details.options['headers']; // redundant + leaks internals
|
|
3045
|
+
}
|
|
3046
|
+
if (details.headers) {
|
|
3047
|
+
details.headers = Object.fromEntries((details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(([name, value]) => [
|
|
3048
|
+
name,
|
|
3049
|
+
(name.toLowerCase() === 'authorization' ||
|
|
3050
|
+
name.toLowerCase() === 'cookie' ||
|
|
3051
|
+
name.toLowerCase() === 'set-cookie') ?
|
|
3052
|
+
'***'
|
|
3053
|
+
: value,
|
|
3054
|
+
]));
|
|
3055
|
+
}
|
|
3056
|
+
if ('retryOfRequestLogID' in details) {
|
|
3057
|
+
if (details.retryOfRequestLogID) {
|
|
3058
|
+
details.retryOf = details.retryOfRequestLogID;
|
|
3059
|
+
}
|
|
3060
|
+
delete details.retryOfRequestLogID;
|
|
3061
|
+
}
|
|
3062
|
+
return details;
|
|
3063
|
+
};
|
|
3064
|
+
|
|
3065
|
+
var _Stream_client;
|
|
3184
3066
|
class Stream {
|
|
3185
|
-
constructor(iterator, controller) {
|
|
3067
|
+
constructor(iterator, controller, client) {
|
|
3186
3068
|
this.iterator = iterator;
|
|
3069
|
+
_Stream_client.set(this, void 0);
|
|
3187
3070
|
this.controller = controller;
|
|
3071
|
+
__classPrivateFieldSet(this, _Stream_client, client);
|
|
3188
3072
|
}
|
|
3189
|
-
static fromSSEResponse(response, controller) {
|
|
3073
|
+
static fromSSEResponse(response, controller, client) {
|
|
3190
3074
|
let consumed = false;
|
|
3075
|
+
const logger = client ? loggerFor(client) : console;
|
|
3191
3076
|
async function* iterator() {
|
|
3192
3077
|
if (consumed) {
|
|
3193
3078
|
throw new OpenAIError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
|
|
@@ -3210,8 +3095,8 @@ class Stream {
|
|
|
3210
3095
|
data = JSON.parse(sse.data);
|
|
3211
3096
|
}
|
|
3212
3097
|
catch (e) {
|
|
3213
|
-
|
|
3214
|
-
|
|
3098
|
+
logger.error(`Could not parse message into JSON:`, sse.data);
|
|
3099
|
+
logger.error(`From chunk:`, sse.raw);
|
|
3215
3100
|
throw e;
|
|
3216
3101
|
}
|
|
3217
3102
|
if (data && data.error) {
|
|
@@ -3250,13 +3135,13 @@ class Stream {
|
|
|
3250
3135
|
controller.abort();
|
|
3251
3136
|
}
|
|
3252
3137
|
}
|
|
3253
|
-
return new Stream(iterator, controller);
|
|
3138
|
+
return new Stream(iterator, controller, client);
|
|
3254
3139
|
}
|
|
3255
3140
|
/**
|
|
3256
3141
|
* Generates a Stream from a newline-separated ReadableStream
|
|
3257
3142
|
* where each item is a JSON value.
|
|
3258
3143
|
*/
|
|
3259
|
-
static fromReadableStream(readableStream, controller) {
|
|
3144
|
+
static fromReadableStream(readableStream, controller, client) {
|
|
3260
3145
|
let consumed = false;
|
|
3261
3146
|
async function* iterLines() {
|
|
3262
3147
|
const lineDecoder = new LineDecoder();
|
|
@@ -3297,9 +3182,9 @@ class Stream {
|
|
|
3297
3182
|
controller.abort();
|
|
3298
3183
|
}
|
|
3299
3184
|
}
|
|
3300
|
-
return new Stream(iterator, controller);
|
|
3185
|
+
return new Stream(iterator, controller, client);
|
|
3301
3186
|
}
|
|
3302
|
-
[Symbol.asyncIterator]() {
|
|
3187
|
+
[(_Stream_client = new WeakMap(), Symbol.asyncIterator)]() {
|
|
3303
3188
|
return this.iterator();
|
|
3304
3189
|
}
|
|
3305
3190
|
/**
|
|
@@ -3323,8 +3208,8 @@ class Stream {
|
|
|
3323
3208
|
};
|
|
3324
3209
|
};
|
|
3325
3210
|
return [
|
|
3326
|
-
new Stream(() => teeIterator(left), this.controller),
|
|
3327
|
-
new Stream(() => teeIterator(right), this.controller),
|
|
3211
|
+
new Stream(() => teeIterator(left), this.controller, __classPrivateFieldGet(this, _Stream_client, "f")),
|
|
3212
|
+
new Stream(() => teeIterator(right), this.controller, __classPrivateFieldGet(this, _Stream_client, "f")),
|
|
3328
3213
|
];
|
|
3329
3214
|
}
|
|
3330
3215
|
/**
|
|
@@ -3444,97 +3329,19 @@ class SSEDecoder {
|
|
|
3444
3329
|
if (fieldname === 'event') {
|
|
3445
3330
|
this.event = value;
|
|
3446
3331
|
}
|
|
3447
|
-
else if (fieldname === 'data') {
|
|
3448
|
-
this.data.push(value);
|
|
3449
|
-
}
|
|
3450
|
-
return null;
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
function partition(str, delimiter) {
|
|
3454
|
-
const index = str.indexOf(delimiter);
|
|
3455
|
-
if (index !== -1) {
|
|
3456
|
-
return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
|
|
3457
|
-
}
|
|
3458
|
-
return [str, '', ''];
|
|
3459
|
-
}
|
|
3460
|
-
|
|
3461
|
-
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
3462
|
-
const levelNumbers = {
|
|
3463
|
-
off: 0,
|
|
3464
|
-
error: 200,
|
|
3465
|
-
warn: 300,
|
|
3466
|
-
info: 400,
|
|
3467
|
-
debug: 500,
|
|
3468
|
-
};
|
|
3469
|
-
const parseLogLevel = (maybeLevel, sourceName, client) => {
|
|
3470
|
-
if (!maybeLevel) {
|
|
3471
|
-
return undefined;
|
|
3472
|
-
}
|
|
3473
|
-
if (hasOwn(levelNumbers, maybeLevel)) {
|
|
3474
|
-
return maybeLevel;
|
|
3475
|
-
}
|
|
3476
|
-
loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
|
|
3477
|
-
return undefined;
|
|
3478
|
-
};
|
|
3479
|
-
function noop() { }
|
|
3480
|
-
function makeLogFn(fnLevel, logger, logLevel) {
|
|
3481
|
-
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
|
|
3482
|
-
return noop;
|
|
3483
|
-
}
|
|
3484
|
-
else {
|
|
3485
|
-
// Don't wrap logger functions, we want the stacktrace intact!
|
|
3486
|
-
return logger[fnLevel].bind(logger);
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
const noopLogger = {
|
|
3490
|
-
error: noop,
|
|
3491
|
-
warn: noop,
|
|
3492
|
-
info: noop,
|
|
3493
|
-
debug: noop,
|
|
3494
|
-
};
|
|
3495
|
-
let cachedLoggers = /** @__PURE__ */ new WeakMap();
|
|
3496
|
-
function loggerFor(client) {
|
|
3497
|
-
const logger = client.logger;
|
|
3498
|
-
const logLevel = client.logLevel ?? 'off';
|
|
3499
|
-
if (!logger) {
|
|
3500
|
-
return noopLogger;
|
|
3501
|
-
}
|
|
3502
|
-
const cachedLogger = cachedLoggers.get(logger);
|
|
3503
|
-
if (cachedLogger && cachedLogger[0] === logLevel) {
|
|
3504
|
-
return cachedLogger[1];
|
|
3332
|
+
else if (fieldname === 'data') {
|
|
3333
|
+
this.data.push(value);
|
|
3334
|
+
}
|
|
3335
|
+
return null;
|
|
3505
3336
|
}
|
|
3506
|
-
const levelLogger = {
|
|
3507
|
-
error: makeLogFn('error', logger, logLevel),
|
|
3508
|
-
warn: makeLogFn('warn', logger, logLevel),
|
|
3509
|
-
info: makeLogFn('info', logger, logLevel),
|
|
3510
|
-
debug: makeLogFn('debug', logger, logLevel),
|
|
3511
|
-
};
|
|
3512
|
-
cachedLoggers.set(logger, [logLevel, levelLogger]);
|
|
3513
|
-
return levelLogger;
|
|
3514
3337
|
}
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
}
|
|
3520
|
-
if (details.headers) {
|
|
3521
|
-
details.headers = Object.fromEntries((details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(([name, value]) => [
|
|
3522
|
-
name,
|
|
3523
|
-
(name.toLowerCase() === 'authorization' ||
|
|
3524
|
-
name.toLowerCase() === 'cookie' ||
|
|
3525
|
-
name.toLowerCase() === 'set-cookie') ?
|
|
3526
|
-
'***'
|
|
3527
|
-
: value,
|
|
3528
|
-
]));
|
|
3529
|
-
}
|
|
3530
|
-
if ('retryOfRequestLogID' in details) {
|
|
3531
|
-
if (details.retryOfRequestLogID) {
|
|
3532
|
-
details.retryOf = details.retryOfRequestLogID;
|
|
3533
|
-
}
|
|
3534
|
-
delete details.retryOfRequestLogID;
|
|
3338
|
+
function partition(str, delimiter) {
|
|
3339
|
+
const index = str.indexOf(delimiter);
|
|
3340
|
+
if (index !== -1) {
|
|
3341
|
+
return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
|
|
3535
3342
|
}
|
|
3536
|
-
return
|
|
3537
|
-
}
|
|
3343
|
+
return [str, '', ''];
|
|
3344
|
+
}
|
|
3538
3345
|
|
|
3539
3346
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
3540
3347
|
async function defaultParseResponse(client, props) {
|
|
@@ -3545,9 +3352,9 @@ async function defaultParseResponse(client, props) {
|
|
|
3545
3352
|
// Note: there is an invariant here that isn't represented in the type system
|
|
3546
3353
|
// that if you set `stream: true` the response type must also be `Stream<T>`
|
|
3547
3354
|
if (props.options.__streamClass) {
|
|
3548
|
-
return props.options.__streamClass.fromSSEResponse(response, props.controller);
|
|
3355
|
+
return props.options.__streamClass.fromSSEResponse(response, props.controller, client);
|
|
3549
3356
|
}
|
|
3550
|
-
return Stream.fromSSEResponse(response, props.controller);
|
|
3357
|
+
return Stream.fromSSEResponse(response, props.controller, client);
|
|
3551
3358
|
}
|
|
3552
3359
|
// fetch refuses to read the body when the status code is 204.
|
|
3553
3360
|
if (response.status === 204) {
|
|
@@ -3801,7 +3608,7 @@ const isAsyncIterable = (value) => value != null && typeof value === 'object' &&
|
|
|
3801
3608
|
const multipartFormRequestOptions = async (opts, fetch) => {
|
|
3802
3609
|
return { ...opts, body: await createForm(opts.body, fetch) };
|
|
3803
3610
|
};
|
|
3804
|
-
const supportsFormDataMap =
|
|
3611
|
+
const supportsFormDataMap = /* @__PURE__ */ new WeakMap();
|
|
3805
3612
|
/**
|
|
3806
3613
|
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
|
|
3807
3614
|
* properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
|
|
@@ -3977,21 +3784,38 @@ class APIResource {
|
|
|
3977
3784
|
function encodeURIPath(str) {
|
|
3978
3785
|
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
|
|
3979
3786
|
}
|
|
3787
|
+
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3980
3788
|
const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics, ...params) {
|
|
3981
3789
|
// If there are no params, no processing is needed.
|
|
3982
3790
|
if (statics.length === 1)
|
|
3983
3791
|
return statics[0];
|
|
3984
3792
|
let postPath = false;
|
|
3793
|
+
const invalidSegments = [];
|
|
3985
3794
|
const path = statics.reduce((previousValue, currentValue, index) => {
|
|
3986
3795
|
if (/[?#]/.test(currentValue)) {
|
|
3987
3796
|
postPath = true;
|
|
3988
3797
|
}
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3798
|
+
const value = params[index];
|
|
3799
|
+
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
|
|
3800
|
+
if (index !== params.length &&
|
|
3801
|
+
(value == null ||
|
|
3802
|
+
(typeof value === 'object' &&
|
|
3803
|
+
// handle values from other realms
|
|
3804
|
+
value.toString ===
|
|
3805
|
+
Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY) ?? EMPTY)
|
|
3806
|
+
?.toString))) {
|
|
3807
|
+
encoded = value + '';
|
|
3808
|
+
invalidSegments.push({
|
|
3809
|
+
start: previousValue.length + currentValue.length,
|
|
3810
|
+
length: encoded.length,
|
|
3811
|
+
error: `Value of type ${Object.prototype.toString
|
|
3812
|
+
.call(value)
|
|
3813
|
+
.slice(8, -1)} is not a valid path parameter`,
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
return previousValue + currentValue + (index === params.length ? '' : encoded);
|
|
3992
3817
|
}, '');
|
|
3993
3818
|
const pathOnly = path.split(/[?#]/, 1)[0];
|
|
3994
|
-
const invalidSegments = [];
|
|
3995
3819
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
3996
3820
|
let match;
|
|
3997
3821
|
// Find all invalid segments
|
|
@@ -3999,8 +3823,10 @@ const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(sta
|
|
|
3999
3823
|
invalidSegments.push({
|
|
4000
3824
|
start: match.index,
|
|
4001
3825
|
length: match[0].length,
|
|
3826
|
+
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
|
|
4002
3827
|
});
|
|
4003
3828
|
}
|
|
3829
|
+
invalidSegments.sort((a, b) => a.start - b.start);
|
|
4004
3830
|
if (invalidSegments.length > 0) {
|
|
4005
3831
|
let lastEnd = 0;
|
|
4006
3832
|
const underline = invalidSegments.reduce((acc, segment) => {
|
|
@@ -4009,7 +3835,9 @@ const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(sta
|
|
|
4009
3835
|
lastEnd = segment.start + segment.length;
|
|
4010
3836
|
return acc + spaces + arrows;
|
|
4011
3837
|
}, '');
|
|
4012
|
-
throw new OpenAIError(`Path parameters result in path with invalid segments:\n${
|
|
3838
|
+
throw new OpenAIError(`Path parameters result in path with invalid segments:\n${invalidSegments
|
|
3839
|
+
.map((e) => e.error)
|
|
3840
|
+
.join('\n')}\n${path}\n${underline}`);
|
|
4013
3841
|
}
|
|
4014
3842
|
return path;
|
|
4015
3843
|
};
|
|
@@ -12053,7 +11881,7 @@ function requireSender () {
|
|
|
12053
11881
|
hasRequiredSender = 1;
|
|
12054
11882
|
|
|
12055
11883
|
const { Duplex } = require$$0$2;
|
|
12056
|
-
const { randomFillSync } = require$$
|
|
11884
|
+
const { randomFillSync } = require$$3;
|
|
12057
11885
|
|
|
12058
11886
|
const PerMessageDeflate = requirePermessageDeflate();
|
|
12059
11887
|
const { EMPTY_BUFFER, kWebSocket, NOOP } = requireConstants();
|
|
@@ -13174,11 +13002,11 @@ function requireWebsocket () {
|
|
|
13174
13002
|
hasRequiredWebsocket = 1;
|
|
13175
13003
|
|
|
13176
13004
|
const EventEmitter = require$$0$3;
|
|
13177
|
-
const https = require$$1
|
|
13005
|
+
const https = require$$1;
|
|
13178
13006
|
const http = require$$2;
|
|
13179
|
-
const net = require$$3;
|
|
13180
|
-
const tls = require$$4;
|
|
13181
|
-
const { randomBytes, createHash } = require$$
|
|
13007
|
+
const net = require$$3$1;
|
|
13008
|
+
const tls = require$$4$1;
|
|
13009
|
+
const { randomBytes, createHash } = require$$3;
|
|
13182
13010
|
const { Duplex, Readable } = require$$0$2;
|
|
13183
13011
|
const { URL } = require$$7;
|
|
13184
13012
|
|
|
@@ -14821,7 +14649,7 @@ function requireWebsocketServer () {
|
|
|
14821
14649
|
const EventEmitter = require$$0$3;
|
|
14822
14650
|
const http = require$$2;
|
|
14823
14651
|
const { Duplex } = require$$0$2;
|
|
14824
|
-
const { createHash } = require$$
|
|
14652
|
+
const { createHash } = require$$3;
|
|
14825
14653
|
|
|
14826
14654
|
const extension = requireExtension();
|
|
14827
14655
|
const PerMessageDeflate = requirePermessageDeflate();
|
|
@@ -15369,35 +15197,522 @@ function requireWebsocketServer () {
|
|
|
15369
15197
|
|
|
15370
15198
|
requireWebsocketServer();
|
|
15371
15199
|
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
|
|
15395
|
-
|
|
15396
|
-
|
|
15397
|
-
|
|
15398
|
-
|
|
15200
|
+
var config = {};
|
|
15201
|
+
|
|
15202
|
+
var main = {exports: {}};
|
|
15203
|
+
|
|
15204
|
+
var version = "17.1.0";
|
|
15205
|
+
var require$$4 = {
|
|
15206
|
+
version: version};
|
|
15207
|
+
|
|
15208
|
+
var hasRequiredMain;
|
|
15209
|
+
|
|
15210
|
+
function requireMain () {
|
|
15211
|
+
if (hasRequiredMain) return main.exports;
|
|
15212
|
+
hasRequiredMain = 1;
|
|
15213
|
+
const fs = require$$0$4;
|
|
15214
|
+
const path = require$$1$1;
|
|
15215
|
+
const os = require$$2$1;
|
|
15216
|
+
const crypto = require$$3;
|
|
15217
|
+
const packageJson = require$$4;
|
|
15218
|
+
|
|
15219
|
+
const version = packageJson.version;
|
|
15220
|
+
|
|
15221
|
+
// Array of tips to display randomly
|
|
15222
|
+
const TIPS = [
|
|
15223
|
+
'🔐 encrypt with dotenvx: https://dotenvx.com',
|
|
15224
|
+
'🔐 prevent committing .env to code: https://dotenvx.com/precommit',
|
|
15225
|
+
'🔐 prevent building .env in docker: https://dotenvx.com/prebuild',
|
|
15226
|
+
'🛠️ run anywhere with `dotenvx run -- yourcommand`',
|
|
15227
|
+
'⚙️ specify custom .env file path with { path: \'/custom/path/.env\' }',
|
|
15228
|
+
'⚙️ enable debug logging with { debug: true }',
|
|
15229
|
+
'⚙️ override existing env vars with { override: true }',
|
|
15230
|
+
'⚙️ suppress all logs with { quiet: true }',
|
|
15231
|
+
'⚙️ write to custom object with { processEnv: myObject }',
|
|
15232
|
+
'⚙️ load multiple .env files with { path: [\'.env.local\', \'.env\'] }'
|
|
15233
|
+
];
|
|
15234
|
+
|
|
15235
|
+
// Get a random tip from the tips array
|
|
15236
|
+
function _getRandomTip () {
|
|
15237
|
+
return TIPS[Math.floor(Math.random() * TIPS.length)]
|
|
15238
|
+
}
|
|
15239
|
+
|
|
15240
|
+
function supportsAnsi () {
|
|
15241
|
+
return process.stdout.isTTY // && process.env.TERM !== 'dumb'
|
|
15242
|
+
}
|
|
15243
|
+
|
|
15244
|
+
function dim (text) {
|
|
15245
|
+
return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text
|
|
15246
|
+
}
|
|
15247
|
+
|
|
15248
|
+
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
15249
|
+
|
|
15250
|
+
// Parse src into an Object
|
|
15251
|
+
function parse (src) {
|
|
15252
|
+
const obj = {};
|
|
15253
|
+
|
|
15254
|
+
// Convert buffer to string
|
|
15255
|
+
let lines = src.toString();
|
|
15256
|
+
|
|
15257
|
+
// Convert line breaks to same format
|
|
15258
|
+
lines = lines.replace(/\r\n?/mg, '\n');
|
|
15259
|
+
|
|
15260
|
+
let match;
|
|
15261
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
15262
|
+
const key = match[1];
|
|
15263
|
+
|
|
15264
|
+
// Default undefined or null to empty string
|
|
15265
|
+
let value = (match[2] || '');
|
|
15266
|
+
|
|
15267
|
+
// Remove whitespace
|
|
15268
|
+
value = value.trim();
|
|
15269
|
+
|
|
15270
|
+
// Check if double quoted
|
|
15271
|
+
const maybeQuote = value[0];
|
|
15272
|
+
|
|
15273
|
+
// Remove surrounding quotes
|
|
15274
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2');
|
|
15275
|
+
|
|
15276
|
+
// Expand newlines if double quoted
|
|
15277
|
+
if (maybeQuote === '"') {
|
|
15278
|
+
value = value.replace(/\\n/g, '\n');
|
|
15279
|
+
value = value.replace(/\\r/g, '\r');
|
|
15280
|
+
}
|
|
15281
|
+
|
|
15282
|
+
// Add to object
|
|
15283
|
+
obj[key] = value;
|
|
15284
|
+
}
|
|
15285
|
+
|
|
15286
|
+
return obj
|
|
15287
|
+
}
|
|
15288
|
+
|
|
15289
|
+
function _parseVault (options) {
|
|
15290
|
+
options = options || {};
|
|
15291
|
+
|
|
15292
|
+
const vaultPath = _vaultPath(options);
|
|
15293
|
+
options.path = vaultPath; // parse .env.vault
|
|
15294
|
+
const result = DotenvModule.configDotenv(options);
|
|
15295
|
+
if (!result.parsed) {
|
|
15296
|
+
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
15297
|
+
err.code = 'MISSING_DATA';
|
|
15298
|
+
throw err
|
|
15299
|
+
}
|
|
15300
|
+
|
|
15301
|
+
// handle scenario for comma separated keys - for use with key rotation
|
|
15302
|
+
// example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
|
|
15303
|
+
const keys = _dotenvKey(options).split(',');
|
|
15304
|
+
const length = keys.length;
|
|
15305
|
+
|
|
15306
|
+
let decrypted;
|
|
15307
|
+
for (let i = 0; i < length; i++) {
|
|
15308
|
+
try {
|
|
15309
|
+
// Get full key
|
|
15310
|
+
const key = keys[i].trim();
|
|
15311
|
+
|
|
15312
|
+
// Get instructions for decrypt
|
|
15313
|
+
const attrs = _instructions(result, key);
|
|
15314
|
+
|
|
15315
|
+
// Decrypt
|
|
15316
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
15317
|
+
|
|
15318
|
+
break
|
|
15319
|
+
} catch (error) {
|
|
15320
|
+
// last key
|
|
15321
|
+
if (i + 1 >= length) {
|
|
15322
|
+
throw error
|
|
15323
|
+
}
|
|
15324
|
+
// try next key
|
|
15325
|
+
}
|
|
15326
|
+
}
|
|
15327
|
+
|
|
15328
|
+
// Parse decrypted .env string
|
|
15329
|
+
return DotenvModule.parse(decrypted)
|
|
15330
|
+
}
|
|
15331
|
+
|
|
15332
|
+
function _warn (message) {
|
|
15333
|
+
console.error(`[dotenv@${version}][WARN] ${message}`);
|
|
15334
|
+
}
|
|
15335
|
+
|
|
15336
|
+
function _debug (message) {
|
|
15337
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
15338
|
+
}
|
|
15339
|
+
|
|
15340
|
+
function _log (message) {
|
|
15341
|
+
console.log(`[dotenv@${version}] ${message}`);
|
|
15342
|
+
}
|
|
15343
|
+
|
|
15344
|
+
function _dotenvKey (options) {
|
|
15345
|
+
// prioritize developer directly setting options.DOTENV_KEY
|
|
15346
|
+
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
15347
|
+
return options.DOTENV_KEY
|
|
15348
|
+
}
|
|
15349
|
+
|
|
15350
|
+
// secondary infra already contains a DOTENV_KEY environment variable
|
|
15351
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
15352
|
+
return process.env.DOTENV_KEY
|
|
15353
|
+
}
|
|
15354
|
+
|
|
15355
|
+
// fallback to empty string
|
|
15356
|
+
return ''
|
|
15357
|
+
}
|
|
15358
|
+
|
|
15359
|
+
function _instructions (result, dotenvKey) {
|
|
15360
|
+
// Parse DOTENV_KEY. Format is a URI
|
|
15361
|
+
let uri;
|
|
15362
|
+
try {
|
|
15363
|
+
uri = new URL(dotenvKey);
|
|
15364
|
+
} catch (error) {
|
|
15365
|
+
if (error.code === 'ERR_INVALID_URL') {
|
|
15366
|
+
const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development');
|
|
15367
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
15368
|
+
throw err
|
|
15369
|
+
}
|
|
15370
|
+
|
|
15371
|
+
throw error
|
|
15372
|
+
}
|
|
15373
|
+
|
|
15374
|
+
// Get decrypt key
|
|
15375
|
+
const key = uri.password;
|
|
15376
|
+
if (!key) {
|
|
15377
|
+
const err = new Error('INVALID_DOTENV_KEY: Missing key part');
|
|
15378
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
15379
|
+
throw err
|
|
15380
|
+
}
|
|
15381
|
+
|
|
15382
|
+
// Get environment
|
|
15383
|
+
const environment = uri.searchParams.get('environment');
|
|
15384
|
+
if (!environment) {
|
|
15385
|
+
const err = new Error('INVALID_DOTENV_KEY: Missing environment part');
|
|
15386
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
15387
|
+
throw err
|
|
15388
|
+
}
|
|
15389
|
+
|
|
15390
|
+
// Get ciphertext payload
|
|
15391
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
15392
|
+
const ciphertext = result.parsed[environmentKey]; // DOTENV_VAULT_PRODUCTION
|
|
15393
|
+
if (!ciphertext) {
|
|
15394
|
+
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
15395
|
+
err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
|
|
15396
|
+
throw err
|
|
15397
|
+
}
|
|
15398
|
+
|
|
15399
|
+
return { ciphertext, key }
|
|
15400
|
+
}
|
|
15401
|
+
|
|
15402
|
+
function _vaultPath (options) {
|
|
15403
|
+
let possibleVaultPath = null;
|
|
15404
|
+
|
|
15405
|
+
if (options && options.path && options.path.length > 0) {
|
|
15406
|
+
if (Array.isArray(options.path)) {
|
|
15407
|
+
for (const filepath of options.path) {
|
|
15408
|
+
if (fs.existsSync(filepath)) {
|
|
15409
|
+
possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
|
|
15410
|
+
}
|
|
15411
|
+
}
|
|
15412
|
+
} else {
|
|
15413
|
+
possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
|
|
15414
|
+
}
|
|
15415
|
+
} else {
|
|
15416
|
+
possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
|
|
15417
|
+
}
|
|
15418
|
+
|
|
15419
|
+
if (fs.existsSync(possibleVaultPath)) {
|
|
15420
|
+
return possibleVaultPath
|
|
15421
|
+
}
|
|
15422
|
+
|
|
15423
|
+
return null
|
|
15424
|
+
}
|
|
15425
|
+
|
|
15426
|
+
function _resolveHome (envPath) {
|
|
15427
|
+
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
|
|
15428
|
+
}
|
|
15429
|
+
|
|
15430
|
+
function _configVault (options) {
|
|
15431
|
+
const debug = Boolean(options && options.debug);
|
|
15432
|
+
const quiet = Boolean(options && options.quiet);
|
|
15433
|
+
|
|
15434
|
+
if (debug || !quiet) {
|
|
15435
|
+
_log('Loading env from encrypted .env.vault');
|
|
15436
|
+
}
|
|
15437
|
+
|
|
15438
|
+
const parsed = DotenvModule._parseVault(options);
|
|
15439
|
+
|
|
15440
|
+
let processEnv = process.env;
|
|
15441
|
+
if (options && options.processEnv != null) {
|
|
15442
|
+
processEnv = options.processEnv;
|
|
15443
|
+
}
|
|
15444
|
+
|
|
15445
|
+
DotenvModule.populate(processEnv, parsed, options);
|
|
15446
|
+
|
|
15447
|
+
return { parsed }
|
|
15448
|
+
}
|
|
15449
|
+
|
|
15450
|
+
function configDotenv (options) {
|
|
15451
|
+
const dotenvPath = path.resolve(process.cwd(), '.env');
|
|
15452
|
+
let encoding = 'utf8';
|
|
15453
|
+
const debug = Boolean(options && options.debug);
|
|
15454
|
+
const quiet = Boolean(options && options.quiet);
|
|
15455
|
+
|
|
15456
|
+
if (options && options.encoding) {
|
|
15457
|
+
encoding = options.encoding;
|
|
15458
|
+
} else {
|
|
15459
|
+
if (debug) {
|
|
15460
|
+
_debug('No encoding is specified. UTF-8 is used by default');
|
|
15461
|
+
}
|
|
15462
|
+
}
|
|
15463
|
+
|
|
15464
|
+
let optionPaths = [dotenvPath]; // default, look for .env
|
|
15465
|
+
if (options && options.path) {
|
|
15466
|
+
if (!Array.isArray(options.path)) {
|
|
15467
|
+
optionPaths = [_resolveHome(options.path)];
|
|
15468
|
+
} else {
|
|
15469
|
+
optionPaths = []; // reset default
|
|
15470
|
+
for (const filepath of options.path) {
|
|
15471
|
+
optionPaths.push(_resolveHome(filepath));
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
}
|
|
15475
|
+
|
|
15476
|
+
// Build the parsed data in a temporary object (because we need to return it). Once we have the final
|
|
15477
|
+
// parsed data, we will combine it with process.env (or options.processEnv if provided).
|
|
15478
|
+
let lastError;
|
|
15479
|
+
const parsedAll = {};
|
|
15480
|
+
for (const path of optionPaths) {
|
|
15481
|
+
try {
|
|
15482
|
+
// Specifying an encoding returns a string instead of a buffer
|
|
15483
|
+
const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
|
|
15484
|
+
|
|
15485
|
+
DotenvModule.populate(parsedAll, parsed, options);
|
|
15486
|
+
} catch (e) {
|
|
15487
|
+
if (debug) {
|
|
15488
|
+
_debug(`Failed to load ${path} ${e.message}`);
|
|
15489
|
+
}
|
|
15490
|
+
lastError = e;
|
|
15491
|
+
}
|
|
15492
|
+
}
|
|
15493
|
+
|
|
15494
|
+
let processEnv = process.env;
|
|
15495
|
+
if (options && options.processEnv != null) {
|
|
15496
|
+
processEnv = options.processEnv;
|
|
15497
|
+
}
|
|
15498
|
+
|
|
15499
|
+
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
15500
|
+
|
|
15501
|
+
if (debug || !quiet) {
|
|
15502
|
+
const keysCount = Object.keys(populated).length;
|
|
15503
|
+
const shortPaths = [];
|
|
15504
|
+
for (const filePath of optionPaths) {
|
|
15505
|
+
try {
|
|
15506
|
+
const relative = path.relative(process.cwd(), filePath);
|
|
15507
|
+
shortPaths.push(relative);
|
|
15508
|
+
} catch (e) {
|
|
15509
|
+
if (debug) {
|
|
15510
|
+
_debug(`Failed to load ${filePath} ${e.message}`);
|
|
15511
|
+
}
|
|
15512
|
+
lastError = e;
|
|
15513
|
+
}
|
|
15514
|
+
}
|
|
15515
|
+
|
|
15516
|
+
_log(`injecting env (${keysCount}) from ${shortPaths.join(',')} ${dim(`(tip: ${_getRandomTip()})`)}`);
|
|
15517
|
+
}
|
|
15518
|
+
|
|
15519
|
+
if (lastError) {
|
|
15520
|
+
return { parsed: parsedAll, error: lastError }
|
|
15521
|
+
} else {
|
|
15522
|
+
return { parsed: parsedAll }
|
|
15523
|
+
}
|
|
15524
|
+
}
|
|
15525
|
+
|
|
15526
|
+
// Populates process.env from .env file
|
|
15527
|
+
function config (options) {
|
|
15528
|
+
// fallback to original dotenv if DOTENV_KEY is not set
|
|
15529
|
+
if (_dotenvKey(options).length === 0) {
|
|
15530
|
+
return DotenvModule.configDotenv(options)
|
|
15531
|
+
}
|
|
15532
|
+
|
|
15533
|
+
const vaultPath = _vaultPath(options);
|
|
15534
|
+
|
|
15535
|
+
// dotenvKey exists but .env.vault file does not exist
|
|
15536
|
+
if (!vaultPath) {
|
|
15537
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
15538
|
+
|
|
15539
|
+
return DotenvModule.configDotenv(options)
|
|
15540
|
+
}
|
|
15541
|
+
|
|
15542
|
+
return DotenvModule._configVault(options)
|
|
15543
|
+
}
|
|
15544
|
+
|
|
15545
|
+
function decrypt (encrypted, keyStr) {
|
|
15546
|
+
const key = Buffer.from(keyStr.slice(-64), 'hex');
|
|
15547
|
+
let ciphertext = Buffer.from(encrypted, 'base64');
|
|
15548
|
+
|
|
15549
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
15550
|
+
const authTag = ciphertext.subarray(-16);
|
|
15551
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
15552
|
+
|
|
15553
|
+
try {
|
|
15554
|
+
const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
|
15555
|
+
aesgcm.setAuthTag(authTag);
|
|
15556
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`
|
|
15557
|
+
} catch (error) {
|
|
15558
|
+
const isRange = error instanceof RangeError;
|
|
15559
|
+
const invalidKeyLength = error.message === 'Invalid key length';
|
|
15560
|
+
const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data';
|
|
15561
|
+
|
|
15562
|
+
if (isRange || invalidKeyLength) {
|
|
15563
|
+
const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)');
|
|
15564
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
15565
|
+
throw err
|
|
15566
|
+
} else if (decryptionFailed) {
|
|
15567
|
+
const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
|
|
15568
|
+
err.code = 'DECRYPTION_FAILED';
|
|
15569
|
+
throw err
|
|
15570
|
+
} else {
|
|
15571
|
+
throw error
|
|
15572
|
+
}
|
|
15573
|
+
}
|
|
15574
|
+
}
|
|
15575
|
+
|
|
15576
|
+
// Populate process.env with parsed values
|
|
15577
|
+
function populate (processEnv, parsed, options = {}) {
|
|
15578
|
+
const debug = Boolean(options && options.debug);
|
|
15579
|
+
const override = Boolean(options && options.override);
|
|
15580
|
+
const populated = {};
|
|
15581
|
+
|
|
15582
|
+
if (typeof parsed !== 'object') {
|
|
15583
|
+
const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate');
|
|
15584
|
+
err.code = 'OBJECT_REQUIRED';
|
|
15585
|
+
throw err
|
|
15586
|
+
}
|
|
15587
|
+
|
|
15588
|
+
// Set process.env
|
|
15589
|
+
for (const key of Object.keys(parsed)) {
|
|
15590
|
+
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
15591
|
+
if (override === true) {
|
|
15592
|
+
processEnv[key] = parsed[key];
|
|
15593
|
+
populated[key] = parsed[key];
|
|
15594
|
+
}
|
|
15595
|
+
|
|
15596
|
+
if (debug) {
|
|
15597
|
+
if (override === true) {
|
|
15598
|
+
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
15599
|
+
} else {
|
|
15600
|
+
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
15601
|
+
}
|
|
15602
|
+
}
|
|
15603
|
+
} else {
|
|
15604
|
+
processEnv[key] = parsed[key];
|
|
15605
|
+
populated[key] = parsed[key];
|
|
15606
|
+
}
|
|
15607
|
+
}
|
|
15608
|
+
|
|
15609
|
+
return populated
|
|
15610
|
+
}
|
|
15611
|
+
|
|
15612
|
+
const DotenvModule = {
|
|
15613
|
+
configDotenv,
|
|
15614
|
+
_configVault,
|
|
15615
|
+
_parseVault,
|
|
15616
|
+
config,
|
|
15617
|
+
decrypt,
|
|
15618
|
+
parse,
|
|
15619
|
+
populate
|
|
15620
|
+
};
|
|
15621
|
+
|
|
15622
|
+
main.exports.configDotenv = DotenvModule.configDotenv;
|
|
15623
|
+
main.exports._configVault = DotenvModule._configVault;
|
|
15624
|
+
main.exports._parseVault = DotenvModule._parseVault;
|
|
15625
|
+
main.exports.config = DotenvModule.config;
|
|
15626
|
+
main.exports.decrypt = DotenvModule.decrypt;
|
|
15627
|
+
main.exports.parse = DotenvModule.parse;
|
|
15628
|
+
main.exports.populate = DotenvModule.populate;
|
|
15629
|
+
|
|
15630
|
+
main.exports = DotenvModule;
|
|
15631
|
+
return main.exports;
|
|
15632
|
+
}
|
|
15633
|
+
|
|
15634
|
+
var envOptions;
|
|
15635
|
+
var hasRequiredEnvOptions;
|
|
15636
|
+
|
|
15637
|
+
function requireEnvOptions () {
|
|
15638
|
+
if (hasRequiredEnvOptions) return envOptions;
|
|
15639
|
+
hasRequiredEnvOptions = 1;
|
|
15640
|
+
// ../config.js accepts options via environment variables
|
|
15641
|
+
const options = {};
|
|
15642
|
+
|
|
15643
|
+
if (process.env.DOTENV_CONFIG_ENCODING != null) {
|
|
15644
|
+
options.encoding = process.env.DOTENV_CONFIG_ENCODING;
|
|
15645
|
+
}
|
|
15646
|
+
|
|
15647
|
+
if (process.env.DOTENV_CONFIG_PATH != null) {
|
|
15648
|
+
options.path = process.env.DOTENV_CONFIG_PATH;
|
|
15649
|
+
}
|
|
15650
|
+
|
|
15651
|
+
if (process.env.DOTENV_CONFIG_QUIET != null) {
|
|
15652
|
+
options.quiet = process.env.DOTENV_CONFIG_QUIET;
|
|
15653
|
+
}
|
|
15654
|
+
|
|
15655
|
+
if (process.env.DOTENV_CONFIG_DEBUG != null) {
|
|
15656
|
+
options.debug = process.env.DOTENV_CONFIG_DEBUG;
|
|
15657
|
+
}
|
|
15658
|
+
|
|
15659
|
+
if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
|
|
15660
|
+
options.override = process.env.DOTENV_CONFIG_OVERRIDE;
|
|
15661
|
+
}
|
|
15662
|
+
|
|
15663
|
+
if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {
|
|
15664
|
+
options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
|
|
15665
|
+
}
|
|
15666
|
+
|
|
15667
|
+
envOptions = options;
|
|
15668
|
+
return envOptions;
|
|
15669
|
+
}
|
|
15670
|
+
|
|
15671
|
+
var cliOptions;
|
|
15672
|
+
var hasRequiredCliOptions;
|
|
15673
|
+
|
|
15674
|
+
function requireCliOptions () {
|
|
15675
|
+
if (hasRequiredCliOptions) return cliOptions;
|
|
15676
|
+
hasRequiredCliOptions = 1;
|
|
15677
|
+
const re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/;
|
|
15678
|
+
|
|
15679
|
+
cliOptions = function optionMatcher (args) {
|
|
15680
|
+
const options = args.reduce(function (acc, cur) {
|
|
15681
|
+
const matches = cur.match(re);
|
|
15682
|
+
if (matches) {
|
|
15683
|
+
acc[matches[1]] = matches[2];
|
|
15684
|
+
}
|
|
15685
|
+
return acc
|
|
15686
|
+
}, {});
|
|
15687
|
+
|
|
15688
|
+
if (!('quiet' in options)) {
|
|
15689
|
+
options.quiet = 'true';
|
|
15690
|
+
}
|
|
15691
|
+
|
|
15692
|
+
return options
|
|
15693
|
+
};
|
|
15694
|
+
return cliOptions;
|
|
15695
|
+
}
|
|
15696
|
+
|
|
15697
|
+
var hasRequiredConfig;
|
|
15698
|
+
|
|
15699
|
+
function requireConfig () {
|
|
15700
|
+
if (hasRequiredConfig) return config;
|
|
15701
|
+
hasRequiredConfig = 1;
|
|
15702
|
+
(function () {
|
|
15703
|
+
requireMain().config(
|
|
15704
|
+
Object.assign(
|
|
15705
|
+
{},
|
|
15706
|
+
requireEnvOptions(),
|
|
15707
|
+
requireCliOptions()(process.argv)
|
|
15708
|
+
)
|
|
15709
|
+
);
|
|
15710
|
+
})();
|
|
15711
|
+
return config;
|
|
15399
15712
|
}
|
|
15400
15713
|
|
|
15714
|
+
requireConfig();
|
|
15715
|
+
|
|
15401
15716
|
const log = (message, options = { type: 'info' }) => {
|
|
15402
15717
|
log$1(message, { ...options, source: 'AlpacaMarketDataAPI' });
|
|
15403
15718
|
};
|
|
@@ -15421,22 +15736,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15421
15736
|
optionWs = null;
|
|
15422
15737
|
stockSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
15423
15738
|
optionSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
15424
|
-
apiKey;
|
|
15425
|
-
secretKey;
|
|
15426
|
-
accountType;
|
|
15427
15739
|
setMode(mode = 'production') {
|
|
15428
|
-
if (mode === 'sandbox') {
|
|
15429
|
-
// sandbox mode
|
|
15740
|
+
if (mode === 'sandbox') { // sandbox mode
|
|
15430
15741
|
this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
|
|
15431
15742
|
this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
|
|
15432
15743
|
}
|
|
15433
|
-
else if (mode === 'test') {
|
|
15434
|
-
// test mode, can only use ticker FAKEPACA
|
|
15744
|
+
else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
|
|
15435
15745
|
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
|
|
15436
15746
|
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // there's no test mode for options
|
|
15437
15747
|
}
|
|
15438
|
-
else {
|
|
15439
|
-
// production
|
|
15748
|
+
else { // production
|
|
15440
15749
|
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
|
|
15441
15750
|
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
|
|
15442
15751
|
}
|
|
@@ -15452,30 +15761,24 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15452
15761
|
return 'production';
|
|
15453
15762
|
}
|
|
15454
15763
|
}
|
|
15455
|
-
constructor(
|
|
15764
|
+
constructor() {
|
|
15456
15765
|
super();
|
|
15457
|
-
this.apiKey = config.apiKey;
|
|
15458
|
-
this.secretKey = config.secretKey;
|
|
15459
|
-
this.accountType = config.accountType || 'LIVE';
|
|
15460
15766
|
this.dataURL = 'https://data.alpaca.markets/v2';
|
|
15461
15767
|
this.apiURL =
|
|
15462
|
-
|
|
15768
|
+
process.env.ALPACA_ACCOUNT_TYPE === 'PAPER'
|
|
15463
15769
|
? 'https://paper-api.alpaca.markets/v2'
|
|
15464
15770
|
: 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
|
|
15465
15771
|
this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
|
|
15466
15772
|
this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
|
|
15467
15773
|
this.headers = {
|
|
15468
|
-
'APCA-API-KEY-ID':
|
|
15469
|
-
'APCA-API-SECRET-KEY':
|
|
15774
|
+
'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
|
|
15775
|
+
'APCA-API-SECRET-KEY': process.env.ALPACA_SECRET_KEY,
|
|
15470
15776
|
'Content-Type': 'application/json',
|
|
15471
15777
|
};
|
|
15472
15778
|
}
|
|
15473
|
-
static getInstance(
|
|
15779
|
+
static getInstance() {
|
|
15474
15780
|
if (!AlpacaMarketDataAPI.instance) {
|
|
15475
|
-
|
|
15476
|
-
throw new Error('AlpacaMarketDataAPI config is required for first initialization');
|
|
15477
|
-
}
|
|
15478
|
-
AlpacaMarketDataAPI.instance = new AlpacaMarketDataAPI(config);
|
|
15781
|
+
AlpacaMarketDataAPI.instance = new AlpacaMarketDataAPI();
|
|
15479
15782
|
}
|
|
15480
15783
|
return AlpacaMarketDataAPI.instance;
|
|
15481
15784
|
}
|
|
@@ -15498,8 +15801,8 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15498
15801
|
log(`${streamType} stream connected`, { type: 'info' });
|
|
15499
15802
|
const authMessage = {
|
|
15500
15803
|
action: 'auth',
|
|
15501
|
-
key:
|
|
15502
|
-
secret:
|
|
15804
|
+
key: process.env.ALPACA_API_KEY,
|
|
15805
|
+
secret: process.env.ALPACA_SECRET_KEY,
|
|
15503
15806
|
};
|
|
15504
15807
|
ws.send(JSON.stringify(authMessage));
|
|
15505
15808
|
});
|
|
@@ -15590,7 +15893,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15590
15893
|
const currentSubscriptions = streamType === 'stock' ? this.stockSubscriptions : this.optionSubscriptions;
|
|
15591
15894
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
15592
15895
|
if (value) {
|
|
15593
|
-
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(
|
|
15896
|
+
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
|
|
15594
15897
|
}
|
|
15595
15898
|
});
|
|
15596
15899
|
const unsubMessage = {
|
|
@@ -15653,11 +15956,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15653
15956
|
let pageCount = 0;
|
|
15654
15957
|
let currency = '';
|
|
15655
15958
|
// Initialize bar arrays for each symbol
|
|
15656
|
-
symbols.forEach(
|
|
15959
|
+
symbols.forEach(symbol => {
|
|
15657
15960
|
allBars[symbol] = [];
|
|
15658
15961
|
});
|
|
15659
15962
|
log(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
15660
|
-
type: 'info'
|
|
15963
|
+
type: 'info'
|
|
15661
15964
|
});
|
|
15662
15965
|
while (hasMorePages) {
|
|
15663
15966
|
pageCount++;
|
|
@@ -15685,7 +15988,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15685
15988
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
15686
15989
|
pageBarsCount += bars.length;
|
|
15687
15990
|
// Track date range for this page
|
|
15688
|
-
bars.forEach(
|
|
15991
|
+
bars.forEach(bar => {
|
|
15689
15992
|
const barDate = new Date(bar.t);
|
|
15690
15993
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
15691
15994
|
earliestTimestamp = barDate;
|
|
@@ -15704,7 +16007,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15704
16007
|
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
15705
16008
|
: 'unknown range';
|
|
15706
16009
|
log(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
15707
|
-
type: 'info'
|
|
16010
|
+
type: 'info'
|
|
15708
16011
|
});
|
|
15709
16012
|
// Prevent infinite loops
|
|
15710
16013
|
if (pageCount > 1000) {
|
|
@@ -15713,11 +16016,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15713
16016
|
}
|
|
15714
16017
|
}
|
|
15715
16018
|
// Final summary
|
|
15716
|
-
const symbolCounts = Object.entries(allBars)
|
|
15717
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
15718
|
-
.join(', ');
|
|
16019
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
15719
16020
|
log(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
15720
|
-
type: 'info'
|
|
16021
|
+
type: 'info'
|
|
15721
16022
|
});
|
|
15722
16023
|
return {
|
|
15723
16024
|
bars: allBars,
|
|
@@ -15985,11 +16286,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15985
16286
|
let totalBarsCount = 0;
|
|
15986
16287
|
let pageCount = 0;
|
|
15987
16288
|
// Initialize bar arrays for each symbol
|
|
15988
|
-
symbols.forEach(
|
|
16289
|
+
symbols.forEach(symbol => {
|
|
15989
16290
|
allBars[symbol] = [];
|
|
15990
16291
|
});
|
|
15991
16292
|
log(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
15992
|
-
type: 'info'
|
|
16293
|
+
type: 'info'
|
|
15993
16294
|
});
|
|
15994
16295
|
while (hasMorePages) {
|
|
15995
16296
|
pageCount++;
|
|
@@ -16011,7 +16312,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16011
16312
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
16012
16313
|
pageBarsCount += bars.length;
|
|
16013
16314
|
// Track date range for this page
|
|
16014
|
-
bars.forEach(
|
|
16315
|
+
bars.forEach(bar => {
|
|
16015
16316
|
const barDate = new Date(bar.t);
|
|
16016
16317
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
16017
16318
|
earliestTimestamp = barDate;
|
|
@@ -16030,7 +16331,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16030
16331
|
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
16031
16332
|
: 'unknown range';
|
|
16032
16333
|
log(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
16033
|
-
type: 'info'
|
|
16334
|
+
type: 'info'
|
|
16034
16335
|
});
|
|
16035
16336
|
// Prevent infinite loops
|
|
16036
16337
|
if (pageCount > 1000) {
|
|
@@ -16039,11 +16340,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16039
16340
|
}
|
|
16040
16341
|
}
|
|
16041
16342
|
// Final summary
|
|
16042
|
-
const symbolCounts = Object.entries(allBars)
|
|
16043
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
16044
|
-
.join(', ');
|
|
16343
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
16045
16344
|
log(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
16046
|
-
type: 'info'
|
|
16345
|
+
type: 'info'
|
|
16047
16346
|
});
|
|
16048
16347
|
return {
|
|
16049
16348
|
bars: allBars,
|
|
@@ -16067,11 +16366,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16067
16366
|
let totalTradesCount = 0;
|
|
16068
16367
|
let pageCount = 0;
|
|
16069
16368
|
// Initialize trades arrays for each symbol
|
|
16070
|
-
symbols.forEach(
|
|
16369
|
+
symbols.forEach(symbol => {
|
|
16071
16370
|
allTrades[symbol] = [];
|
|
16072
16371
|
});
|
|
16073
16372
|
log(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
16074
|
-
type: 'info'
|
|
16373
|
+
type: 'info'
|
|
16075
16374
|
});
|
|
16076
16375
|
while (hasMorePages) {
|
|
16077
16376
|
pageCount++;
|
|
@@ -16093,7 +16392,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16093
16392
|
allTrades[symbol] = [...allTrades[symbol], ...trades];
|
|
16094
16393
|
pageTradesCount += trades.length;
|
|
16095
16394
|
// Track date range for this page
|
|
16096
|
-
trades.forEach(
|
|
16395
|
+
trades.forEach(trade => {
|
|
16097
16396
|
const tradeDate = new Date(trade.t);
|
|
16098
16397
|
if (!earliestTimestamp || tradeDate < earliestTimestamp) {
|
|
16099
16398
|
earliestTimestamp = tradeDate;
|
|
@@ -16112,7 +16411,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16112
16411
|
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
16113
16412
|
: 'unknown range';
|
|
16114
16413
|
log(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
16115
|
-
type: 'info'
|
|
16414
|
+
type: 'info'
|
|
16116
16415
|
});
|
|
16117
16416
|
// Prevent infinite loops
|
|
16118
16417
|
if (pageCount > 1000) {
|
|
@@ -16121,11 +16420,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16121
16420
|
}
|
|
16122
16421
|
}
|
|
16123
16422
|
// Final summary
|
|
16124
|
-
const symbolCounts = Object.entries(allTrades)
|
|
16125
|
-
.map(([symbol, trades]) => `${symbol}: ${trades.length}`)
|
|
16126
|
-
.join(', ');
|
|
16423
|
+
const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
|
|
16127
16424
|
log(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
|
|
16128
|
-
type: 'info'
|
|
16425
|
+
type: 'info'
|
|
16129
16426
|
});
|
|
16130
16427
|
return {
|
|
16131
16428
|
trades: allTrades,
|
|
@@ -16270,9 +16567,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16270
16567
|
...(symbol && { symbols: symbol }),
|
|
16271
16568
|
...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
|
|
16272
16569
|
...(mergedParams.sort && { sort: mergedParams.sort }),
|
|
16273
|
-
...(mergedParams.include_content !== undefined
|
|
16274
|
-
? { include_content: mergedParams.include_content.toString() }
|
|
16275
|
-
: {}),
|
|
16570
|
+
...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
|
|
16276
16571
|
...(pageToken && { page_token: pageToken }),
|
|
16277
16572
|
});
|
|
16278
16573
|
const url = `${this.v1beta1url}/news?${queryParams}`;
|
|
@@ -16316,6 +16611,8 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16316
16611
|
return newsArticles;
|
|
16317
16612
|
}
|
|
16318
16613
|
}
|
|
16614
|
+
// Export the singleton instance
|
|
16615
|
+
const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
16319
16616
|
|
|
16320
16617
|
const limitPriceSlippagePercent100 = 0.1; // 0.1%
|
|
16321
16618
|
/**
|
|
@@ -16327,11 +16624,11 @@ Websocket example
|
|
|
16327
16624
|
alpacaAPI.connectWebsocket(); // necessary to connect to the WebSocket
|
|
16328
16625
|
*/
|
|
16329
16626
|
class AlpacaTradingAPI {
|
|
16330
|
-
static new(credentials
|
|
16331
|
-
return new AlpacaTradingAPI(credentials
|
|
16627
|
+
static new(credentials) {
|
|
16628
|
+
return new AlpacaTradingAPI(credentials);
|
|
16332
16629
|
}
|
|
16333
|
-
static getInstance(credentials
|
|
16334
|
-
return new AlpacaTradingAPI(credentials
|
|
16630
|
+
static getInstance(credentials) {
|
|
16631
|
+
return new AlpacaTradingAPI(credentials);
|
|
16335
16632
|
}
|
|
16336
16633
|
ws = null;
|
|
16337
16634
|
headers;
|
|
@@ -16345,7 +16642,6 @@ class AlpacaTradingAPI {
|
|
|
16345
16642
|
reconnectTimeout = null;
|
|
16346
16643
|
messageHandlers = new Map();
|
|
16347
16644
|
debugLogging = false;
|
|
16348
|
-
marketDataAPI;
|
|
16349
16645
|
/**
|
|
16350
16646
|
* Constructor for AlpacaTradingAPI
|
|
16351
16647
|
* @param credentials - Alpaca credentials,
|
|
@@ -16354,14 +16650,11 @@ class AlpacaTradingAPI {
|
|
|
16354
16650
|
* apiSecret: string; // Alpaca API secret
|
|
16355
16651
|
* type: AlpacaAccountType;
|
|
16356
16652
|
* orderType: AlpacaOrderType;
|
|
16357
|
-
* @param marketDataConfig - Market data API configuration
|
|
16358
16653
|
* @param options - Optional options
|
|
16359
16654
|
* debugLogging: boolean; // Whether to log messages of type 'debug'
|
|
16360
16655
|
*/
|
|
16361
|
-
constructor(credentials,
|
|
16656
|
+
constructor(credentials, options) {
|
|
16362
16657
|
this.credentials = credentials;
|
|
16363
|
-
// Initialize market data API instance
|
|
16364
|
-
this.marketDataAPI = AlpacaMarketDataAPI.getInstance(marketDataConfig);
|
|
16365
16658
|
// Set URLs based on account type
|
|
16366
16659
|
this.apiBaseUrl =
|
|
16367
16660
|
credentials.type === 'PAPER' ? 'https://paper-api.alpaca.markets/v2' : 'https://api.alpaca.markets/v2';
|
|
@@ -16900,7 +17193,7 @@ class AlpacaTradingAPI {
|
|
|
16900
17193
|
this.log(`Found ${positions.length} positions to close`);
|
|
16901
17194
|
// Get latest quotes for all positions
|
|
16902
17195
|
const symbols = positions.map((position) => position.symbol);
|
|
16903
|
-
const quotesResponse = await
|
|
17196
|
+
const quotesResponse = await marketDataAPI.getLatestQuotes(symbols);
|
|
16904
17197
|
const lengthOfQuotes = Object.keys(quotesResponse.quotes).length;
|
|
16905
17198
|
if (lengthOfQuotes === 0) {
|
|
16906
17199
|
this.log('No quotes available for positions, received 0 quotes', {
|
|
@@ -16967,7 +17260,7 @@ class AlpacaTradingAPI {
|
|
|
16967
17260
|
this.log(`Cancelled all open orders`);
|
|
16968
17261
|
// Get latest quotes for all positions
|
|
16969
17262
|
const symbols = positions.map((position) => position.symbol);
|
|
16970
|
-
const quotesResponse = await
|
|
17263
|
+
const quotesResponse = await marketDataAPI.getLatestQuotes(symbols);
|
|
16971
17264
|
// Create limit orders to close each position
|
|
16972
17265
|
for (const position of positions) {
|
|
16973
17266
|
const quote = quotesResponse.quotes[position.symbol];
|
|
@@ -17650,13 +17943,6 @@ class AlpacaTradingAPI {
|
|
|
17650
17943
|
}
|
|
17651
17944
|
}
|
|
17652
17945
|
|
|
17653
|
-
// Export factory functions for easier instantiation
|
|
17654
|
-
const createAlpacaTradingAPI = (credentials, marketDataConfig) => {
|
|
17655
|
-
return new AlpacaTradingAPI(credentials, marketDataConfig);
|
|
17656
|
-
};
|
|
17657
|
-
const createAlpacaMarketDataAPI = (config) => {
|
|
17658
|
-
return AlpacaMarketDataAPI.getInstance(config);
|
|
17659
|
-
};
|
|
17660
17946
|
const disco = {
|
|
17661
17947
|
types: Types,
|
|
17662
17948
|
alpaca: {
|
|
@@ -17704,31 +17990,14 @@ const disco = {
|
|
|
17704
17990
|
calculateFibonacciLevels: calculateFibonacciLevels,
|
|
17705
17991
|
},
|
|
17706
17992
|
time: {
|
|
17707
|
-
toUnixTimestamp: toUnixTimestamp,
|
|
17708
|
-
getTimeAgo: getTimeAgo,
|
|
17709
|
-
timeAgo: timeAgo,
|
|
17710
|
-
normalizeDate: normalizeDate,
|
|
17711
|
-
getDateInNY: getDateInNY,
|
|
17712
|
-
createMarketTimeUtil: createMarketTimeUtil,
|
|
17713
|
-
getStartAndEndTimestamps: getStartAndEndTimestamps,
|
|
17714
17993
|
getStartAndEndDates: getStartAndEndDates,
|
|
17715
17994
|
getMarketOpenClose: getMarketOpenClose,
|
|
17716
|
-
calculateTimeRange: calculateTimeRange,
|
|
17717
|
-
calculateDaysLeft: calculateDaysLeft,
|
|
17718
|
-
formatDate: formatDate /* move to format, keeping here for compatibility */,
|
|
17719
|
-
currentTimeET: currentTimeET,
|
|
17720
|
-
MarketTimeUtil: MarketTimeUtil,
|
|
17721
|
-
MARKET_TIMES: MARKET_TIMES,
|
|
17722
|
-
getLastTradingDateYYYYMMDD: getLastTradingDateYYYYMMDD,
|
|
17723
17995
|
getLastFullTradingDate: getLastFullTradingDate,
|
|
17724
17996
|
getNextMarketDay: getNextMarketDay,
|
|
17725
|
-
parseETDateFromAV: parseETDateFromAV,
|
|
17726
|
-
formatToUSEastern: formatToUSEastern,
|
|
17727
|
-
unixTimetoUSEastern: unixTimetoUSEastern,
|
|
17728
17997
|
getMarketStatus: getMarketStatus,
|
|
17729
|
-
timeDiffString: timeDiffString,
|
|
17730
17998
|
getNYTimeZone: getNYTimeZone,
|
|
17731
17999
|
getTradingDate: getTradingDate,
|
|
18000
|
+
getTradingStartAndEndDates: getTradingStartAndEndDates,
|
|
17732
18001
|
},
|
|
17733
18002
|
utils: {
|
|
17734
18003
|
logIfDebug: logIfDebug,
|
|
@@ -17737,5 +18006,5 @@ const disco = {
|
|
|
17737
18006
|
},
|
|
17738
18007
|
};
|
|
17739
18008
|
|
|
17740
|
-
export { AlpacaMarketDataAPI, AlpacaTradingAPI,
|
|
18009
|
+
export { AlpacaMarketDataAPI, AlpacaTradingAPI, disco };
|
|
17741
18010
|
//# sourceMappingURL=index.mjs.map
|