mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-07-27 20:37:45 +00:00
0657b50c07
Central bank governance (nouvel onglet Banques centrales) : - lib/centralBankGovernance.ts + app/api/central-bank-sources : scraping live du vote de la derniere reunion, dot plot Fed (SEP), et desormais les previsions macro (PIB + inflation) publiees par chaque BC elle-meme (Fed SEP, Eurosystem staff projections, BoJ Outlook Report PDF, SNB conditional forecast, BoC MPR, RBA SMP). GBP/NZD laisses honnetement vides quand aucune source chiffree fiable n'est accessible (RBNZ bloque par Cloudflare). - components/CentralBankSourcesTab.tsx : nouvel onglet avec cards par banque. Taux directeurs + Money Supply M3 : - data/rate_decisions.json corrige (JPY, EUR, NZD etc. etaient perimes d'1-2 decisions) et desormais auto-maintenu : .github/workflows/update-rate-decisions.yml (horaire) detecte les changements de taux via Trading Economics et fait glisser current -> prev sans perte de donnee. - data/money-supply-m3.json (nouveau) + .github/workflows/fetch-money-supply.yml (hebdo) : M3 par devise (proxy M2 pour l'USD, la Fed ne publiant plus M3 depuis 2006), affiche dans CurrencyCard. Calendrier economique elargi : - lib/calendar-countries.ts, lib/calendar-taxonomy.ts, lib/fxstreetCalendar.ts : couverture pays elargie + classification des evenements + source FXStreet. Fix InvestingLive : - lib/investinglive.ts : l'ancienne API WordPress (wp-json) renvoyait 404 depuis leur migration Nuxt.js -> reecrit vers api.investinglive.com/api/homepage/articles, + fix crash silencieux (Tldr pas toujours un tableau). Divers : - .vercel/ ajoute au .gitignore (ne doit jamais etre commite, cf. son propre README). - scripts/ (lancement PWA, push env Vercel), captures d'ecran, cache InvestingLive. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
123 lines
4.6 KiB
JavaScript
123 lines
4.6 KiB
JavaScript
// Détecte automatiquement les changements de taux directeur (Trading Economics)
|
|
// et fait glisser l'ancien "current" vers "prev" — sans perte de donnée, sans
|
|
// intervention manuelle.
|
|
//
|
|
// Principe (demandé explicitement) :
|
|
// - Si le taux scrapé == data/rate_decisions.json[ccy].current → rien ne bouge
|
|
// (le "prev" existant reste tel quel, ce n'est pas une nouvelle décision).
|
|
// - Si le taux scrapé != .current → c'est une nouvelle décision de la banque
|
|
// centrale : l'ancien "current" glisse vers "prev", le nouveau taux devient
|
|
// "current". Le "source" de cette devise est réécrit pour tracer le switch.
|
|
// - Si le scrape échoue (page indisponible / format changé) → on garde les
|
|
// valeurs existantes telles quelles, aucune perte de données.
|
|
//
|
|
// Source : meta description de https://tradingeconomics.com/{slug}/interest-rate
|
|
// Format observé : "The benchmark interest rate in/In {Country} was last
|
|
// recorded at {value} percent."
|
|
// Note EUR : TE reporte directement le taux MRO sur cette page (2.40% le
|
|
// 2026-07-08), cohérent avec la convention MRO déjà utilisée dans ce fichier.
|
|
|
|
import { writeFileSync, readFileSync, mkdirSync } from "fs";
|
|
|
|
const CHROME_HEADERS = {
|
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
"Accept-Language": "en-US,en;q=0.9",
|
|
"Cache-Control": "no-cache",
|
|
"Pragma": "no-cache",
|
|
};
|
|
|
|
const TE_SLUGS = {
|
|
USD: "united-states",
|
|
EUR: "euro-area",
|
|
GBP: "united-kingdom",
|
|
JPY: "japan",
|
|
CHF: "switzerland",
|
|
CAD: "canada",
|
|
AUD: "australia",
|
|
NZD: "new-zealand",
|
|
};
|
|
|
|
const CB_NAME = {
|
|
USD: "Fed (FOMC)", EUR: "BCE", GBP: "BoE MPC", JPY: "BoJ",
|
|
CHF: "SNB", CAD: "BoC", AUD: "RBA", NZD: "RBNZ",
|
|
};
|
|
|
|
async function fetchTERate(slug) {
|
|
try {
|
|
const res = await fetch(`https://tradingeconomics.com/${slug}/interest-rate`, { headers: CHROME_HEADERS });
|
|
if (!res.ok) return null;
|
|
const html = await res.text();
|
|
const meta = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']+)["']/i)
|
|
?? html.match(/<meta[^>]*content=["']([^"']+)["'][^>]*name=["']description["']/i);
|
|
if (!meta) return null;
|
|
const m = meta[1].match(/at\s+([\d.]+)\s*percent/i);
|
|
return m ? parseFloat(m[1]) : null;
|
|
} catch { return null; }
|
|
}
|
|
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
|
|
let existing;
|
|
try {
|
|
existing = JSON.parse(readFileSync("data/rate_decisions.json", "utf8"))[0];
|
|
} catch {
|
|
console.error("✗ Impossible de lire data/rate_decisions.json existant, abandon.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const decisions = { ...existing.decisions };
|
|
let changed = false;
|
|
const changeLog = [];
|
|
|
|
for (const [ccy, slug] of Object.entries(TE_SLUGS)) {
|
|
const scraped = await fetchTERate(slug);
|
|
const prevEntry = decisions[ccy];
|
|
|
|
if (scraped === null) {
|
|
console.log(`[TE] ${ccy} ✗ scrape échoué → conservé (${prevEntry?.current ?? "?"}%)`);
|
|
await new Promise(r => setTimeout(r, 800));
|
|
continue;
|
|
}
|
|
|
|
if (!prevEntry) {
|
|
// Devise jamais vue — initialisation sans historique de "prev"
|
|
decisions[ccy] = {
|
|
current: scraped,
|
|
prev: scraped,
|
|
source: `${CB_NAME[ccy] ?? ccy} — initialisation automatique via Trading Economics (${slug}/interest-rate), pas d'historique disponible pour "prev".`,
|
|
};
|
|
changed = true;
|
|
changeLog.push(`${ccy}: init @ ${scraped}%`);
|
|
console.log(`[TE] ${ccy} ⊕ init ${scraped}%`);
|
|
} else if (Math.abs(scraped - prevEntry.current) > 0.001) {
|
|
// Changement détecté : l'ancien "current" glisse vers "prev"
|
|
decisions[ccy] = {
|
|
current: scraped,
|
|
prev: prevEntry.current,
|
|
source: `${CB_NAME[ccy] ?? ccy} — changement détecté automatiquement le ${today} via Trading Economics (${slug}/interest-rate) : ${prevEntry.current}% → ${scraped}%.`,
|
|
};
|
|
changed = true;
|
|
changeLog.push(`${ccy}: ${prevEntry.current}% → ${scraped}% (prev glisse)`);
|
|
console.log(`[TE] ${ccy} ⚡ CHANGEMENT ${prevEntry.current}% → ${scraped}%`);
|
|
} else {
|
|
console.log(`[TE] ${ccy} = ${scraped}% (inchangé)`);
|
|
}
|
|
|
|
await new Promise(r => setTimeout(r, 800)); // évite le rate-limiting TE
|
|
}
|
|
|
|
if (!changed) {
|
|
console.log("\nAucun changement de taux directeur détecté — fichier inchangé.");
|
|
process.exit(0);
|
|
}
|
|
|
|
mkdirSync("data", { recursive: true });
|
|
writeFileSync("data/rate_decisions.json", JSON.stringify([{
|
|
updated_at: today,
|
|
note: existing.note,
|
|
decisions,
|
|
}], null, 2) + "\n");
|
|
|
|
console.log(`\n✓ Mis à jour : ${changeLog.join(" | ")}`);
|