@discomedia/utils 1.0.3 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +651 -903
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +653 -903
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +50 -0
- package/dist/test.js +755 -6495
- package/dist/test.js.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +2 -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.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/testing/market-time-refactor-test.d.ts +1 -0
- package/dist/types/testing/market-time-refactor-test.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/index-frontend.cjs +0 -7798
- package/dist/index-frontend.cjs.map +0 -1
- package/dist/index-frontend.mjs +0 -7792
- package/dist/index-frontend.mjs.map +0 -1
- package/dist/types/index-frontend.d.ts +0 -18
- package/dist/types/index-frontend.d.ts.map +0 -1
- package/dist/types/testing/frontend-test.d.ts +0 -2
- package/dist/types/testing/frontend-test.d.ts.map +0 -1
- 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,6 +1,5 @@
|
|
|
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
4
|
import require$$1$1 from 'https';
|
|
6
5
|
import require$$2 from 'http';
|
|
@@ -12,170 +11,34 @@ import require$$7 from 'url';
|
|
|
12
11
|
import require$$0 from 'zlib';
|
|
13
12
|
import require$$0$1 from 'buffer';
|
|
14
13
|
|
|
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}`);
|
|
14
|
+
/**
|
|
15
|
+
* Logs a message to the console.
|
|
16
|
+
* @param message The message to log.
|
|
17
|
+
* @param options Optional options.
|
|
18
|
+
* @param options.source The source of the message.
|
|
19
|
+
* @param options.type The type of message to log.
|
|
20
|
+
* @param options.symbol The trading symbol associated with this log.
|
|
21
|
+
* @param options.account The account associated with this log.
|
|
22
|
+
*/
|
|
23
|
+
function log$1(message, options = { source: 'App', type: 'info' }) {
|
|
24
|
+
// Format the timestamp
|
|
25
|
+
const date = new Date();
|
|
26
|
+
const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
27
|
+
const account = options?.account;
|
|
28
|
+
const symbol = options?.symbol;
|
|
29
|
+
// Build the log message
|
|
30
|
+
const logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
|
|
31
|
+
// Use appropriate console method based on type
|
|
32
|
+
if (options?.type === 'error') {
|
|
33
|
+
console.error(logMessage);
|
|
82
34
|
}
|
|
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');
|
|
35
|
+
else if (options?.type === 'warn') {
|
|
36
|
+
console.warn(logMessage);
|
|
99
37
|
}
|
|
100
38
|
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;
|
|
39
|
+
console.log(logMessage);
|
|
154
40
|
}
|
|
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
|
-
};
|
|
41
|
+
}
|
|
179
42
|
|
|
180
43
|
// market-hours.ts
|
|
181
44
|
const marketHolidays = {
|
|
@@ -280,99 +143,66 @@ const marketEarlyCloses = {
|
|
|
280
143
|
},
|
|
281
144
|
};
|
|
282
145
|
|
|
283
|
-
// market-time.ts
|
|
146
|
+
// market-time.ts - Refactored for better organization and usability
|
|
147
|
+
// ===== CONFIGURATION =====
|
|
284
148
|
/**
|
|
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
|
|
149
|
+
* Market configuration constants
|
|
291
150
|
*/
|
|
292
|
-
const
|
|
151
|
+
const MARKET_CONFIG = {
|
|
293
152
|
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 } },
|
|
153
|
+
TIMES: {
|
|
154
|
+
EXTENDED_START: { hour: 4, minute: 0 },
|
|
155
|
+
MARKET_OPEN: { hour: 9, minute: 30 },
|
|
156
|
+
EARLY_MARKET_END: { hour: 10, minute: 0 },
|
|
157
|
+
MARKET_CLOSE: { hour: 16, minute: 0 },
|
|
158
|
+
EARLY_CLOSE: { hour: 13, minute: 0 },
|
|
159
|
+
EXTENDED_END: { hour: 20, minute: 0 },
|
|
160
|
+
EARLY_EXTENDED_END: { hour: 17, minute: 0 },
|
|
161
|
+
},
|
|
306
162
|
};
|
|
163
|
+
// ===== MARKET CALENDAR SERVICE =====
|
|
307
164
|
/**
|
|
308
|
-
*
|
|
165
|
+
* Service for handling market calendar operations (holidays, early closes, market days)
|
|
309
166
|
*/
|
|
310
|
-
class
|
|
167
|
+
class MarketCalendar {
|
|
311
168
|
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);
|
|
169
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
320
170
|
this.timezone = timezone;
|
|
321
|
-
this.intradayReporting = intradayReporting;
|
|
322
171
|
}
|
|
323
172
|
/**
|
|
324
|
-
*
|
|
325
|
-
* @private
|
|
326
|
-
* @param {string} timezone - The timezone to validate
|
|
327
|
-
* @throws {Error} If the timezone is invalid
|
|
173
|
+
* Check if a date is a weekend
|
|
328
174
|
*/
|
|
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
175
|
isWeekend(date) {
|
|
350
176
|
const day = date.getDay();
|
|
351
|
-
return day === 0 || day === 6;
|
|
177
|
+
return day === 0 || day === 6; // Sunday or Saturday
|
|
352
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if a date is a market holiday
|
|
181
|
+
*/
|
|
353
182
|
isHoliday(date) {
|
|
354
|
-
const formattedDate =
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
return false;
|
|
183
|
+
const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
184
|
+
const year = toZonedTime(date, this.timezone).getFullYear();
|
|
185
|
+
const yearHolidays = marketHolidays[year];
|
|
186
|
+
if (!yearHolidays)
|
|
187
|
+
return false;
|
|
188
|
+
return Object.values(yearHolidays).some(holiday => holiday.date === formattedDate);
|
|
362
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Check if a date is an early close day
|
|
192
|
+
*/
|
|
363
193
|
isEarlyCloseDay(date) {
|
|
364
|
-
const formattedDate =
|
|
365
|
-
const
|
|
194
|
+
const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
195
|
+
const year = toZonedTime(date, this.timezone).getFullYear();
|
|
196
|
+
const yearEarlyCloses = marketEarlyCloses[year];
|
|
366
197
|
return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
|
|
367
198
|
}
|
|
368
199
|
/**
|
|
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
|
|
200
|
+
* Get the early close time for a date (in minutes from midnight)
|
|
372
201
|
*/
|
|
373
202
|
getEarlyCloseTime(date) {
|
|
374
|
-
const formattedDate =
|
|
375
|
-
const
|
|
203
|
+
const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
204
|
+
const year = toZonedTime(date, this.timezone).getFullYear();
|
|
205
|
+
const yearEarlyCloses = marketEarlyCloses[year];
|
|
376
206
|
if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
|
|
377
207
|
const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(':').map(Number);
|
|
378
208
|
return hours * 60 + minutes;
|
|
@@ -380,194 +210,283 @@ class MarketTimeUtil {
|
|
|
380
210
|
return null;
|
|
381
211
|
}
|
|
382
212
|
/**
|
|
383
|
-
* Check if a
|
|
384
|
-
* @param date - The date to check
|
|
385
|
-
* @returns true if the date is a market day, false otherwise
|
|
213
|
+
* Check if a date is a market day (not weekend or holiday)
|
|
386
214
|
*/
|
|
387
215
|
isMarketDay(date) {
|
|
388
|
-
|
|
389
|
-
const isHolidayDay = this.isHoliday(date);
|
|
390
|
-
const returner = !isWeekendDay && !isHolidayDay;
|
|
391
|
-
return returner;
|
|
216
|
+
return !this.isWeekend(date) && !this.isHoliday(date);
|
|
392
217
|
}
|
|
393
218
|
/**
|
|
394
|
-
*
|
|
395
|
-
* @param date - The date to check
|
|
396
|
-
* @returns true if the date is within market hours, false otherwise
|
|
219
|
+
* Get the next market day from a given date
|
|
397
220
|
*/
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
221
|
+
getNextMarketDay(date) {
|
|
222
|
+
let nextDay = add(date, { days: 1 });
|
|
223
|
+
while (!this.isMarketDay(nextDay)) {
|
|
224
|
+
nextDay = add(nextDay, { days: 1 });
|
|
402
225
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
226
|
+
return nextDay;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the previous market day from a given date
|
|
230
|
+
*/
|
|
231
|
+
getPreviousMarketDay(date) {
|
|
232
|
+
let prevDay = sub(date, { days: 1 });
|
|
233
|
+
while (!this.isMarketDay(prevDay)) {
|
|
234
|
+
prevDay = sub(prevDay, { days: 1 });
|
|
410
235
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
236
|
+
return prevDay;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// ===== TIME FORMATTER SERVICE =====
|
|
240
|
+
/**
|
|
241
|
+
* Service for formatting time outputs
|
|
242
|
+
*/
|
|
243
|
+
class TimeFormatter {
|
|
244
|
+
timezone;
|
|
245
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
246
|
+
this.timezone = timezone;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Format a date based on the output format
|
|
250
|
+
*/
|
|
251
|
+
formatDate(date, outputFormat = 'iso') {
|
|
252
|
+
switch (outputFormat) {
|
|
253
|
+
case 'unix-seconds':
|
|
254
|
+
return Math.floor(date.getTime() / 1000);
|
|
255
|
+
case 'unix-ms':
|
|
256
|
+
return date.getTime();
|
|
257
|
+
case 'iso':
|
|
258
|
+
default:
|
|
259
|
+
return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
|
433
260
|
}
|
|
434
|
-
return returner;
|
|
435
261
|
}
|
|
436
262
|
/**
|
|
437
|
-
*
|
|
438
|
-
* @param date - The date to check
|
|
439
|
-
* @returns true if the date is before market hours, false otherwise
|
|
263
|
+
* Get New York timezone offset
|
|
440
264
|
*/
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
265
|
+
getNYTimeZone(date = new Date()) {
|
|
266
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
267
|
+
timeZone: this.timezone,
|
|
268
|
+
timeZoneName: 'shortOffset',
|
|
269
|
+
});
|
|
270
|
+
const parts = dtf.formatToParts(date);
|
|
271
|
+
const tz = parts.find(p => p.type === 'timeZoneName')?.value;
|
|
272
|
+
if (!tz) {
|
|
273
|
+
throw new Error('Could not determine New York offset');
|
|
274
|
+
}
|
|
275
|
+
const shortOffset = tz.replace('GMT', '');
|
|
276
|
+
if (shortOffset === '-4') {
|
|
277
|
+
return '-04:00';
|
|
278
|
+
}
|
|
279
|
+
else if (shortOffset === '-5') {
|
|
280
|
+
return '-05:00';
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
throw new Error(`Unexpected timezone offset: ${shortOffset}`);
|
|
284
|
+
}
|
|
447
285
|
}
|
|
448
286
|
/**
|
|
449
|
-
* Get
|
|
450
|
-
* @param currentDate - The current date
|
|
451
|
-
* @returns The last trading date
|
|
287
|
+
* Get trading date in YYYY-MM-DD format
|
|
452
288
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
return nowET;
|
|
289
|
+
getTradingDate(time) {
|
|
290
|
+
let date;
|
|
291
|
+
if (typeof time === 'number') {
|
|
292
|
+
date = new Date(time);
|
|
293
|
+
}
|
|
294
|
+
else if (typeof time === 'string') {
|
|
295
|
+
date = new Date(time);
|
|
461
296
|
}
|
|
462
297
|
else {
|
|
463
|
-
|
|
464
|
-
let lastTradingDate = sub(nowET, { days: 1 });
|
|
465
|
-
while (!this.isMarketDay(lastTradingDate)) {
|
|
466
|
-
lastTradingDate = sub(lastTradingDate, { days: 1 });
|
|
467
|
-
}
|
|
468
|
-
return lastTradingDate;
|
|
298
|
+
date = time;
|
|
469
299
|
}
|
|
300
|
+
return formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
|
|
470
301
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
302
|
+
}
|
|
303
|
+
// ===== MARKET TIME CALCULATOR =====
|
|
304
|
+
/**
|
|
305
|
+
* Service for core market time calculations
|
|
306
|
+
*/
|
|
307
|
+
class MarketTimeCalculator {
|
|
308
|
+
calendar;
|
|
309
|
+
formatter;
|
|
310
|
+
timezone;
|
|
311
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
312
|
+
this.timezone = timezone;
|
|
313
|
+
this.calendar = new MarketCalendar(timezone);
|
|
314
|
+
this.formatter = new TimeFormatter(timezone);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get market open/close times for a date
|
|
318
|
+
*/
|
|
319
|
+
getMarketTimes(date) {
|
|
320
|
+
const zonedDate = toZonedTime(date, this.timezone);
|
|
321
|
+
// Market closed on weekends and holidays
|
|
322
|
+
if (!this.calendar.isMarketDay(zonedDate)) {
|
|
323
|
+
return {
|
|
324
|
+
marketOpen: false,
|
|
325
|
+
open: null,
|
|
326
|
+
close: null,
|
|
327
|
+
openExt: null,
|
|
328
|
+
closeExt: null,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const dayStart = startOfDay(zonedDate);
|
|
332
|
+
// Regular market times
|
|
333
|
+
const open = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour, minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute }), this.timezone);
|
|
334
|
+
let close = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, minutes: MARKET_CONFIG.TIMES.MARKET_CLOSE.minute }), this.timezone);
|
|
335
|
+
// Extended hours
|
|
336
|
+
const openExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour, minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute }), this.timezone);
|
|
337
|
+
let closeExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EXTENDED_END.hour, minutes: MARKET_CONFIG.TIMES.EXTENDED_END.minute }), this.timezone);
|
|
338
|
+
// Handle early close days
|
|
339
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
340
|
+
close = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, minutes: MARKET_CONFIG.TIMES.EARLY_CLOSE.minute }), this.timezone);
|
|
341
|
+
closeExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, minutes: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute }), this.timezone);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
marketOpen: true,
|
|
345
|
+
open,
|
|
346
|
+
close,
|
|
347
|
+
openExt,
|
|
348
|
+
closeExt,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Check if a time is within market hours based on intraday reporting mode
|
|
353
|
+
*/
|
|
354
|
+
isWithinMarketHours(date, intradayReporting = 'market_hours') {
|
|
355
|
+
const zonedDate = toZonedTime(date, this.timezone);
|
|
356
|
+
// Not a market day
|
|
357
|
+
if (!this.calendar.isMarketDay(zonedDate)) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
const timeInMinutes = zonedDate.getHours() * 60 + zonedDate.getMinutes();
|
|
361
|
+
switch (intradayReporting) {
|
|
362
|
+
case 'extended_hours': {
|
|
363
|
+
const startMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
364
|
+
let endMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
365
|
+
// Handle early close
|
|
366
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
367
|
+
endMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
368
|
+
}
|
|
369
|
+
return timeInMinutes >= startMinutes && timeInMinutes <= endMinutes;
|
|
370
|
+
}
|
|
371
|
+
case 'continuous':
|
|
372
|
+
return true;
|
|
373
|
+
default: {
|
|
374
|
+
// 'market_hours'
|
|
375
|
+
const startMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
376
|
+
let endMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
377
|
+
// Handle early close
|
|
378
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
379
|
+
endMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
380
|
+
}
|
|
381
|
+
return timeInMinutes >= startMinutes && timeInMinutes <= endMinutes;
|
|
382
|
+
}
|
|
475
383
|
}
|
|
476
|
-
return currentDate;
|
|
477
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Get the last full trading date
|
|
387
|
+
*/
|
|
478
388
|
getLastFullTradingDate(currentDate = new Date()) {
|
|
479
389
|
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)) {
|
|
390
|
+
// If today is a market day and we're after extended hours close, return today
|
|
391
|
+
if (this.calendar.isMarketDay(nowET)) {
|
|
483
392
|
const timeInMinutes = nowET.getHours() * 60 + nowET.getMinutes();
|
|
484
|
-
|
|
485
|
-
|
|
393
|
+
let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
394
|
+
if (this.calendar.isEarlyCloseDay(nowET)) {
|
|
395
|
+
extendedEndMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
396
|
+
}
|
|
486
397
|
if (timeInMinutes >= extendedEndMinutes) {
|
|
487
|
-
// Set to midnight ET while preserving the date
|
|
488
398
|
return fromZonedTime(set(nowET, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
|
|
489
399
|
}
|
|
490
400
|
}
|
|
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);
|
|
401
|
+
// Return the last completed trading day
|
|
402
|
+
const lastMarketDay = this.calendar.getPreviousMarketDay(nowET);
|
|
403
|
+
return fromZonedTime(set(lastMarketDay, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
|
|
496
404
|
}
|
|
497
405
|
/**
|
|
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
|
|
406
|
+
* Get day boundaries based on intraday reporting mode
|
|
505
407
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
while (!this.isMarketDay(currentDate)) {
|
|
509
|
-
currentDate = add(currentDate, { days: 1 });
|
|
510
|
-
}
|
|
511
|
-
return currentDate;
|
|
512
|
-
}
|
|
513
|
-
getDayBoundaries(date) {
|
|
408
|
+
getDayBoundaries(date, intradayReporting = 'market_hours') {
|
|
409
|
+
const zonedDate = toZonedTime(date, this.timezone);
|
|
514
410
|
let start;
|
|
515
411
|
let end;
|
|
516
|
-
switch (
|
|
412
|
+
switch (intradayReporting) {
|
|
517
413
|
case 'extended_hours': {
|
|
518
|
-
start = set(
|
|
519
|
-
hours:
|
|
520
|
-
minutes:
|
|
414
|
+
start = set(zonedDate, {
|
|
415
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
416
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
521
417
|
seconds: 0,
|
|
522
418
|
milliseconds: 0,
|
|
523
419
|
});
|
|
524
|
-
end = set(
|
|
525
|
-
hours:
|
|
526
|
-
minutes:
|
|
420
|
+
end = set(zonedDate, {
|
|
421
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_END.hour,
|
|
422
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_END.minute,
|
|
527
423
|
seconds: 59,
|
|
528
424
|
milliseconds: 999,
|
|
529
425
|
});
|
|
426
|
+
// Handle early close
|
|
427
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
428
|
+
end = set(zonedDate, {
|
|
429
|
+
hours: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour,
|
|
430
|
+
minutes: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute,
|
|
431
|
+
seconds: 59,
|
|
432
|
+
milliseconds: 999,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
530
435
|
break;
|
|
531
436
|
}
|
|
532
437
|
case 'continuous': {
|
|
533
|
-
start = startOfDay(
|
|
534
|
-
end = endOfDay(
|
|
438
|
+
start = startOfDay(zonedDate);
|
|
439
|
+
end = endOfDay(zonedDate);
|
|
535
440
|
break;
|
|
536
441
|
}
|
|
537
442
|
default: {
|
|
538
|
-
// market_hours
|
|
539
|
-
start = set(
|
|
540
|
-
hours:
|
|
541
|
-
minutes:
|
|
443
|
+
// 'market_hours'
|
|
444
|
+
start = set(zonedDate, {
|
|
445
|
+
hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour,
|
|
446
|
+
minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute,
|
|
542
447
|
seconds: 0,
|
|
543
448
|
milliseconds: 0,
|
|
544
449
|
});
|
|
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,
|
|
450
|
+
end = set(zonedDate, {
|
|
451
|
+
hours: MARKET_CONFIG.TIMES.MARKET_CLOSE.hour,
|
|
452
|
+
minutes: MARKET_CONFIG.TIMES.MARKET_CLOSE.minute,
|
|
563
453
|
seconds: 59,
|
|
564
454
|
milliseconds: 999,
|
|
565
455
|
});
|
|
456
|
+
// Handle early close
|
|
457
|
+
if (this.calendar.isEarlyCloseDay(zonedDate)) {
|
|
458
|
+
end = set(zonedDate, {
|
|
459
|
+
hours: MARKET_CONFIG.TIMES.EARLY_CLOSE.hour,
|
|
460
|
+
minutes: MARKET_CONFIG.TIMES.EARLY_CLOSE.minute,
|
|
461
|
+
seconds: 59,
|
|
462
|
+
milliseconds: 999,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
566
465
|
break;
|
|
567
466
|
}
|
|
568
467
|
}
|
|
569
|
-
return {
|
|
468
|
+
return {
|
|
469
|
+
start: fromZonedTime(start, this.timezone),
|
|
470
|
+
end: fromZonedTime(end, this.timezone),
|
|
471
|
+
};
|
|
570
472
|
}
|
|
473
|
+
}
|
|
474
|
+
// ===== PERIOD CALCULATOR =====
|
|
475
|
+
/**
|
|
476
|
+
* Service for calculating time periods
|
|
477
|
+
*/
|
|
478
|
+
class PeriodCalculator {
|
|
479
|
+
calendar;
|
|
480
|
+
timeCalculator;
|
|
481
|
+
formatter;
|
|
482
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
483
|
+
this.calendar = new MarketCalendar(timezone);
|
|
484
|
+
this.timeCalculator = new MarketTimeCalculator(timezone);
|
|
485
|
+
this.formatter = new TimeFormatter(timezone);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Calculate the start date for a given period
|
|
489
|
+
*/
|
|
571
490
|
calculatePeriodStartDate(endDate, period) {
|
|
572
491
|
let startDate;
|
|
573
492
|
switch (period) {
|
|
@@ -575,7 +494,7 @@ class MarketTimeUtil {
|
|
|
575
494
|
startDate = set(endDate, { month: 0, date: 1 });
|
|
576
495
|
break;
|
|
577
496
|
case '1D':
|
|
578
|
-
startDate = this.
|
|
497
|
+
startDate = this.calendar.getPreviousMarketDay(endDate);
|
|
579
498
|
break;
|
|
580
499
|
case '3D':
|
|
581
500
|
startDate = sub(endDate, { days: 3 });
|
|
@@ -601,405 +520,286 @@ class MarketTimeUtil {
|
|
|
601
520
|
default:
|
|
602
521
|
throw new Error(`Invalid period: ${period}`);
|
|
603
522
|
}
|
|
604
|
-
|
|
605
|
-
|
|
523
|
+
// Ensure start date is a market day
|
|
524
|
+
while (!this.calendar.isMarketDay(startDate)) {
|
|
525
|
+
startDate = this.calendar.getNextMarketDay(startDate);
|
|
606
526
|
}
|
|
607
527
|
return startDate;
|
|
608
528
|
}
|
|
609
|
-
|
|
529
|
+
/**
|
|
530
|
+
* Get period dates for market time calculations
|
|
531
|
+
*/
|
|
532
|
+
getMarketTimePeriod(params) {
|
|
533
|
+
const { period, end = new Date(), intraday_reporting = 'market_hours', outputFormat = 'iso', } = params;
|
|
610
534
|
if (!period) {
|
|
611
535
|
throw new Error('Period is required');
|
|
612
536
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
// Convert end date to specified timezone
|
|
617
|
-
const zonedEndDate = toZonedTime(end, this.timezone);
|
|
618
|
-
let startDate;
|
|
537
|
+
const zonedEndDate = toZonedTime(end, MARKET_CONFIG.TIMEZONE);
|
|
538
|
+
// Determine effective end date based on current market conditions
|
|
619
539
|
let endDate;
|
|
620
|
-
const isCurrentMarketDay = this.isMarketDay(zonedEndDate);
|
|
621
|
-
const isWithinHours = this.isWithinMarketHours(
|
|
622
|
-
const
|
|
623
|
-
|
|
540
|
+
const isCurrentMarketDay = this.calendar.isMarketDay(zonedEndDate);
|
|
541
|
+
const isWithinHours = this.timeCalculator.isWithinMarketHours(end, intraday_reporting);
|
|
542
|
+
const timeInMinutes = zonedEndDate.getHours() * 60 + zonedEndDate.getMinutes();
|
|
543
|
+
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
624
544
|
if (isCurrentMarketDay) {
|
|
625
|
-
if (
|
|
626
|
-
//
|
|
627
|
-
const lastMarketDay = this.
|
|
628
|
-
const { end: dayEnd } = this.getDayBoundaries(lastMarketDay);
|
|
545
|
+
if (timeInMinutes < marketStartMinutes) {
|
|
546
|
+
// Before market open - use previous day's close
|
|
547
|
+
const lastMarketDay = this.calendar.getPreviousMarketDay(zonedEndDate);
|
|
548
|
+
const { end: dayEnd } = this.timeCalculator.getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
629
549
|
endDate = dayEnd;
|
|
630
550
|
}
|
|
631
551
|
else if (isWithinHours) {
|
|
632
|
-
//
|
|
633
|
-
endDate =
|
|
552
|
+
// During market hours - use current time
|
|
553
|
+
endDate = end;
|
|
634
554
|
}
|
|
635
555
|
else {
|
|
636
|
-
//
|
|
637
|
-
const { end: dayEnd } = this.getDayBoundaries(zonedEndDate);
|
|
556
|
+
// After market close - use today's close
|
|
557
|
+
const { end: dayEnd } = this.timeCalculator.getDayBoundaries(zonedEndDate, intraday_reporting);
|
|
638
558
|
endDate = dayEnd;
|
|
639
559
|
}
|
|
640
560
|
}
|
|
641
561
|
else {
|
|
642
|
-
//
|
|
643
|
-
const lastMarketDay = this.
|
|
644
|
-
const { end: dayEnd } = this.getDayBoundaries(lastMarketDay);
|
|
562
|
+
// Not a market day - use previous market day's close
|
|
563
|
+
const lastMarketDay = this.calendar.getPreviousMarketDay(zonedEndDate);
|
|
564
|
+
const { end: dayEnd } = this.timeCalculator.getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
645
565
|
endDate = dayEnd;
|
|
646
566
|
}
|
|
647
|
-
//
|
|
567
|
+
// Calculate start date
|
|
648
568
|
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);
|
|
569
|
+
const { start: dayStart } = this.timeCalculator.getDayBoundaries(periodStartDate, intraday_reporting);
|
|
654
570
|
// Ensure start is not after end
|
|
655
|
-
if (isBefore(
|
|
571
|
+
if (isBefore(endDate, dayStart)) {
|
|
656
572
|
throw new Error('Start date cannot be after end date');
|
|
657
573
|
}
|
|
658
574
|
return {
|
|
659
|
-
start: this.formatDate(
|
|
660
|
-
end: this.formatDate(
|
|
575
|
+
start: this.formatter.formatDate(dayStart, outputFormat),
|
|
576
|
+
end: this.formatter.formatDate(endDate, outputFormat),
|
|
661
577
|
};
|
|
662
578
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
579
|
+
}
|
|
580
|
+
// ===== MARKET STATUS SERVICE =====
|
|
581
|
+
/**
|
|
582
|
+
* Service for determining market status
|
|
583
|
+
*/
|
|
584
|
+
class MarketStatusService {
|
|
585
|
+
calendar;
|
|
586
|
+
timeCalculator;
|
|
587
|
+
timezone;
|
|
588
|
+
constructor(timezone = MARKET_CONFIG.TIMEZONE) {
|
|
589
|
+
this.timezone = timezone;
|
|
590
|
+
this.calendar = new MarketCalendar(timezone);
|
|
591
|
+
this.timeCalculator = new MarketTimeCalculator(timezone);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Get current market status
|
|
595
|
+
*/
|
|
596
|
+
getMarketStatus(date = new Date()) {
|
|
597
|
+
const nyTime = toZonedTime(date, this.timezone);
|
|
598
|
+
const timeInMinutes = nyTime.getHours() * 60 + nyTime.getMinutes();
|
|
599
|
+
const isMarketDay = this.calendar.isMarketDay(nyTime);
|
|
600
|
+
const isEarlyCloseDay = this.calendar.isEarlyCloseDay(nyTime);
|
|
601
|
+
// Time boundaries
|
|
602
|
+
const extendedStartMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
603
|
+
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
604
|
+
const earlyMarketEndMinutes = MARKET_CONFIG.TIMES.EARLY_MARKET_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_MARKET_END.minute;
|
|
605
|
+
let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
606
|
+
let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
607
|
+
if (isEarlyCloseDay) {
|
|
608
|
+
marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
609
|
+
extendedEndMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
610
|
+
}
|
|
611
|
+
let status;
|
|
612
|
+
let nextStatus;
|
|
613
|
+
let nextStatusTime;
|
|
614
|
+
let marketPeriod;
|
|
615
|
+
if (!isMarketDay) {
|
|
616
|
+
// Market is closed (holiday/weekend)
|
|
617
|
+
status = 'closed';
|
|
618
|
+
nextStatus = 'extended hours';
|
|
619
|
+
marketPeriod = 'closed';
|
|
620
|
+
const nextMarketDay = this.calendar.getNextMarketDay(nyTime);
|
|
621
|
+
nextStatusTime = set(nextMarketDay, {
|
|
622
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
623
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
624
|
+
});
|
|
675
625
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
626
|
+
else if (timeInMinutes < extendedStartMinutes) {
|
|
627
|
+
// Before extended hours
|
|
628
|
+
status = 'closed';
|
|
629
|
+
nextStatus = 'extended hours';
|
|
630
|
+
marketPeriod = 'closed';
|
|
631
|
+
nextStatusTime = set(nyTime, {
|
|
632
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
633
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
else if (timeInMinutes < marketStartMinutes) {
|
|
637
|
+
// Pre-market extended hours
|
|
638
|
+
status = 'extended hours';
|
|
639
|
+
nextStatus = 'open';
|
|
640
|
+
marketPeriod = 'preMarket';
|
|
641
|
+
nextStatusTime = set(nyTime, {
|
|
642
|
+
hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour,
|
|
643
|
+
minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
else if (timeInMinutes < marketCloseMinutes) {
|
|
647
|
+
// Market is open
|
|
648
|
+
status = 'open';
|
|
649
|
+
nextStatus = 'extended hours';
|
|
650
|
+
marketPeriod = timeInMinutes < earlyMarketEndMinutes ? 'earlyMarket' : 'regularMarket';
|
|
651
|
+
nextStatusTime = set(nyTime, {
|
|
652
|
+
hours: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_CLOSE.hour : MARKET_CONFIG.TIMES.MARKET_CLOSE.hour,
|
|
653
|
+
minutes: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_CLOSE.minute : MARKET_CONFIG.TIMES.MARKET_CLOSE.minute,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
else if (timeInMinutes < extendedEndMinutes) {
|
|
657
|
+
// After-market extended hours
|
|
658
|
+
status = 'extended hours';
|
|
659
|
+
nextStatus = 'closed';
|
|
660
|
+
marketPeriod = 'afterMarket';
|
|
661
|
+
nextStatusTime = set(nyTime, {
|
|
662
|
+
hours: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour : MARKET_CONFIG.TIMES.EXTENDED_END.hour,
|
|
663
|
+
minutes: isEarlyCloseDay ? MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute : MARKET_CONFIG.TIMES.EXTENDED_END.minute,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// After extended hours
|
|
668
|
+
status = 'closed';
|
|
669
|
+
nextStatus = 'extended hours';
|
|
670
|
+
marketPeriod = 'closed';
|
|
671
|
+
const nextMarketDay = this.calendar.getNextMarketDay(nyTime);
|
|
672
|
+
nextStatusTime = set(nextMarketDay, {
|
|
673
|
+
hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
|
|
674
|
+
minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
|
|
675
|
+
});
|
|
699
676
|
}
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
const openExt = fromZonedTime(set(dayStart, { hours: extendedOpenTime.HOUR, minutes: extendedOpenTime.MINUTE }), this.timezone);
|
|
703
|
-
const closeExt = fromZonedTime(set(dayStart, { hours: extendedCloseTime.HOUR, minutes: extendedCloseTime.MINUTE }), this.timezone);
|
|
677
|
+
const nextStatusTimeUTC = fromZonedTime(nextStatusTime, this.timezone);
|
|
678
|
+
const dateFormat = 'MMMM dd, yyyy, HH:mm:ss a';
|
|
704
679
|
return {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
680
|
+
time: date,
|
|
681
|
+
timeString: format(nyTime, dateFormat),
|
|
682
|
+
status,
|
|
683
|
+
nextStatus,
|
|
684
|
+
marketPeriod,
|
|
685
|
+
nextStatusTime: nextStatusTimeUTC,
|
|
686
|
+
nextStatusTimeDifference: differenceInMilliseconds(nextStatusTime, nyTime),
|
|
687
|
+
nextStatusTimeString: format(nextStatusTime, dateFormat),
|
|
710
688
|
};
|
|
711
689
|
}
|
|
712
690
|
}
|
|
691
|
+
// ===== FUNCTIONAL API =====
|
|
713
692
|
/**
|
|
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
|
|
693
|
+
* Simple functional API that uses the services above
|
|
718
694
|
*/
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
* @returns {PeriodDates} The start and end timestamps
|
|
726
|
-
*/
|
|
727
|
-
function getStartAndEndTimestamps(params = {}) {
|
|
728
|
-
const util = createMarketTimeUtil(params.timezone, params.intraday_reporting);
|
|
729
|
-
const effectiveParams = {
|
|
730
|
-
...params,
|
|
731
|
-
end: params.referenceDate || params.end || new Date(),
|
|
732
|
-
};
|
|
733
|
-
return util.getMarketTimePeriod(effectiveParams);
|
|
734
|
-
}
|
|
695
|
+
// Create service instances
|
|
696
|
+
const marketCalendar = new MarketCalendar();
|
|
697
|
+
const marketTimeCalculator = new MarketTimeCalculator();
|
|
698
|
+
const periodCalculator = new PeriodCalculator();
|
|
699
|
+
const marketStatusService = new MarketStatusService();
|
|
700
|
+
const timeFormatter = new TimeFormatter();
|
|
735
701
|
/**
|
|
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
|
|
702
|
+
* Get market open/close times for a given date
|
|
740
703
|
*/
|
|
741
704
|
function getMarketOpenClose(options = {}) {
|
|
742
|
-
const
|
|
743
|
-
return
|
|
705
|
+
const { date = new Date() } = options;
|
|
706
|
+
return marketTimeCalculator.getMarketTimes(date);
|
|
744
707
|
}
|
|
745
708
|
/**
|
|
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
|
|
709
|
+
* Get start and end dates for a given market time period
|
|
751
710
|
*/
|
|
752
711
|
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
|
|
712
|
+
const { start, end } = periodCalculator.getMarketTimePeriod(params);
|
|
760
713
|
return {
|
|
761
714
|
start: new Date(start),
|
|
762
715
|
end: new Date(end),
|
|
763
716
|
};
|
|
764
717
|
}
|
|
765
718
|
/**
|
|
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
|
|
719
|
+
* Get the last full trading date
|
|
780
720
|
*/
|
|
781
721
|
function getLastFullTradingDate(currentDate = new Date()) {
|
|
782
|
-
const
|
|
783
|
-
const date = util.getLastFullTradingDate(currentDate);
|
|
784
|
-
// Format the date in NY timezone to ensure consistency
|
|
722
|
+
const date = marketTimeCalculator.getLastFullTradingDate(currentDate);
|
|
785
723
|
return {
|
|
786
724
|
date,
|
|
787
|
-
YYYYMMDD:
|
|
725
|
+
YYYYMMDD: timeFormatter.getTradingDate(date),
|
|
788
726
|
};
|
|
789
727
|
}
|
|
790
728
|
/**
|
|
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
|
|
729
|
+
* Get the next market day
|
|
798
730
|
*/
|
|
799
731
|
function getNextMarketDay({ referenceDate } = {}) {
|
|
800
|
-
const util = new MarketTimeUtil();
|
|
801
732
|
const startDate = referenceDate || new Date();
|
|
802
|
-
const nextDate =
|
|
733
|
+
const nextDate = marketCalendar.getNextMarketDay(startDate);
|
|
803
734
|
// Convert to start of day in NY time
|
|
804
|
-
const startOfDayNY = startOfDay(toZonedTime(nextDate,
|
|
805
|
-
const dateInET = fromZonedTime(startOfDayNY,
|
|
735
|
+
const startOfDayNY = startOfDay(toZonedTime(nextDate, MARKET_CONFIG.TIMEZONE));
|
|
736
|
+
const dateInET = fromZonedTime(startOfDayNY, MARKET_CONFIG.TIMEZONE);
|
|
806
737
|
return {
|
|
807
738
|
date: dateInET,
|
|
808
|
-
yyyymmdd:
|
|
739
|
+
yyyymmdd: timeFormatter.getTradingDate(dateInET),
|
|
809
740
|
dateISOString: dateInET.toISOString(),
|
|
810
741
|
};
|
|
811
742
|
}
|
|
812
743
|
/**
|
|
813
|
-
*
|
|
814
|
-
* @returns {Date} The current time in Eastern Time
|
|
744
|
+
* Get trading date in YYYY-MM-DD format
|
|
815
745
|
*/
|
|
816
|
-
|
|
817
|
-
return
|
|
818
|
-
}
|
|
746
|
+
function getTradingDate(time) {
|
|
747
|
+
return timeFormatter.getTradingDate(time);
|
|
748
|
+
}
|
|
819
749
|
/**
|
|
820
|
-
*
|
|
821
|
-
* @param {number|string|Date} time - The time to convert
|
|
822
|
-
* @returns {Date} The date in New York timezone
|
|
750
|
+
* Get New York timezone offset
|
|
823
751
|
*/
|
|
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');
|
|
752
|
+
function getNYTimeZone(date) {
|
|
753
|
+
return timeFormatter.getNYTimeZone(date);
|
|
835
754
|
}
|
|
836
755
|
/**
|
|
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
|
|
756
|
+
* Get current market status
|
|
840
757
|
*/
|
|
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');
|
|
758
|
+
function getMarketStatus(options = {}) {
|
|
759
|
+
const { date = new Date() } = options;
|
|
760
|
+
return marketStatusService.getMarketStatus(date);
|
|
855
761
|
}
|
|
856
762
|
/**
|
|
857
|
-
*
|
|
858
|
-
* @param dateString - The date string to check
|
|
859
|
-
* @returns "-04:00" during daylight savings (EDT) or "-05:00" during standard time (EST)
|
|
763
|
+
* Check if a date is a market day
|
|
860
764
|
*/
|
|
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
|
-
};
|
|
765
|
+
function isMarketDay(date) {
|
|
766
|
+
return marketCalendar.isMarketDay(date);
|
|
767
|
+
}
|
|
894
768
|
/**
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
769
|
+
* Function to find complete trading date periods, starting at the beginning of one trading date and ending at the last.
|
|
770
|
+
* By default, it gets the last trading date, returning the beginning and end. But we can also a) define the end date
|
|
771
|
+
* 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),
|
|
772
|
+
* 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).
|
|
773
|
+
* @param options.endDate - The end date to use, defaults to today
|
|
774
|
+
* @param options.days - The number of days to go back, defaults to 1
|
|
775
|
+
* @returns The start and end dates with proper market open/close times
|
|
899
776
|
*/
|
|
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
|
|
777
|
+
function getTradingStartAndEndDates(options = {}) {
|
|
778
|
+
const { endDate = new Date(), days = 1 } = options;
|
|
779
|
+
// Get the last full trading date for the end date
|
|
780
|
+
const endTradingDate = getLastFullTradingDate(endDate).date;
|
|
781
|
+
// Get the market close time for the end date (4:00 PM ET or 1:00 PM on short days)
|
|
782
|
+
const endMarketClose = getMarketOpenClose({ date: endTradingDate }).close;
|
|
783
|
+
let startDate;
|
|
784
|
+
if (days <= 1) {
|
|
785
|
+
// For 1 day, start is market open of the same trading day as end
|
|
786
|
+
startDate = getMarketOpenClose({ date: endTradingDate }).open;
|
|
982
787
|
}
|
|
983
788
|
else {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
789
|
+
// For multiple days, go back the specified number of trading days
|
|
790
|
+
let currentDate = new Date(endTradingDate);
|
|
791
|
+
let tradingDaysBack = 0;
|
|
792
|
+
// Count back trading days
|
|
793
|
+
while (tradingDaysBack < days - 1) {
|
|
794
|
+
currentDate = sub(currentDate, { days: 1 });
|
|
795
|
+
if (isMarketDay(currentDate)) {
|
|
796
|
+
tradingDaysBack++;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
// Get the market open time for the start date
|
|
800
|
+
startDate = getMarketOpenClose({ date: currentDate }).open;
|
|
991
801
|
}
|
|
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
|
-
};
|
|
802
|
+
return { startDate, endDate: endMarketClose };
|
|
1003
803
|
}
|
|
1004
804
|
|
|
1005
805
|
// format-tools.ts
|
|
@@ -2429,7 +2229,7 @@ const safeJSON = (text) => {
|
|
|
2429
2229
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2430
2230
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2431
2231
|
|
|
2432
|
-
const VERSION = '5.8.
|
|
2232
|
+
const VERSION = '5.8.3'; // x-release-please-version
|
|
2433
2233
|
|
|
2434
2234
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2435
2235
|
const isRunningInBrowser = () => {
|
|
@@ -3181,13 +2981,95 @@ function findDoubleNewlineIndex(buffer) {
|
|
|
3181
2981
|
return -1;
|
|
3182
2982
|
}
|
|
3183
2983
|
|
|
2984
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2985
|
+
const levelNumbers = {
|
|
2986
|
+
off: 0,
|
|
2987
|
+
error: 200,
|
|
2988
|
+
warn: 300,
|
|
2989
|
+
info: 400,
|
|
2990
|
+
debug: 500,
|
|
2991
|
+
};
|
|
2992
|
+
const parseLogLevel = (maybeLevel, sourceName, client) => {
|
|
2993
|
+
if (!maybeLevel) {
|
|
2994
|
+
return undefined;
|
|
2995
|
+
}
|
|
2996
|
+
if (hasOwn(levelNumbers, maybeLevel)) {
|
|
2997
|
+
return maybeLevel;
|
|
2998
|
+
}
|
|
2999
|
+
loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
|
|
3000
|
+
return undefined;
|
|
3001
|
+
};
|
|
3002
|
+
function noop() { }
|
|
3003
|
+
function makeLogFn(fnLevel, logger, logLevel) {
|
|
3004
|
+
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
|
|
3005
|
+
return noop;
|
|
3006
|
+
}
|
|
3007
|
+
else {
|
|
3008
|
+
// Don't wrap logger functions, we want the stacktrace intact!
|
|
3009
|
+
return logger[fnLevel].bind(logger);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
const noopLogger = {
|
|
3013
|
+
error: noop,
|
|
3014
|
+
warn: noop,
|
|
3015
|
+
info: noop,
|
|
3016
|
+
debug: noop,
|
|
3017
|
+
};
|
|
3018
|
+
let cachedLoggers = /* @__PURE__ */ new WeakMap();
|
|
3019
|
+
function loggerFor(client) {
|
|
3020
|
+
const logger = client.logger;
|
|
3021
|
+
const logLevel = client.logLevel ?? 'off';
|
|
3022
|
+
if (!logger) {
|
|
3023
|
+
return noopLogger;
|
|
3024
|
+
}
|
|
3025
|
+
const cachedLogger = cachedLoggers.get(logger);
|
|
3026
|
+
if (cachedLogger && cachedLogger[0] === logLevel) {
|
|
3027
|
+
return cachedLogger[1];
|
|
3028
|
+
}
|
|
3029
|
+
const levelLogger = {
|
|
3030
|
+
error: makeLogFn('error', logger, logLevel),
|
|
3031
|
+
warn: makeLogFn('warn', logger, logLevel),
|
|
3032
|
+
info: makeLogFn('info', logger, logLevel),
|
|
3033
|
+
debug: makeLogFn('debug', logger, logLevel),
|
|
3034
|
+
};
|
|
3035
|
+
cachedLoggers.set(logger, [logLevel, levelLogger]);
|
|
3036
|
+
return levelLogger;
|
|
3037
|
+
}
|
|
3038
|
+
const formatRequestDetails = (details) => {
|
|
3039
|
+
if (details.options) {
|
|
3040
|
+
details.options = { ...details.options };
|
|
3041
|
+
delete details.options['headers']; // redundant + leaks internals
|
|
3042
|
+
}
|
|
3043
|
+
if (details.headers) {
|
|
3044
|
+
details.headers = Object.fromEntries((details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(([name, value]) => [
|
|
3045
|
+
name,
|
|
3046
|
+
(name.toLowerCase() === 'authorization' ||
|
|
3047
|
+
name.toLowerCase() === 'cookie' ||
|
|
3048
|
+
name.toLowerCase() === 'set-cookie') ?
|
|
3049
|
+
'***'
|
|
3050
|
+
: value,
|
|
3051
|
+
]));
|
|
3052
|
+
}
|
|
3053
|
+
if ('retryOfRequestLogID' in details) {
|
|
3054
|
+
if (details.retryOfRequestLogID) {
|
|
3055
|
+
details.retryOf = details.retryOfRequestLogID;
|
|
3056
|
+
}
|
|
3057
|
+
delete details.retryOfRequestLogID;
|
|
3058
|
+
}
|
|
3059
|
+
return details;
|
|
3060
|
+
};
|
|
3061
|
+
|
|
3062
|
+
var _Stream_client;
|
|
3184
3063
|
class Stream {
|
|
3185
|
-
constructor(iterator, controller) {
|
|
3064
|
+
constructor(iterator, controller, client) {
|
|
3186
3065
|
this.iterator = iterator;
|
|
3066
|
+
_Stream_client.set(this, void 0);
|
|
3187
3067
|
this.controller = controller;
|
|
3068
|
+
__classPrivateFieldSet(this, _Stream_client, client);
|
|
3188
3069
|
}
|
|
3189
|
-
static fromSSEResponse(response, controller) {
|
|
3070
|
+
static fromSSEResponse(response, controller, client) {
|
|
3190
3071
|
let consumed = false;
|
|
3072
|
+
const logger = client ? loggerFor(client) : console;
|
|
3191
3073
|
async function* iterator() {
|
|
3192
3074
|
if (consumed) {
|
|
3193
3075
|
throw new OpenAIError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
|
|
@@ -3210,8 +3092,8 @@ class Stream {
|
|
|
3210
3092
|
data = JSON.parse(sse.data);
|
|
3211
3093
|
}
|
|
3212
3094
|
catch (e) {
|
|
3213
|
-
|
|
3214
|
-
|
|
3095
|
+
logger.error(`Could not parse message into JSON:`, sse.data);
|
|
3096
|
+
logger.error(`From chunk:`, sse.raw);
|
|
3215
3097
|
throw e;
|
|
3216
3098
|
}
|
|
3217
3099
|
if (data && data.error) {
|
|
@@ -3250,13 +3132,13 @@ class Stream {
|
|
|
3250
3132
|
controller.abort();
|
|
3251
3133
|
}
|
|
3252
3134
|
}
|
|
3253
|
-
return new Stream(iterator, controller);
|
|
3135
|
+
return new Stream(iterator, controller, client);
|
|
3254
3136
|
}
|
|
3255
3137
|
/**
|
|
3256
3138
|
* Generates a Stream from a newline-separated ReadableStream
|
|
3257
3139
|
* where each item is a JSON value.
|
|
3258
3140
|
*/
|
|
3259
|
-
static fromReadableStream(readableStream, controller) {
|
|
3141
|
+
static fromReadableStream(readableStream, controller, client) {
|
|
3260
3142
|
let consumed = false;
|
|
3261
3143
|
async function* iterLines() {
|
|
3262
3144
|
const lineDecoder = new LineDecoder();
|
|
@@ -3297,9 +3179,9 @@ class Stream {
|
|
|
3297
3179
|
controller.abort();
|
|
3298
3180
|
}
|
|
3299
3181
|
}
|
|
3300
|
-
return new Stream(iterator, controller);
|
|
3182
|
+
return new Stream(iterator, controller, client);
|
|
3301
3183
|
}
|
|
3302
|
-
[Symbol.asyncIterator]() {
|
|
3184
|
+
[(_Stream_client = new WeakMap(), Symbol.asyncIterator)]() {
|
|
3303
3185
|
return this.iterator();
|
|
3304
3186
|
}
|
|
3305
3187
|
/**
|
|
@@ -3323,8 +3205,8 @@ class Stream {
|
|
|
3323
3205
|
};
|
|
3324
3206
|
};
|
|
3325
3207
|
return [
|
|
3326
|
-
new Stream(() => teeIterator(left), this.controller),
|
|
3327
|
-
new Stream(() => teeIterator(right), this.controller),
|
|
3208
|
+
new Stream(() => teeIterator(left), this.controller, __classPrivateFieldGet(this, _Stream_client, "f")),
|
|
3209
|
+
new Stream(() => teeIterator(right), this.controller, __classPrivateFieldGet(this, _Stream_client, "f")),
|
|
3328
3210
|
];
|
|
3329
3211
|
}
|
|
3330
3212
|
/**
|
|
@@ -3458,84 +3340,6 @@ function partition(str, delimiter) {
|
|
|
3458
3340
|
return [str, '', ''];
|
|
3459
3341
|
}
|
|
3460
3342
|
|
|
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];
|
|
3505
|
-
}
|
|
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
|
-
}
|
|
3515
|
-
const formatRequestDetails = (details) => {
|
|
3516
|
-
if (details.options) {
|
|
3517
|
-
details.options = { ...details.options };
|
|
3518
|
-
delete details.options['headers']; // redundant + leaks internals
|
|
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;
|
|
3535
|
-
}
|
|
3536
|
-
return details;
|
|
3537
|
-
};
|
|
3538
|
-
|
|
3539
3343
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
3540
3344
|
async function defaultParseResponse(client, props) {
|
|
3541
3345
|
const { response, requestLogID, retryOfRequestLogID, startTime } = props;
|
|
@@ -3545,9 +3349,9 @@ async function defaultParseResponse(client, props) {
|
|
|
3545
3349
|
// Note: there is an invariant here that isn't represented in the type system
|
|
3546
3350
|
// that if you set `stream: true` the response type must also be `Stream<T>`
|
|
3547
3351
|
if (props.options.__streamClass) {
|
|
3548
|
-
return props.options.__streamClass.fromSSEResponse(response, props.controller);
|
|
3352
|
+
return props.options.__streamClass.fromSSEResponse(response, props.controller, client);
|
|
3549
3353
|
}
|
|
3550
|
-
return Stream.fromSSEResponse(response, props.controller);
|
|
3354
|
+
return Stream.fromSSEResponse(response, props.controller, client);
|
|
3551
3355
|
}
|
|
3552
3356
|
// fetch refuses to read the body when the status code is 204.
|
|
3553
3357
|
if (response.status === 204) {
|
|
@@ -3801,7 +3605,7 @@ const isAsyncIterable = (value) => value != null && typeof value === 'object' &&
|
|
|
3801
3605
|
const multipartFormRequestOptions = async (opts, fetch) => {
|
|
3802
3606
|
return { ...opts, body: await createForm(opts.body, fetch) };
|
|
3803
3607
|
};
|
|
3804
|
-
const supportsFormDataMap =
|
|
3608
|
+
const supportsFormDataMap = /* @__PURE__ */ new WeakMap();
|
|
3805
3609
|
/**
|
|
3806
3610
|
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
|
|
3807
3611
|
* properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
|
|
@@ -3977,21 +3781,38 @@ class APIResource {
|
|
|
3977
3781
|
function encodeURIPath(str) {
|
|
3978
3782
|
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
|
|
3979
3783
|
}
|
|
3784
|
+
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
3980
3785
|
const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics, ...params) {
|
|
3981
3786
|
// If there are no params, no processing is needed.
|
|
3982
3787
|
if (statics.length === 1)
|
|
3983
3788
|
return statics[0];
|
|
3984
3789
|
let postPath = false;
|
|
3790
|
+
const invalidSegments = [];
|
|
3985
3791
|
const path = statics.reduce((previousValue, currentValue, index) => {
|
|
3986
3792
|
if (/[?#]/.test(currentValue)) {
|
|
3987
3793
|
postPath = true;
|
|
3988
3794
|
}
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3795
|
+
const value = params[index];
|
|
3796
|
+
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
|
|
3797
|
+
if (index !== params.length &&
|
|
3798
|
+
(value == null ||
|
|
3799
|
+
(typeof value === 'object' &&
|
|
3800
|
+
// handle values from other realms
|
|
3801
|
+
value.toString ===
|
|
3802
|
+
Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY) ?? EMPTY)
|
|
3803
|
+
?.toString))) {
|
|
3804
|
+
encoded = value + '';
|
|
3805
|
+
invalidSegments.push({
|
|
3806
|
+
start: previousValue.length + currentValue.length,
|
|
3807
|
+
length: encoded.length,
|
|
3808
|
+
error: `Value of type ${Object.prototype.toString
|
|
3809
|
+
.call(value)
|
|
3810
|
+
.slice(8, -1)} is not a valid path parameter`,
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
return previousValue + currentValue + (index === params.length ? '' : encoded);
|
|
3992
3814
|
}, '');
|
|
3993
3815
|
const pathOnly = path.split(/[?#]/, 1)[0];
|
|
3994
|
-
const invalidSegments = [];
|
|
3995
3816
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
3996
3817
|
let match;
|
|
3997
3818
|
// Find all invalid segments
|
|
@@ -3999,8 +3820,10 @@ const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(sta
|
|
|
3999
3820
|
invalidSegments.push({
|
|
4000
3821
|
start: match.index,
|
|
4001
3822
|
length: match[0].length,
|
|
3823
|
+
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
|
|
4002
3824
|
});
|
|
4003
3825
|
}
|
|
3826
|
+
invalidSegments.sort((a, b) => a.start - b.start);
|
|
4004
3827
|
if (invalidSegments.length > 0) {
|
|
4005
3828
|
let lastEnd = 0;
|
|
4006
3829
|
const underline = invalidSegments.reduce((acc, segment) => {
|
|
@@ -4009,7 +3832,9 @@ const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(sta
|
|
|
4009
3832
|
lastEnd = segment.start + segment.length;
|
|
4010
3833
|
return acc + spaces + arrows;
|
|
4011
3834
|
}, '');
|
|
4012
|
-
throw new OpenAIError(`Path parameters result in path with invalid segments:\n${
|
|
3835
|
+
throw new OpenAIError(`Path parameters result in path with invalid segments:\n${invalidSegments
|
|
3836
|
+
.map((e) => e.error)
|
|
3837
|
+
.join('\n')}\n${path}\n${underline}`);
|
|
4013
3838
|
}
|
|
4014
3839
|
return path;
|
|
4015
3840
|
};
|
|
@@ -15369,35 +15194,6 @@ function requireWebsocketServer () {
|
|
|
15369
15194
|
|
|
15370
15195
|
requireWebsocketServer();
|
|
15371
15196
|
|
|
15372
|
-
/**
|
|
15373
|
-
* Logs a message to the console.
|
|
15374
|
-
* @param message The message to log.
|
|
15375
|
-
* @param options Optional options.
|
|
15376
|
-
* @param options.source The source of the message.
|
|
15377
|
-
* @param options.type The type of message to log.
|
|
15378
|
-
* @param options.symbol The trading symbol associated with this log.
|
|
15379
|
-
* @param options.account The account associated with this log.
|
|
15380
|
-
*/
|
|
15381
|
-
function log$1(message, options = { source: 'App', type: 'info' }) {
|
|
15382
|
-
// Format the timestamp
|
|
15383
|
-
const date = new Date();
|
|
15384
|
-
const timestamp = date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
15385
|
-
const account = options?.account;
|
|
15386
|
-
const symbol = options?.symbol;
|
|
15387
|
-
// Build the log message
|
|
15388
|
-
const logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ''}${account ? ` [${account}] ` : ''}${symbol ? ` [${symbol}] ` : ''}${message}`;
|
|
15389
|
-
// Use appropriate console method based on type
|
|
15390
|
-
if (options?.type === 'error') {
|
|
15391
|
-
console.error(logMessage);
|
|
15392
|
-
}
|
|
15393
|
-
else if (options?.type === 'warn') {
|
|
15394
|
-
console.warn(logMessage);
|
|
15395
|
-
}
|
|
15396
|
-
else {
|
|
15397
|
-
console.log(logMessage);
|
|
15398
|
-
}
|
|
15399
|
-
}
|
|
15400
|
-
|
|
15401
15197
|
const log = (message, options = { type: 'info' }) => {
|
|
15402
15198
|
log$1(message, { ...options, source: 'AlpacaMarketDataAPI' });
|
|
15403
15199
|
};
|
|
@@ -15421,22 +15217,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15421
15217
|
optionWs = null;
|
|
15422
15218
|
stockSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
15423
15219
|
optionSubscriptions = { trades: [], quotes: [], bars: [] };
|
|
15424
|
-
apiKey;
|
|
15425
|
-
secretKey;
|
|
15426
|
-
accountType;
|
|
15427
15220
|
setMode(mode = 'production') {
|
|
15428
|
-
if (mode === 'sandbox') {
|
|
15429
|
-
// sandbox mode
|
|
15221
|
+
if (mode === 'sandbox') { // sandbox mode
|
|
15430
15222
|
this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
|
|
15431
15223
|
this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
|
|
15432
15224
|
}
|
|
15433
|
-
else if (mode === 'test') {
|
|
15434
|
-
// test mode, can only use ticker FAKEPACA
|
|
15225
|
+
else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
|
|
15435
15226
|
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
|
|
15436
15227
|
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // there's no test mode for options
|
|
15437
15228
|
}
|
|
15438
|
-
else {
|
|
15439
|
-
// production
|
|
15229
|
+
else { // production
|
|
15440
15230
|
this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
|
|
15441
15231
|
this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
|
|
15442
15232
|
}
|
|
@@ -15452,30 +15242,24 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15452
15242
|
return 'production';
|
|
15453
15243
|
}
|
|
15454
15244
|
}
|
|
15455
|
-
constructor(
|
|
15245
|
+
constructor() {
|
|
15456
15246
|
super();
|
|
15457
|
-
this.apiKey = config.apiKey;
|
|
15458
|
-
this.secretKey = config.secretKey;
|
|
15459
|
-
this.accountType = config.accountType || 'LIVE';
|
|
15460
15247
|
this.dataURL = 'https://data.alpaca.markets/v2';
|
|
15461
15248
|
this.apiURL =
|
|
15462
|
-
|
|
15249
|
+
process.env.ALPACA_ACCOUNT_TYPE === 'PAPER'
|
|
15463
15250
|
? 'https://paper-api.alpaca.markets/v2'
|
|
15464
15251
|
: 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
|
|
15465
15252
|
this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
|
|
15466
15253
|
this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
|
|
15467
15254
|
this.headers = {
|
|
15468
|
-
'APCA-API-KEY-ID':
|
|
15469
|
-
'APCA-API-SECRET-KEY':
|
|
15255
|
+
'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
|
|
15256
|
+
'APCA-API-SECRET-KEY': process.env.ALPACA_SECRET_KEY,
|
|
15470
15257
|
'Content-Type': 'application/json',
|
|
15471
15258
|
};
|
|
15472
15259
|
}
|
|
15473
|
-
static getInstance(
|
|
15260
|
+
static getInstance() {
|
|
15474
15261
|
if (!AlpacaMarketDataAPI.instance) {
|
|
15475
|
-
|
|
15476
|
-
throw new Error('AlpacaMarketDataAPI config is required for first initialization');
|
|
15477
|
-
}
|
|
15478
|
-
AlpacaMarketDataAPI.instance = new AlpacaMarketDataAPI(config);
|
|
15262
|
+
AlpacaMarketDataAPI.instance = new AlpacaMarketDataAPI();
|
|
15479
15263
|
}
|
|
15480
15264
|
return AlpacaMarketDataAPI.instance;
|
|
15481
15265
|
}
|
|
@@ -15498,8 +15282,8 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15498
15282
|
log(`${streamType} stream connected`, { type: 'info' });
|
|
15499
15283
|
const authMessage = {
|
|
15500
15284
|
action: 'auth',
|
|
15501
|
-
key:
|
|
15502
|
-
secret:
|
|
15285
|
+
key: process.env.ALPACA_API_KEY,
|
|
15286
|
+
secret: process.env.ALPACA_SECRET_KEY,
|
|
15503
15287
|
};
|
|
15504
15288
|
ws.send(JSON.stringify(authMessage));
|
|
15505
15289
|
});
|
|
@@ -15590,7 +15374,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15590
15374
|
const currentSubscriptions = streamType === 'stock' ? this.stockSubscriptions : this.optionSubscriptions;
|
|
15591
15375
|
Object.entries(subscriptions).forEach(([key, value]) => {
|
|
15592
15376
|
if (value) {
|
|
15593
|
-
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(
|
|
15377
|
+
currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
|
|
15594
15378
|
}
|
|
15595
15379
|
});
|
|
15596
15380
|
const unsubMessage = {
|
|
@@ -15653,11 +15437,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15653
15437
|
let pageCount = 0;
|
|
15654
15438
|
let currency = '';
|
|
15655
15439
|
// Initialize bar arrays for each symbol
|
|
15656
|
-
symbols.forEach(
|
|
15440
|
+
symbols.forEach(symbol => {
|
|
15657
15441
|
allBars[symbol] = [];
|
|
15658
15442
|
});
|
|
15659
15443
|
log(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
15660
|
-
type: 'info'
|
|
15444
|
+
type: 'info'
|
|
15661
15445
|
});
|
|
15662
15446
|
while (hasMorePages) {
|
|
15663
15447
|
pageCount++;
|
|
@@ -15685,7 +15469,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15685
15469
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
15686
15470
|
pageBarsCount += bars.length;
|
|
15687
15471
|
// Track date range for this page
|
|
15688
|
-
bars.forEach(
|
|
15472
|
+
bars.forEach(bar => {
|
|
15689
15473
|
const barDate = new Date(bar.t);
|
|
15690
15474
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
15691
15475
|
earliestTimestamp = barDate;
|
|
@@ -15704,7 +15488,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15704
15488
|
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
15705
15489
|
: 'unknown range';
|
|
15706
15490
|
log(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
15707
|
-
type: 'info'
|
|
15491
|
+
type: 'info'
|
|
15708
15492
|
});
|
|
15709
15493
|
// Prevent infinite loops
|
|
15710
15494
|
if (pageCount > 1000) {
|
|
@@ -15713,11 +15497,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15713
15497
|
}
|
|
15714
15498
|
}
|
|
15715
15499
|
// Final summary
|
|
15716
|
-
const symbolCounts = Object.entries(allBars)
|
|
15717
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
15718
|
-
.join(', ');
|
|
15500
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
15719
15501
|
log(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
15720
|
-
type: 'info'
|
|
15502
|
+
type: 'info'
|
|
15721
15503
|
});
|
|
15722
15504
|
return {
|
|
15723
15505
|
bars: allBars,
|
|
@@ -15985,11 +15767,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
15985
15767
|
let totalBarsCount = 0;
|
|
15986
15768
|
let pageCount = 0;
|
|
15987
15769
|
// Initialize bar arrays for each symbol
|
|
15988
|
-
symbols.forEach(
|
|
15770
|
+
symbols.forEach(symbol => {
|
|
15989
15771
|
allBars[symbol] = [];
|
|
15990
15772
|
});
|
|
15991
15773
|
log(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
15992
|
-
type: 'info'
|
|
15774
|
+
type: 'info'
|
|
15993
15775
|
});
|
|
15994
15776
|
while (hasMorePages) {
|
|
15995
15777
|
pageCount++;
|
|
@@ -16011,7 +15793,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16011
15793
|
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
16012
15794
|
pageBarsCount += bars.length;
|
|
16013
15795
|
// Track date range for this page
|
|
16014
|
-
bars.forEach(
|
|
15796
|
+
bars.forEach(bar => {
|
|
16015
15797
|
const barDate = new Date(bar.t);
|
|
16016
15798
|
if (!earliestTimestamp || barDate < earliestTimestamp) {
|
|
16017
15799
|
earliestTimestamp = barDate;
|
|
@@ -16030,7 +15812,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16030
15812
|
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
16031
15813
|
: 'unknown range';
|
|
16032
15814
|
log(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
16033
|
-
type: 'info'
|
|
15815
|
+
type: 'info'
|
|
16034
15816
|
});
|
|
16035
15817
|
// Prevent infinite loops
|
|
16036
15818
|
if (pageCount > 1000) {
|
|
@@ -16039,11 +15821,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16039
15821
|
}
|
|
16040
15822
|
}
|
|
16041
15823
|
// Final summary
|
|
16042
|
-
const symbolCounts = Object.entries(allBars)
|
|
16043
|
-
.map(([symbol, bars]) => `${symbol}: ${bars.length}`)
|
|
16044
|
-
.join(', ');
|
|
15824
|
+
const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
|
|
16045
15825
|
log(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
|
|
16046
|
-
type: 'info'
|
|
15826
|
+
type: 'info'
|
|
16047
15827
|
});
|
|
16048
15828
|
return {
|
|
16049
15829
|
bars: allBars,
|
|
@@ -16067,11 +15847,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16067
15847
|
let totalTradesCount = 0;
|
|
16068
15848
|
let pageCount = 0;
|
|
16069
15849
|
// Initialize trades arrays for each symbol
|
|
16070
|
-
symbols.forEach(
|
|
15850
|
+
symbols.forEach(symbol => {
|
|
16071
15851
|
allTrades[symbol] = [];
|
|
16072
15852
|
});
|
|
16073
15853
|
log(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
|
|
16074
|
-
type: 'info'
|
|
15854
|
+
type: 'info'
|
|
16075
15855
|
});
|
|
16076
15856
|
while (hasMorePages) {
|
|
16077
15857
|
pageCount++;
|
|
@@ -16093,7 +15873,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16093
15873
|
allTrades[symbol] = [...allTrades[symbol], ...trades];
|
|
16094
15874
|
pageTradesCount += trades.length;
|
|
16095
15875
|
// Track date range for this page
|
|
16096
|
-
trades.forEach(
|
|
15876
|
+
trades.forEach(trade => {
|
|
16097
15877
|
const tradeDate = new Date(trade.t);
|
|
16098
15878
|
if (!earliestTimestamp || tradeDate < earliestTimestamp) {
|
|
16099
15879
|
earliestTimestamp = tradeDate;
|
|
@@ -16112,7 +15892,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16112
15892
|
? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
|
|
16113
15893
|
: 'unknown range';
|
|
16114
15894
|
log(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
|
|
16115
|
-
type: 'info'
|
|
15895
|
+
type: 'info'
|
|
16116
15896
|
});
|
|
16117
15897
|
// Prevent infinite loops
|
|
16118
15898
|
if (pageCount > 1000) {
|
|
@@ -16121,11 +15901,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16121
15901
|
}
|
|
16122
15902
|
}
|
|
16123
15903
|
// Final summary
|
|
16124
|
-
const symbolCounts = Object.entries(allTrades)
|
|
16125
|
-
.map(([symbol, trades]) => `${symbol}: ${trades.length}`)
|
|
16126
|
-
.join(', ');
|
|
15904
|
+
const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
|
|
16127
15905
|
log(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
|
|
16128
|
-
type: 'info'
|
|
15906
|
+
type: 'info'
|
|
16129
15907
|
});
|
|
16130
15908
|
return {
|
|
16131
15909
|
trades: allTrades,
|
|
@@ -16270,9 +16048,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16270
16048
|
...(symbol && { symbols: symbol }),
|
|
16271
16049
|
...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
|
|
16272
16050
|
...(mergedParams.sort && { sort: mergedParams.sort }),
|
|
16273
|
-
...(mergedParams.include_content !== undefined
|
|
16274
|
-
? { include_content: mergedParams.include_content.toString() }
|
|
16275
|
-
: {}),
|
|
16051
|
+
...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
|
|
16276
16052
|
...(pageToken && { page_token: pageToken }),
|
|
16277
16053
|
});
|
|
16278
16054
|
const url = `${this.v1beta1url}/news?${queryParams}`;
|
|
@@ -16316,6 +16092,8 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
16316
16092
|
return newsArticles;
|
|
16317
16093
|
}
|
|
16318
16094
|
}
|
|
16095
|
+
// Export the singleton instance
|
|
16096
|
+
const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
16319
16097
|
|
|
16320
16098
|
const limitPriceSlippagePercent100 = 0.1; // 0.1%
|
|
16321
16099
|
/**
|
|
@@ -16327,11 +16105,11 @@ Websocket example
|
|
|
16327
16105
|
alpacaAPI.connectWebsocket(); // necessary to connect to the WebSocket
|
|
16328
16106
|
*/
|
|
16329
16107
|
class AlpacaTradingAPI {
|
|
16330
|
-
static new(credentials
|
|
16331
|
-
return new AlpacaTradingAPI(credentials
|
|
16108
|
+
static new(credentials) {
|
|
16109
|
+
return new AlpacaTradingAPI(credentials);
|
|
16332
16110
|
}
|
|
16333
|
-
static getInstance(credentials
|
|
16334
|
-
return new AlpacaTradingAPI(credentials
|
|
16111
|
+
static getInstance(credentials) {
|
|
16112
|
+
return new AlpacaTradingAPI(credentials);
|
|
16335
16113
|
}
|
|
16336
16114
|
ws = null;
|
|
16337
16115
|
headers;
|
|
@@ -16345,7 +16123,6 @@ class AlpacaTradingAPI {
|
|
|
16345
16123
|
reconnectTimeout = null;
|
|
16346
16124
|
messageHandlers = new Map();
|
|
16347
16125
|
debugLogging = false;
|
|
16348
|
-
marketDataAPI;
|
|
16349
16126
|
/**
|
|
16350
16127
|
* Constructor for AlpacaTradingAPI
|
|
16351
16128
|
* @param credentials - Alpaca credentials,
|
|
@@ -16354,14 +16131,11 @@ class AlpacaTradingAPI {
|
|
|
16354
16131
|
* apiSecret: string; // Alpaca API secret
|
|
16355
16132
|
* type: AlpacaAccountType;
|
|
16356
16133
|
* orderType: AlpacaOrderType;
|
|
16357
|
-
* @param marketDataConfig - Market data API configuration
|
|
16358
16134
|
* @param options - Optional options
|
|
16359
16135
|
* debugLogging: boolean; // Whether to log messages of type 'debug'
|
|
16360
16136
|
*/
|
|
16361
|
-
constructor(credentials,
|
|
16137
|
+
constructor(credentials, options) {
|
|
16362
16138
|
this.credentials = credentials;
|
|
16363
|
-
// Initialize market data API instance
|
|
16364
|
-
this.marketDataAPI = AlpacaMarketDataAPI.getInstance(marketDataConfig);
|
|
16365
16139
|
// Set URLs based on account type
|
|
16366
16140
|
this.apiBaseUrl =
|
|
16367
16141
|
credentials.type === 'PAPER' ? 'https://paper-api.alpaca.markets/v2' : 'https://api.alpaca.markets/v2';
|
|
@@ -16900,7 +16674,7 @@ class AlpacaTradingAPI {
|
|
|
16900
16674
|
this.log(`Found ${positions.length} positions to close`);
|
|
16901
16675
|
// Get latest quotes for all positions
|
|
16902
16676
|
const symbols = positions.map((position) => position.symbol);
|
|
16903
|
-
const quotesResponse = await
|
|
16677
|
+
const quotesResponse = await marketDataAPI.getLatestQuotes(symbols);
|
|
16904
16678
|
const lengthOfQuotes = Object.keys(quotesResponse.quotes).length;
|
|
16905
16679
|
if (lengthOfQuotes === 0) {
|
|
16906
16680
|
this.log('No quotes available for positions, received 0 quotes', {
|
|
@@ -16967,7 +16741,7 @@ class AlpacaTradingAPI {
|
|
|
16967
16741
|
this.log(`Cancelled all open orders`);
|
|
16968
16742
|
// Get latest quotes for all positions
|
|
16969
16743
|
const symbols = positions.map((position) => position.symbol);
|
|
16970
|
-
const quotesResponse = await
|
|
16744
|
+
const quotesResponse = await marketDataAPI.getLatestQuotes(symbols);
|
|
16971
16745
|
// Create limit orders to close each position
|
|
16972
16746
|
for (const position of positions) {
|
|
16973
16747
|
const quote = quotesResponse.quotes[position.symbol];
|
|
@@ -17650,13 +17424,6 @@ class AlpacaTradingAPI {
|
|
|
17650
17424
|
}
|
|
17651
17425
|
}
|
|
17652
17426
|
|
|
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
17427
|
const disco = {
|
|
17661
17428
|
types: Types,
|
|
17662
17429
|
alpaca: {
|
|
@@ -17704,31 +17471,14 @@ const disco = {
|
|
|
17704
17471
|
calculateFibonacciLevels: calculateFibonacciLevels,
|
|
17705
17472
|
},
|
|
17706
17473
|
time: {
|
|
17707
|
-
toUnixTimestamp: toUnixTimestamp,
|
|
17708
|
-
getTimeAgo: getTimeAgo,
|
|
17709
|
-
timeAgo: timeAgo,
|
|
17710
|
-
normalizeDate: normalizeDate,
|
|
17711
|
-
getDateInNY: getDateInNY,
|
|
17712
|
-
createMarketTimeUtil: createMarketTimeUtil,
|
|
17713
|
-
getStartAndEndTimestamps: getStartAndEndTimestamps,
|
|
17714
17474
|
getStartAndEndDates: getStartAndEndDates,
|
|
17715
17475
|
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
17476
|
getLastFullTradingDate: getLastFullTradingDate,
|
|
17724
17477
|
getNextMarketDay: getNextMarketDay,
|
|
17725
|
-
parseETDateFromAV: parseETDateFromAV,
|
|
17726
|
-
formatToUSEastern: formatToUSEastern,
|
|
17727
|
-
unixTimetoUSEastern: unixTimetoUSEastern,
|
|
17728
17478
|
getMarketStatus: getMarketStatus,
|
|
17729
|
-
timeDiffString: timeDiffString,
|
|
17730
17479
|
getNYTimeZone: getNYTimeZone,
|
|
17731
17480
|
getTradingDate: getTradingDate,
|
|
17481
|
+
getTradingStartAndEndDates: getTradingStartAndEndDates,
|
|
17732
17482
|
},
|
|
17733
17483
|
utils: {
|
|
17734
17484
|
logIfDebug: logIfDebug,
|
|
@@ -17737,5 +17487,5 @@ const disco = {
|
|
|
17737
17487
|
},
|
|
17738
17488
|
};
|
|
17739
17489
|
|
|
17740
|
-
export { AlpacaMarketDataAPI, AlpacaTradingAPI,
|
|
17490
|
+
export { AlpacaMarketDataAPI, AlpacaTradingAPI, disco };
|
|
17741
17491
|
//# sourceMappingURL=index.mjs.map
|