@watsonserve/stock-base 0.0.30 → 0.1.1
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/index.js +19 -9
- package/package.json +1 -1
- package/types/us-holiday.d.ts +6 -0
- package/us-holiday.js +122 -0
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { A_DAY_S } from './close-time.js';
|
|
2
2
|
import { DividendLoader } from './dividend-loader.js';
|
|
3
|
+
import { generateUSHolidays } from './us-holiday.js';
|
|
3
4
|
export * from './close-time.js';
|
|
4
5
|
export * from './stock.js';
|
|
5
6
|
export * from './log.js';
|
|
@@ -7,6 +8,17 @@ export * from './helper.js';
|
|
|
7
8
|
function parseUTCDate(str) {
|
|
8
9
|
return ~~(Date.UTC(+str.slice(0, 4), +str.slice(4, 6) - 1, +str.slice(6, 8)) / 1000);
|
|
9
10
|
}
|
|
11
|
+
const HKDict = Object.entries({
|
|
12
|
+
'The first day of January': 'New Year\'s Day',
|
|
13
|
+
'Lunar New Year’s Day': 'Chinese New Year',
|
|
14
|
+
'Hong Kong Special Administrative Region': 'HK SAR',
|
|
15
|
+
'香港公眾假期 -': '',
|
|
16
|
+
'Hong Kong Public Holidays -': '',
|
|
17
|
+
'The day following': ''
|
|
18
|
+
});
|
|
19
|
+
function simpleTitle(title = '') {
|
|
20
|
+
return HKDict.reduce((str, [k, v]) => str.replace(k, v), title).trim();
|
|
21
|
+
}
|
|
10
22
|
export class StockLoader extends DividendLoader {
|
|
11
23
|
async loadHolidaysSG(year) {
|
|
12
24
|
const resp = await fetch(`https://www.mom.gov.sg/-/media/mom/documents/employment-practices/public-holidays/public-holidays-sg-${year}.ics`);
|
|
@@ -45,20 +57,18 @@ export class StockLoader extends DividendLoader {
|
|
|
45
57
|
lastOne.end = end;
|
|
46
58
|
}
|
|
47
59
|
else {
|
|
48
|
-
|
|
49
|
-
if (title.startsWith('香港公眾假期 - ') || title.startsWith('Hong Kong Public Holidays - ')) {
|
|
50
|
-
title = title.split(' - ')[1];
|
|
51
|
-
}
|
|
52
|
-
pre.push({ market: 'HK', title: title, start, end });
|
|
60
|
+
pre.push({ market: 'HK', title: simpleTitle(ev.get('SUMMARY')), start, end });
|
|
53
61
|
}
|
|
54
62
|
return pre;
|
|
55
63
|
}, []);
|
|
56
64
|
}
|
|
57
65
|
async loadHolidays() {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
this.
|
|
66
|
+
const yyyy = new Date().getUTCFullYear();
|
|
67
|
+
const [holidaysSG, holidaysHK, holidaysUS] = await Promise.all([
|
|
68
|
+
this.loadHolidaysSG(yyyy),
|
|
69
|
+
this.loadHolidaysHK(),
|
|
70
|
+
generateUSHolidays(yyyy)
|
|
61
71
|
]);
|
|
62
|
-
return holidaysSG.concat(holidaysHK);
|
|
72
|
+
return holidaysSG.concat(holidaysHK).concat(holidaysUS);
|
|
63
73
|
}
|
|
64
74
|
}
|
package/package.json
CHANGED
package/us-holiday.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { A_DAY_S } from "./close-time.js";
|
|
2
|
+
// ==================== 工具函数 ====================
|
|
3
|
+
function formatDate(d) {
|
|
4
|
+
const y = d.getFullYear();
|
|
5
|
+
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
6
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
7
|
+
return `${y}${m}${day}`;
|
|
8
|
+
}
|
|
9
|
+
// 获取第N个周X
|
|
10
|
+
function getNthWeekday(y, m, n, weekday) {
|
|
11
|
+
const first = new Date(y, m - 1, 1);
|
|
12
|
+
const diff = (weekday - first.getDay() + 7) % 7;
|
|
13
|
+
const d = new Date(first);
|
|
14
|
+
d.setDate(1 + diff + (n - 1) * 7);
|
|
15
|
+
return d;
|
|
16
|
+
}
|
|
17
|
+
// 获取最后一个周X
|
|
18
|
+
function getLastWeekday(y, m, weekday) {
|
|
19
|
+
const last = new Date(y, m, 0);
|
|
20
|
+
const diff = (last.getDay() - weekday + 7) % 7;
|
|
21
|
+
const d = new Date(last);
|
|
22
|
+
d.setDate(last.getDate() - diff);
|
|
23
|
+
return d;
|
|
24
|
+
}
|
|
25
|
+
function getGoodFriday(y) {
|
|
26
|
+
const a = y % 19;
|
|
27
|
+
const b = Math.floor(y / 100);
|
|
28
|
+
const c = y % 100;
|
|
29
|
+
const d = Math.floor(b / 4);
|
|
30
|
+
const e = b % 4;
|
|
31
|
+
const f = Math.floor((b + 8) / 25);
|
|
32
|
+
const g = Math.floor((b - f + 1) / 3);
|
|
33
|
+
const h = (19 * a + b - d - g + 15) % 30;
|
|
34
|
+
const i = Math.floor(c / 4);
|
|
35
|
+
const k = c % 4;
|
|
36
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
37
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
38
|
+
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
39
|
+
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
40
|
+
const easter = new Date(y, month - 1, day);
|
|
41
|
+
const goodFriday = new Date(easter);
|
|
42
|
+
goodFriday.setDate(easter.getDate() - 2);
|
|
43
|
+
return goodFriday;
|
|
44
|
+
}
|
|
45
|
+
function createEvent(uid, dateStr, summary, description) {
|
|
46
|
+
return `BEGIN:VEVENT
|
|
47
|
+
UID:${uid}-${dateStr}@usstockmarket
|
|
48
|
+
DTSTART;VALUE=DATE:${dateStr}
|
|
49
|
+
DTEND;VALUE=DATE:${dateStr}
|
|
50
|
+
SUMMARY:${summary}
|
|
51
|
+
DESCRIPTION:${description}
|
|
52
|
+
STATUS:CONFIRMED
|
|
53
|
+
END:VEVENT
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
function toUTCs(d) {
|
|
57
|
+
return ~~(Date.UTC(d.getFullYear(), d.getMonth() - 1, d.getDate()) / 1000);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 生成美股休市 & 提前收盘日历(ICS格式)
|
|
61
|
+
* @param year 年份 如 2025
|
|
62
|
+
* @returns ics 文本,可直接保存为 .ics 文件
|
|
63
|
+
*/
|
|
64
|
+
export function generateUSHolidays(year) {
|
|
65
|
+
const events = [];
|
|
66
|
+
// 1. 元旦(周六不补,周日补周一)
|
|
67
|
+
const newYear = new Date(year, 0, 1);
|
|
68
|
+
if (6 !== newYear.getDay()) {
|
|
69
|
+
const start = ~~(Date.UTC(year, 0, newYear.getDay() === 0 ? 2 : 1) / 1000);
|
|
70
|
+
events.push({ title: 'New Year\'s Day', start, end: start + A_DAY_S });
|
|
71
|
+
}
|
|
72
|
+
// 2. 马丁路德金纪念日(1月第3个周一)
|
|
73
|
+
const mlkDay = toUTCs(getNthWeekday(year, 1, 3, 1));
|
|
74
|
+
events.push({ title: 'Martin Luther King Jr. Day', start: mlkDay, end: mlkDay + A_DAY_S });
|
|
75
|
+
// 3. 总统日(2月第3个周一)
|
|
76
|
+
const presidentsDay = toUTCs(getNthWeekday(year, 2, 3, 1));
|
|
77
|
+
events.push({ title: 'Presidents\' Day', start: presidentsDay, end: presidentsDay + A_DAY_S });
|
|
78
|
+
// 4. 耶稣受难日(复活节前周五,固定算法)
|
|
79
|
+
const goodFriday = toUTCs(getGoodFriday(year));
|
|
80
|
+
events.push({ title: 'Good Friday', start: goodFriday, end: goodFriday + A_DAY_S });
|
|
81
|
+
// 5. 阵亡将士纪念日(5月最后一个周一)
|
|
82
|
+
const memorialDay = toUTCs(getLastWeekday(year, 5, 1));
|
|
83
|
+
events.push({ title: 'Memorial Friday', start: memorialDay, end: memorialDay + A_DAY_S });
|
|
84
|
+
// 6. 六月节(6.19,周六不补,周日补周一)
|
|
85
|
+
const juneteenth = new Date(year, 5, 19);
|
|
86
|
+
if (6 !== juneteenth.getDay()) {
|
|
87
|
+
const jtDate = ~~(Date.UTC(year, 5, juneteenth.getDay() === 0 ? 20 : 19) / 1000);
|
|
88
|
+
events.push({ title: 'Juneteenth National Independence Day', start: jtDate, end: jtDate + A_DAY_S });
|
|
89
|
+
}
|
|
90
|
+
// 7. 独立日(7.4,周六补周五,周日补周一)
|
|
91
|
+
const independenceDay = new Date(year, 6, 4);
|
|
92
|
+
let indDate = Date.UTC(year, 6, 4);
|
|
93
|
+
if (independenceDay.getDay() === 6)
|
|
94
|
+
indDate = Date.UTC(year, 6, 3);
|
|
95
|
+
if (independenceDay.getDay() === 0)
|
|
96
|
+
indDate = Date.UTC(year, 6, 5);
|
|
97
|
+
indDate = ~~(indDate / 1000);
|
|
98
|
+
events.push({ title: 'Independence Day', start: indDate, end: indDate + A_DAY_S });
|
|
99
|
+
// 8. 劳动节(9月第一个周一)
|
|
100
|
+
const laborDay = toUTCs(getNthWeekday(year, 9, 1, 1));
|
|
101
|
+
events.push({ title: 'Labor Day', start: laborDay, end: laborDay + A_DAY_S });
|
|
102
|
+
// 9. 感恩节(11月第4个周四)
|
|
103
|
+
const thanksgiving = toUTCs(getNthWeekday(year, 11, 4, 4));
|
|
104
|
+
events.push({ title: 'Thanksgiving Day And Black Friday', start: thanksgiving, end: thanksgiving + (A_DAY_S << 1) });
|
|
105
|
+
// 10. 圣诞节(12.25,周六补周五,周日补周一)
|
|
106
|
+
const christmasDay = new Date(year, 11, 25);
|
|
107
|
+
let xmasDate = Date.UTC(year, 11, 25);
|
|
108
|
+
let title = 'Christmas Day';
|
|
109
|
+
let during = 0;
|
|
110
|
+
switch (christmasDay.getDay()) {
|
|
111
|
+
case 0:
|
|
112
|
+
xmasDate = Date.UTC(year, 11, 26);
|
|
113
|
+
case 1:
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
title = 'Christmas Eve & Christmas Day';
|
|
117
|
+
xmasDate = Date.UTC(year, 11, 24);
|
|
118
|
+
during = 1;
|
|
119
|
+
}
|
|
120
|
+
events.push({ title, start: xmasDate, end: xmasDate + (A_DAY_S << during) });
|
|
121
|
+
return events;
|
|
122
|
+
}
|