ts-time-utils 3.0.4 → 4.1.0
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 +186 -6
- package/dist/calculate.d.ts +25 -0
- package/dist/calculate.d.ts.map +1 -1
- package/dist/calculate.js +125 -0
- package/dist/calendar.d.ts +45 -0
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +68 -0
- package/dist/calendars.d.ts +156 -0
- package/dist/calendars.d.ts.map +1 -0
- package/dist/calendars.js +348 -0
- package/dist/compare.d.ts +27 -0
- package/dist/compare.d.ts.map +1 -1
- package/dist/compare.js +46 -0
- package/dist/esm/calculate.d.ts +25 -0
- package/dist/esm/calculate.d.ts.map +1 -1
- package/dist/esm/calculate.js +125 -0
- package/dist/esm/calendar.d.ts +45 -0
- package/dist/esm/calendar.d.ts.map +1 -1
- package/dist/esm/calendar.js +68 -0
- package/dist/esm/calendars.d.ts +156 -0
- package/dist/esm/calendars.d.ts.map +1 -0
- package/dist/esm/calendars.js +348 -0
- package/dist/esm/compare.d.ts +27 -0
- package/dist/esm/compare.d.ts.map +1 -1
- package/dist/esm/compare.js +46 -0
- package/dist/esm/finance.d.ts +236 -0
- package/dist/esm/finance.d.ts.map +1 -0
- package/dist/esm/finance.js +495 -0
- package/dist/esm/healthcare.d.ts +260 -0
- package/dist/esm/healthcare.d.ts.map +1 -0
- package/dist/esm/healthcare.js +447 -0
- package/dist/esm/holidays.d.ts +11 -1
- package/dist/esm/holidays.d.ts.map +1 -1
- package/dist/esm/holidays.js +220 -1
- package/dist/esm/index.d.ts +19 -7
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +23 -9
- package/dist/esm/iterate.d.ts +55 -0
- package/dist/esm/iterate.d.ts.map +1 -1
- package/dist/esm/iterate.js +86 -0
- package/dist/esm/locale.d.ts +53 -0
- package/dist/esm/locale.d.ts.map +1 -1
- package/dist/esm/locale.js +141 -0
- package/dist/esm/precision.d.ts +225 -0
- package/dist/esm/precision.d.ts.map +1 -0
- package/dist/esm/precision.js +491 -0
- package/dist/esm/scheduling.d.ts +206 -0
- package/dist/esm/scheduling.d.ts.map +1 -0
- package/dist/esm/scheduling.js +329 -0
- package/dist/esm/temporal.d.ts +237 -0
- package/dist/esm/temporal.d.ts.map +1 -0
- package/dist/esm/temporal.js +660 -0
- package/dist/esm/validate.d.ts +30 -0
- package/dist/esm/validate.d.ts.map +1 -1
- package/dist/esm/validate.js +48 -0
- package/dist/finance.d.ts +236 -0
- package/dist/finance.d.ts.map +1 -0
- package/dist/finance.js +495 -0
- package/dist/healthcare.d.ts +260 -0
- package/dist/healthcare.d.ts.map +1 -0
- package/dist/healthcare.js +447 -0
- package/dist/holidays.d.ts +11 -1
- package/dist/holidays.d.ts.map +1 -1
- package/dist/holidays.js +220 -1
- package/dist/index.d.ts +19 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -9
- package/dist/iterate.d.ts +55 -0
- package/dist/iterate.d.ts.map +1 -1
- package/dist/iterate.js +86 -0
- package/dist/locale.d.ts +53 -0
- package/dist/locale.d.ts.map +1 -1
- package/dist/locale.js +141 -0
- package/dist/precision.d.ts +225 -0
- package/dist/precision.d.ts.map +1 -0
- package/dist/precision.js +491 -0
- package/dist/scheduling.d.ts +206 -0
- package/dist/scheduling.d.ts.map +1 -0
- package/dist/scheduling.js +329 -0
- package/dist/temporal.d.ts +237 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +660 -0
- package/dist/validate.d.ts +30 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +48 -0
- package/package.json +31 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Finance utilities for market-aware date calculations
|
|
3
|
+
* Provides US market hours, trading days, settlement dates, and options expiration
|
|
4
|
+
*/
|
|
5
|
+
/** Market hours for US exchanges */
|
|
6
|
+
export const MARKET_HOURS = {
|
|
7
|
+
NYSE: {
|
|
8
|
+
open: { hour: 9, minute: 30 },
|
|
9
|
+
close: { hour: 16, minute: 0 },
|
|
10
|
+
timezone: 'America/New_York',
|
|
11
|
+
preMarket: { hour: 4, minute: 0 },
|
|
12
|
+
afterHours: { hour: 20, minute: 0 }
|
|
13
|
+
},
|
|
14
|
+
NASDAQ: {
|
|
15
|
+
open: { hour: 9, minute: 30 },
|
|
16
|
+
close: { hour: 16, minute: 0 },
|
|
17
|
+
timezone: 'America/New_York',
|
|
18
|
+
preMarket: { hour: 4, minute: 0 },
|
|
19
|
+
afterHours: { hour: 20, minute: 0 }
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/** US market holidays (NYSE/NASDAQ follow same schedule) */
|
|
23
|
+
export const US_MARKET_HOLIDAYS = [
|
|
24
|
+
"New Year's Day",
|
|
25
|
+
'Martin Luther King Jr. Day',
|
|
26
|
+
"Presidents' Day",
|
|
27
|
+
'Good Friday',
|
|
28
|
+
'Memorial Day',
|
|
29
|
+
'Juneteenth',
|
|
30
|
+
'Independence Day',
|
|
31
|
+
'Labor Day',
|
|
32
|
+
'Thanksgiving Day',
|
|
33
|
+
'Christmas Day'
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Helper to convert DateInput to Date
|
|
37
|
+
*/
|
|
38
|
+
function toDate(input) {
|
|
39
|
+
if (input instanceof Date)
|
|
40
|
+
return new Date(input);
|
|
41
|
+
return new Date(input);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Calculate Easter Sunday using the Anonymous Gregorian algorithm
|
|
45
|
+
*/
|
|
46
|
+
function getEasterSunday(year) {
|
|
47
|
+
const a = year % 19;
|
|
48
|
+
const b = Math.floor(year / 100);
|
|
49
|
+
const c = year % 100;
|
|
50
|
+
const d = Math.floor(b / 4);
|
|
51
|
+
const e = b % 4;
|
|
52
|
+
const f = Math.floor((b + 8) / 25);
|
|
53
|
+
const g = Math.floor((b - f + 1) / 3);
|
|
54
|
+
const h = (19 * a + b - d - g + 15) % 30;
|
|
55
|
+
const i = Math.floor(c / 4);
|
|
56
|
+
const k = c % 4;
|
|
57
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
58
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
59
|
+
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
60
|
+
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
61
|
+
return new Date(year, month - 1, day);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get nth occurrence of a weekday in a month
|
|
65
|
+
*/
|
|
66
|
+
function getNthWeekdayOfMonth(year, month, dayOfWeek, n) {
|
|
67
|
+
const firstDay = new Date(year, month, 1);
|
|
68
|
+
const firstWeekday = firstDay.getDay();
|
|
69
|
+
let dayOffset = dayOfWeek - firstWeekday;
|
|
70
|
+
if (dayOffset < 0)
|
|
71
|
+
dayOffset += 7;
|
|
72
|
+
const date = 1 + dayOffset + (n - 1) * 7;
|
|
73
|
+
return new Date(year, month, date);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get last occurrence of a weekday in a month
|
|
77
|
+
*/
|
|
78
|
+
function getLastWeekdayOfMonth(year, month, dayOfWeek) {
|
|
79
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
80
|
+
const lastDate = lastDay.getDate();
|
|
81
|
+
const lastWeekday = lastDay.getDay();
|
|
82
|
+
let diff = lastWeekday - dayOfWeek;
|
|
83
|
+
if (diff < 0)
|
|
84
|
+
diff += 7;
|
|
85
|
+
return new Date(year, month, lastDate - diff);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Adjust date if it falls on a weekend (observed holiday rules)
|
|
89
|
+
* - If Saturday, observe on Friday
|
|
90
|
+
* - If Sunday, observe on Monday
|
|
91
|
+
*/
|
|
92
|
+
function adjustForWeekend(date) {
|
|
93
|
+
const day = date.getDay();
|
|
94
|
+
if (day === 0) { // Sunday -> Monday
|
|
95
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
|
|
96
|
+
}
|
|
97
|
+
else if (day === 6) { // Saturday -> Friday
|
|
98
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
|
|
99
|
+
}
|
|
100
|
+
return date;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get all US market holidays for a year
|
|
104
|
+
*/
|
|
105
|
+
function getUSMarketHolidays(year) {
|
|
106
|
+
const holidays = [];
|
|
107
|
+
// New Year's Day (January 1, or observed)
|
|
108
|
+
holidays.push(adjustForWeekend(new Date(year, 0, 1)));
|
|
109
|
+
// Martin Luther King Jr. Day (3rd Monday of January)
|
|
110
|
+
holidays.push(getNthWeekdayOfMonth(year, 0, 1, 3));
|
|
111
|
+
// Presidents' Day (3rd Monday of February)
|
|
112
|
+
holidays.push(getNthWeekdayOfMonth(year, 1, 1, 3));
|
|
113
|
+
// Good Friday (2 days before Easter)
|
|
114
|
+
const easter = getEasterSunday(year);
|
|
115
|
+
holidays.push(new Date(easter.getFullYear(), easter.getMonth(), easter.getDate() - 2));
|
|
116
|
+
// Memorial Day (last Monday of May)
|
|
117
|
+
holidays.push(getLastWeekdayOfMonth(year, 4, 1));
|
|
118
|
+
// Juneteenth (June 19, or observed)
|
|
119
|
+
holidays.push(adjustForWeekend(new Date(year, 5, 19)));
|
|
120
|
+
// Independence Day (July 4, or observed)
|
|
121
|
+
holidays.push(adjustForWeekend(new Date(year, 6, 4)));
|
|
122
|
+
// Labor Day (1st Monday of September)
|
|
123
|
+
holidays.push(getNthWeekdayOfMonth(year, 8, 1, 1));
|
|
124
|
+
// Thanksgiving Day (4th Thursday of November)
|
|
125
|
+
holidays.push(getNthWeekdayOfMonth(year, 10, 4, 4));
|
|
126
|
+
// Christmas Day (December 25, or observed)
|
|
127
|
+
holidays.push(adjustForWeekend(new Date(year, 11, 25)));
|
|
128
|
+
return holidays;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Compare two dates by year, month, day only (ignoring time)
|
|
132
|
+
*/
|
|
133
|
+
function isSameDay(date1, date2) {
|
|
134
|
+
return date1.getFullYear() === date2.getFullYear() &&
|
|
135
|
+
date1.getMonth() === date2.getMonth() &&
|
|
136
|
+
date1.getDate() === date2.getDate();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if a date is a US market holiday
|
|
140
|
+
* @param date - Date to check
|
|
141
|
+
* @param market - Market (default: NYSE)
|
|
142
|
+
* @returns True if the date is a market holiday
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* isMarketHoliday(new Date('2024-12-25')); // true (Christmas)
|
|
147
|
+
* isMarketHoliday(new Date('2024-01-02')); // false
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function isMarketHoliday(date, market = 'NYSE') {
|
|
151
|
+
const d = toDate(date);
|
|
152
|
+
const year = d.getFullYear();
|
|
153
|
+
const holidays = getUSMarketHolidays(year);
|
|
154
|
+
return holidays.some(h => isSameDay(d, h));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if a date is a trading day (weekday and not a market holiday)
|
|
158
|
+
* @param date - Date to check
|
|
159
|
+
* @param market - Market (default: NYSE)
|
|
160
|
+
* @returns True if the date is a trading day
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* isTradingDay(new Date('2024-01-15')); // true (Monday)
|
|
165
|
+
* isTradingDay(new Date('2024-01-13')); // false (Saturday)
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export function isTradingDay(date, market = 'NYSE') {
|
|
169
|
+
const d = toDate(date);
|
|
170
|
+
const day = d.getDay();
|
|
171
|
+
// Weekend check
|
|
172
|
+
if (day === 0 || day === 6)
|
|
173
|
+
return false;
|
|
174
|
+
// Holiday check
|
|
175
|
+
return !isMarketHoliday(d, market);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Check if the market is currently open
|
|
179
|
+
* @param date - Date/time to check
|
|
180
|
+
* @param market - Market (default: NYSE)
|
|
181
|
+
* @returns True if market is open at the specified time
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* // Check if NYSE is open now
|
|
186
|
+
* isMarketOpen(new Date(), 'NYSE');
|
|
187
|
+
*
|
|
188
|
+
* // Check specific time
|
|
189
|
+
* isMarketOpen(new Date('2024-01-15T10:30:00-05:00')); // true
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export function isMarketOpen(date, market = 'NYSE') {
|
|
193
|
+
const d = toDate(date);
|
|
194
|
+
// First check if it's a trading day
|
|
195
|
+
if (!isTradingDay(d, market))
|
|
196
|
+
return false;
|
|
197
|
+
const hours = MARKET_HOURS[market];
|
|
198
|
+
// Convert to market timezone for comparison
|
|
199
|
+
// For simplicity, we assume the input is already in market timezone
|
|
200
|
+
// or we compare hours directly
|
|
201
|
+
const hour = d.getHours();
|
|
202
|
+
const minute = d.getMinutes();
|
|
203
|
+
const timeInMinutes = hour * 60 + minute;
|
|
204
|
+
const openInMinutes = hours.open.hour * 60 + hours.open.minute;
|
|
205
|
+
const closeInMinutes = hours.close.hour * 60 + hours.close.minute;
|
|
206
|
+
return timeInMinutes >= openInMinutes && timeInMinutes < closeInMinutes;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get market hours configuration
|
|
210
|
+
* @param market - Market (default: NYSE)
|
|
211
|
+
* @returns Market hours configuration (deep copy)
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* const hours = getMarketHours('NASDAQ');
|
|
216
|
+
* console.log(hours.open); // { hour: 9, minute: 30 }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export function getMarketHours(market = 'NYSE') {
|
|
220
|
+
const source = MARKET_HOURS[market];
|
|
221
|
+
return {
|
|
222
|
+
open: { ...source.open },
|
|
223
|
+
close: { ...source.close },
|
|
224
|
+
timezone: source.timezone,
|
|
225
|
+
preMarket: source.preMarket ? { ...source.preMarket } : undefined,
|
|
226
|
+
afterHours: source.afterHours ? { ...source.afterHours } : undefined
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get market open time for a specific date
|
|
231
|
+
* @param date - Date to get market open for
|
|
232
|
+
* @param market - Market (default: NYSE)
|
|
233
|
+
* @returns Date set to market open time
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const open = getMarketOpen(new Date('2024-01-15'));
|
|
238
|
+
* console.log(open); // 2024-01-15T09:30:00
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
export function getMarketOpen(date, market = 'NYSE') {
|
|
242
|
+
const d = toDate(date);
|
|
243
|
+
const hours = MARKET_HOURS[market];
|
|
244
|
+
const result = new Date(d);
|
|
245
|
+
result.setHours(hours.open.hour, hours.open.minute, 0, 0);
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get market close time for a specific date
|
|
250
|
+
* @param date - Date to get market close for
|
|
251
|
+
* @param market - Market (default: NYSE)
|
|
252
|
+
* @returns Date set to market close time
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* const close = getMarketClose(new Date('2024-01-15'));
|
|
257
|
+
* console.log(close); // 2024-01-15T16:00:00
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
export function getMarketClose(date, market = 'NYSE') {
|
|
261
|
+
const d = toDate(date);
|
|
262
|
+
const hours = MARKET_HOURS[market];
|
|
263
|
+
const result = new Date(d);
|
|
264
|
+
result.setHours(hours.close.hour, hours.close.minute, 0, 0);
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get next market open time after a given date
|
|
269
|
+
* @param after - Start searching after this date
|
|
270
|
+
* @param market - Market (default: NYSE)
|
|
271
|
+
* @returns Next market open date/time
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* // If it's Friday evening, returns Monday 9:30 AM
|
|
276
|
+
* const nextOpen = getNextMarketOpen(new Date('2024-01-12T17:00:00'));
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function getNextMarketOpen(after, market = 'NYSE') {
|
|
280
|
+
const d = toDate(after);
|
|
281
|
+
const hours = MARKET_HOURS[market];
|
|
282
|
+
// Start with current day's open
|
|
283
|
+
let candidate = getMarketOpen(d, market);
|
|
284
|
+
// If we're past today's open, start from tomorrow
|
|
285
|
+
if (d >= candidate) {
|
|
286
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
287
|
+
}
|
|
288
|
+
// Find next trading day
|
|
289
|
+
while (!isTradingDay(candidate, market)) {
|
|
290
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
291
|
+
}
|
|
292
|
+
// Set to market open time
|
|
293
|
+
candidate.setHours(hours.open.hour, hours.open.minute, 0, 0);
|
|
294
|
+
return candidate;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get next market close time after a given date
|
|
298
|
+
* @param after - Start searching after this date
|
|
299
|
+
* @param market - Market (default: NYSE)
|
|
300
|
+
* @returns Next market close date/time
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```ts
|
|
304
|
+
* const nextClose = getNextMarketClose(new Date('2024-01-15T10:00:00'));
|
|
305
|
+
* // Returns 2024-01-15T16:00:00 (same day close)
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
export function getNextMarketClose(after, market = 'NYSE') {
|
|
309
|
+
const d = toDate(after);
|
|
310
|
+
const hours = MARKET_HOURS[market];
|
|
311
|
+
// Start with current day's close
|
|
312
|
+
let candidate = getMarketClose(d, market);
|
|
313
|
+
// If we're past today's close or not a trading day, go to next trading day
|
|
314
|
+
if (d >= candidate || !isTradingDay(d, market)) {
|
|
315
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
316
|
+
while (!isTradingDay(candidate, market)) {
|
|
317
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Set to market close time
|
|
321
|
+
candidate.setHours(hours.close.hour, hours.close.minute, 0, 0);
|
|
322
|
+
return candidate;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Calculate settlement date (T+N) from trade date
|
|
326
|
+
* @param tradeDate - Trade date
|
|
327
|
+
* @param days - Number of business days for settlement (e.g., 1 for T+1, 2 for T+2)
|
|
328
|
+
* @param market - Market (default: NYSE)
|
|
329
|
+
* @returns Settlement date
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```ts
|
|
333
|
+
* // T+2 settlement
|
|
334
|
+
* const settlement = getSettlementDate(new Date('2024-01-15'), 2);
|
|
335
|
+
* // Returns 2024-01-17 (skipping weekends/holidays)
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
export function getSettlementDate(tradeDate, days, market = 'NYSE') {
|
|
339
|
+
const d = toDate(tradeDate);
|
|
340
|
+
const result = new Date(d);
|
|
341
|
+
result.setHours(0, 0, 0, 0);
|
|
342
|
+
let remaining = days;
|
|
343
|
+
while (remaining > 0) {
|
|
344
|
+
result.setDate(result.getDate() + 1);
|
|
345
|
+
if (isTradingDay(result, market)) {
|
|
346
|
+
remaining--;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Calculate trade date from settlement date (reverse T+N)
|
|
353
|
+
* @param settlementDate - Settlement date
|
|
354
|
+
* @param days - Number of business days for settlement
|
|
355
|
+
* @param market - Market (default: NYSE)
|
|
356
|
+
* @returns Trade date
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```ts
|
|
360
|
+
* const tradeDate = getTradeDateFromSettlement(new Date('2024-01-17'), 2);
|
|
361
|
+
* // Returns 2024-01-15
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
export function getTradeDateFromSettlement(settlementDate, days, market = 'NYSE') {
|
|
365
|
+
const d = toDate(settlementDate);
|
|
366
|
+
const result = new Date(d);
|
|
367
|
+
result.setHours(0, 0, 0, 0);
|
|
368
|
+
let remaining = days;
|
|
369
|
+
while (remaining > 0) {
|
|
370
|
+
result.setDate(result.getDate() - 1);
|
|
371
|
+
if (isTradingDay(result, market)) {
|
|
372
|
+
remaining--;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Iterate through each trading day in a range
|
|
379
|
+
* @param start - Start date
|
|
380
|
+
* @param end - End date
|
|
381
|
+
* @param market - Market (default: NYSE)
|
|
382
|
+
* @returns Array of trading days
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* const days = eachTradingDay(new Date('2024-01-15'), new Date('2024-01-19'));
|
|
387
|
+
* // Returns Mon, Tue, Wed, Thu, Fri (if no holidays)
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
export function eachTradingDay(start, end, market = 'NYSE') {
|
|
391
|
+
const startDate = toDate(start);
|
|
392
|
+
const endDate = toDate(end);
|
|
393
|
+
startDate.setHours(0, 0, 0, 0);
|
|
394
|
+
endDate.setHours(0, 0, 0, 0);
|
|
395
|
+
const days = [];
|
|
396
|
+
const current = new Date(startDate);
|
|
397
|
+
while (current <= endDate) {
|
|
398
|
+
if (isTradingDay(current, market)) {
|
|
399
|
+
days.push(new Date(current));
|
|
400
|
+
}
|
|
401
|
+
current.setDate(current.getDate() + 1);
|
|
402
|
+
}
|
|
403
|
+
return days;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Count trading days between two dates
|
|
407
|
+
* @param start - Start date
|
|
408
|
+
* @param end - End date
|
|
409
|
+
* @param market - Market (default: NYSE)
|
|
410
|
+
* @returns Number of trading days
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* const count = countTradingDays(new Date('2024-01-15'), new Date('2024-01-19'));
|
|
415
|
+
* // Returns 5 (Mon-Fri if no holidays)
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
export function countTradingDays(start, end, market = 'NYSE') {
|
|
419
|
+
return eachTradingDay(start, end, market).length;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Add trading days to a date
|
|
423
|
+
* @param date - Start date
|
|
424
|
+
* @param days - Number of trading days to add (can be negative)
|
|
425
|
+
* @param market - Market (default: NYSE)
|
|
426
|
+
* @returns Resulting date
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* const result = addTradingDays(new Date('2024-01-15'), 5);
|
|
431
|
+
* // Returns 5 trading days later
|
|
432
|
+
* ```
|
|
433
|
+
*/
|
|
434
|
+
export function addTradingDays(date, days, market = 'NYSE') {
|
|
435
|
+
const d = toDate(date);
|
|
436
|
+
const result = new Date(d);
|
|
437
|
+
result.setHours(0, 0, 0, 0);
|
|
438
|
+
if (days === 0)
|
|
439
|
+
return result;
|
|
440
|
+
const direction = days > 0 ? 1 : -1;
|
|
441
|
+
let remaining = Math.abs(days);
|
|
442
|
+
while (remaining > 0) {
|
|
443
|
+
result.setDate(result.getDate() + direction);
|
|
444
|
+
if (isTradingDay(result, market)) {
|
|
445
|
+
remaining--;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Get options expiration date
|
|
452
|
+
* @param year - Year
|
|
453
|
+
* @param month - Month (1-12)
|
|
454
|
+
* @param type - Expiration type (default: 'monthly')
|
|
455
|
+
* @returns Options expiration date
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```ts
|
|
459
|
+
* // Monthly options expire on 3rd Friday
|
|
460
|
+
* const exp = getOptionsExpiration(2024, 1, 'monthly');
|
|
461
|
+
*
|
|
462
|
+
* // Weekly options expire every Friday
|
|
463
|
+
* const weekly = getOptionsExpiration(2024, 1, 'weekly');
|
|
464
|
+
*
|
|
465
|
+
* // Quarterly options expire on 3rd Friday of Mar, Jun, Sep, Dec
|
|
466
|
+
* const quarterly = getOptionsExpiration(2024, 3, 'quarterly');
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
export function getOptionsExpiration(year, month, type = 'monthly') {
|
|
470
|
+
// Adjust month to 0-indexed
|
|
471
|
+
const monthIndex = month - 1;
|
|
472
|
+
switch (type) {
|
|
473
|
+
case 'monthly':
|
|
474
|
+
case 'quarterly': {
|
|
475
|
+
// 3rd Friday of the month
|
|
476
|
+
const thirdFriday = getNthWeekdayOfMonth(year, monthIndex, 5, 3);
|
|
477
|
+
// If it's a holiday, move to Thursday
|
|
478
|
+
if (isMarketHoliday(thirdFriday)) {
|
|
479
|
+
thirdFriday.setDate(thirdFriday.getDate() - 1);
|
|
480
|
+
}
|
|
481
|
+
return thirdFriday;
|
|
482
|
+
}
|
|
483
|
+
case 'weekly': {
|
|
484
|
+
// First Friday of the month for weekly
|
|
485
|
+
const firstFriday = getNthWeekdayOfMonth(year, monthIndex, 5, 1);
|
|
486
|
+
// If it's a holiday, move to Thursday
|
|
487
|
+
if (isMarketHoliday(firstFriday)) {
|
|
488
|
+
firstFriday.setDate(firstFriday.getDate() - 1);
|
|
489
|
+
}
|
|
490
|
+
return firstFriday;
|
|
491
|
+
}
|
|
492
|
+
default:
|
|
493
|
+
return getNthWeekdayOfMonth(year, monthIndex, 5, 3);
|
|
494
|
+
}
|
|
495
|
+
}
|