@discomedia/utils 1.0.3 → 1.0.6

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