finance-dashboard/lib/calendar-market.js

197 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 市場結構日選擇權結算、休市、Jackson Hole、主要央行利率決議靜態日程每年可更新
function iso(y, m, d) {
return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
}
function inRange(date, start, end) {
return date >= start && date <= end;
}
function addEvent(events, ev) {
if (!ev?.date || !ev?.title) return;
events.push({
time: ev.time || '',
impact: ev.impact || 'low',
category: ev.category || 'macro',
source: ev.source || 'Market calendar',
symbol: null,
url: ev.url || null,
note: ev.note || '',
...ev,
});
}
function thirdFridayISO(year, monthIndex) {
const dow = new Date(Date.UTC(year, monthIndex, 1)).getUTCDay();
const day = 1 + ((5 - dow + 7) % 7) + 14;
return iso(year, monthIndex, day);
}
const QUAD_MONTHS = new Set([2, 5, 8, 11]);
export function fetchOptionsExpiryEvents(start, end) {
const events = [];
const y0 = Number(start.slice(0, 4));
const y1 = Number(end.slice(0, 4));
for (let y = y0; y <= y1; y++) {
for (let mo = 0; mo < 12; mo++) {
const date = thirdFridayISO(y, mo);
if (!inRange(date, start, end)) continue;
if (QUAD_MONTHS.has(mo)) continue; // 四巫日由 calendar.js 另列
addEvent(events, {
date,
time: '16:00 美東',
title: '月選擇權結算',
category: 'derivatives',
impact: 'medium',
note: '每月第三個週五,個股與指數選擇權到期,換倉時波動可能加大',
});
}
}
return events;
}
function nthWeekday(year, monthIndex, weekday, n) {
let count = 0;
for (let day = 1; day <= 31; day++) {
const d = new Date(Date.UTC(year, monthIndex, day));
if (d.getUTCMonth() !== monthIndex) break;
if (d.getUTCDay() !== weekday) continue;
count++;
if (count === n) return iso(year, monthIndex, day);
}
return null;
}
function lastWeekday(year, monthIndex, weekday) {
for (let day = 31; day >= 1; day--) {
const d = new Date(Date.UTC(year, monthIndex, day));
if (d.getUTCMonth() !== monthIndex) continue;
if (d.getUTCDay() === weekday) return iso(year, monthIndex, day);
}
return null;
}
function observedFixed(year, monthIndex, day) {
const d = new Date(Date.UTC(year, monthIndex, day));
const dow = d.getUTCDay();
if (dow === 6) return iso(year, monthIndex, day - 1);
if (dow === 0) return iso(year, monthIndex, day + 1);
return iso(year, monthIndex, day);
}
function easterSunday(year) {
const a = year % 19;
const b = Math.floor(year / 100);
const c = year % 100;
const d = Math.floor(b / 4);
const e = c % 4;
const f = Math.floor((b + 8) / 25);
const g = Math.floor((b - f + 1) / 3);
const h = (19 * a + b - d - g + 15) % 30;
const i = Math.floor(c / 4);
const k = c % 4;
const l = (32 + 2 * e + 2 * i - h - k) % 7;
const m = Math.floor((a + 11 * h + 22 * l) / 451);
const month = Math.floor((h + l - 7 * m + 114) / 31) - 1;
const day = ((h + l - 7 * m + 114) % 31) + 1;
return iso(year, month, day);
}
function goodFriday(year) {
const e = easterSunday(year);
const d = new Date(e + 'T00:00:00Z');
d.setUTCDate(d.getUTCDate() - 2);
return d.toISOString().slice(0, 10);
}
export function fetchUsMarketHolidayEvents(start, end) {
const events = [];
const y0 = Number(start.slice(0, 4));
const y1 = Number(end.slice(0, 4));
for (let y = y0; y <= y1; y++) {
const holidays = [
[observedFixed(y, 0, 1), '美股休市:元旦', '全美主要交易所休市,流動性偏低'],
[nthWeekday(y, 0, 1, 3), '美股休市:馬丁路德金恩日', ''],
[nthWeekday(y, 1, 1, 3), '美股休市:總統日', ''],
[goodFriday(y), '美股休市Good Friday', '復活節前一週五,歐美常同步休市'],
[lastWeekday(y, 4, 1), '美股休市:陣亡將士紀念日', ''],
[observedFixed(y, 5, 19), '美股休市Juneteenth', ''],
[observedFixed(y, 6, 4), '美股休市:獨立紀念日', ''],
[nthWeekday(y, 8, 1, 1), '美股休市:勞動節', ''],
[nthWeekday(y, 10, 4, 4), '美股休市:感恩節', ''],
[observedFixed(y, 11, 25), '美股休市:聖誕節', ''],
];
for (const [date, title, note] of holidays) {
if (!date || !inRange(date, start, end)) continue;
addEvent(events, {
date,
title,
category: 'market',
impact: 'medium',
note: note || '交易所休市或提早收盤,留意流動性',
});
}
const jackson = lastWeekday(y, 7, 5);
if (jackson && inRange(jackson, start, end)) {
addEvent(events, {
date: jackson,
time: '美東日間',
title: 'Jackson Hole 全球央行年會',
category: 'fed',
impact: 'high',
note: 'Fed 主席與各國央行官員演講,常影響利率與風險資產預期',
});
}
}
return events;
}
// 官方已公布的 2026 利率決議日(超出範圍者會被 start/end 自動濾掉)
const CENTRAL_BANK_MEETINGS = [
{ date: '2026-02-05', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-03-19', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-04-30', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-06-11', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-07-23', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-09-10', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-10-29', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-12-17', title: 'ECB 利率決議', source: 'ECB', impact: 'high', note: '歐洲央行貨幣政策決議與記者會' },
{ date: '2026-01-24', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議,影響日圓與亞股' },
{ date: '2026-03-19', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-05-01', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-06-17', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-07-31', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-09-19', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-10-30', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-12-19', title: '日本央行 利率決議', source: 'BOJ', impact: 'high', note: '日本銀行貨幣政策決議' },
{ date: '2026-02-05', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-03-19', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-05-07', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-06-18', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-08-06', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-09-17', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-11-05', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
{ date: '2026-12-17', title: '英央行 MPC 利率決議', source: 'BOE', impact: 'high', note: '英格蘭銀行貨幣政策委員會決議' },
];
export function fetchCentralBankEvents(start, end) {
const events = [];
for (const row of CENTRAL_BANK_MEETINGS) {
if (!inRange(row.date, start, end)) continue;
addEvent(events, {
date: row.date,
time: '依各國時間',
title: row.title,
category: 'central_bank',
impact: row.impact,
source: row.source,
note: row.note,
});
}
return events;
}
export function fetchMarketStructureEvents(start, end) {
return [
...fetchOptionsExpiryEvents(start, end),
...fetchUsMarketHolidayEvents(start, end),
...fetchCentralBankEvents(start, end),
];
}