finance-dashboard/lib/calendar-fred.js

70 lines
3.6 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.

// FRED 發布日程:補足 BLS/BEA 未涵蓋的重要美國總經零售、房市、ADP、初領失業金等
const UA = 'finance-dashboard/1.0';
const FRED_RELEASES = [
{ id: 9, title: '零售銷售', impact: 'medium', time: '08:30 美東', note: '全美零售與餐飲銷售,反映內需動能' },
{ id: 13, title: '工業生產 / 產能利用率', impact: 'medium', time: '09:15 美東', note: '工廠產出與產能使用率,景氣先行指標' },
{ id: 91, title: '密西根消費者信心', impact: 'medium', time: '10:00 美東', note: '消費者對景氣與通膨的預期,影響風險偏好' },
{ id: 27, title: '新屋開工 / 營建許可', impact: 'medium', time: '08:30 美東', note: '住宅建築活動,利率敏感指標' },
{ id: 291, title: '成屋銷售', impact: 'medium', time: '10:00 美東', note: '既有住宅成交,觀察房市需求' },
{ id: 95, title: '耐久財 / 工廠接單出貨', impact: 'medium', time: '08:30 美東', note: '企業資本支出與製造需求' },
{ id: 14, title: '消費信貸', impact: 'low', time: '15:00 美東', note: '家庭與信用卡借款,觀察消費支撐' },
{ id: 180, title: '初領失業救濟金', impact: 'medium', time: '08:30 美東', note: '每週勞動市場溫度,突增常引發避險' },
{ id: 194, title: 'ADP 私部門就業', impact: 'medium', time: '08:15 美東', note: '非農公布前的私部門就業參考' },
{ id: 351, title: '費城 Fed 製造業指數', impact: 'medium', time: '08:30 美東', note: '製造業景氣調查PMI 類指標' },
{ id: 352, title: '非製造業景氣調查', impact: 'medium', time: '10:00 美東', note: '服務業活動,占美國經濟比重大' },
{ id: 219, title: '芝加哥 Fed 全國活動指數', impact: 'low', time: '08:30 美東', note: '綜合 85 項經濟指標的月度摘要' },
{ id: 11, title: '就業成本指數 ECI', impact: 'medium', time: '08:30 美東', note: '薪資與福利成本Fed 關注的通膨壓力' },
{ id: 16, title: '生產力與單位成本', impact: 'medium', time: '08:30 美東', note: '企業效率與人工成本,影響獲利與通膨' },
];
function getFredKey() {
const key = process.env.FRED_API_KEY;
if (!key || key === 'your_fred_api_key_here') return null;
return key;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function fetchReleaseDates(releaseId, key) {
const url = `https://api.stlouisfed.org/fred/release/dates?release_id=${releaseId}` +
`&include_release_dates_with_no_data=true&limit=100&sort_order=desc&api_key=${key}&file_type=json`;
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(url, { headers: { 'User-Agent': UA } });
if (res.status === 429) {
await sleep(800 * (attempt + 1));
continue;
}
if (!res.ok) throw new Error(`FRED release ${releaseId} HTTP ${res.status}`);
const data = await res.json();
return (data.release_dates || []).map((row) => row.date);
}
throw new Error(`FRED release ${releaseId} rate limited`);
}
export async function fetchFredMacroEvents(start, end) {
const key = getFredKey();
if (!key) return [];
const events = [];
for (const rel of FRED_RELEASES) {
try {
const dates = await fetchReleaseDates(rel.id, key);
for (const date of dates) {
if (date < start || date > end) continue;
events.push({
date,
time: rel.time,
title: rel.title,
category: 'macro',
impact: rel.impact,
source: 'FRED releases',
note: rel.note,
});
}
} catch {
// 單一來源失敗不阻擋整體日曆
}
await sleep(300);
}
return events;
}