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