mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-08-13 04:28:04 +00:00
feat: calendrier économique TE+Investing, OIS SNB, yields 10Y, taux directeurs corrects
Calendrier économique - Nouvelles sources : TE HTML scraping (483 events/2 sem.) + Investing.com en co-primaires - Dédupe déterministe par clé currency+category+date (±1j pour policy_rate) - Filtre catégorie "other", filtre discours CB (fix doublon TE vs Investing) - CHF/Switzerland via page séparée TE (hors G20) - Fenêtre 14 jours avec ?startDate=&endDate= passés à TE - ForexFactory+FRED en secours uniquement si les deux scrapers tombent à vide Probabilités OIS - rateprobability.com (7 CBs : FED/ECB/BOJ/BOE/BOC/RBA/RBNZ) - investinglive.com (Dellamotta) : scan URL-date 14 jours → couvre SNB/CHF manquant - SNB : 3% probabilité hausse au 19 juin 2026 (source : IL article 2026-05-29) - rate_expectations.json MAJ : RBNZ 79% hike (corrigé depuis 70% no-change stale) - Dédup ±1 jour pour policy_rate (fix doublon SNB TE-18/06 vs IL-19/06) Taux directeurs - USD : scrapeTeRate() = 3.75% (fin midpoint FRED 3.625%) - AUD prev : 4.10% (corrigé depuis 4.60%) - NZD/CHF/JPY/CAD : cohérents avec TE officiel Yields 10Y souverains - Nouvelle lib lib/tebonds.ts : scrape TE bonds, données du jour, cache 1h - Remplace FRED IRLTLT01XXM156N mensuel (JPY/CHF/AUD/NZD avaient 1 mois de retard) - 8 devises + spread vs USD calculé automatiquement - /api/yields et /api/drivers utilisent la même lib Drivers globaux - BTC : ticker/price Binance (plus précis que 24hr lastPrice) - HY Spread cache : 86400s → 3600s (données FRED du jour) - DriversBar : suppression affichage direct US 10Y, tooltip Crb 2-10 affiche US10Y+US2Y Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3a39904ce5
commit
eb416be780
+31
-80
@@ -1,95 +1,46 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { fetchTEBondYields } from "@/lib/tebonds";
|
||||
|
||||
// 10Y sovereign yields — mixed sources per CDC §6.4
|
||||
// USD: FRED DGS10 (daily)
|
||||
// EUR: ECB API (daily Bund)
|
||||
// GBP: BoE API IUDMNPY (daily)
|
||||
// Others: FRED monthly as fallback
|
||||
|
||||
const FRED_KEY = () => process.env.FRED_API_KEY ?? "";
|
||||
|
||||
async function fredObs(series: string): Promise<number | null> {
|
||||
try {
|
||||
const url = `https://api.stlouisfed.org/fred/series/observations?series_id=${series}&api_key=${FRED_KEY()}&file_type=json&sort_order=desc&limit=3`;
|
||||
const res = await fetch(url, { next: { revalidate: 86400 } });
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const val = (data?.observations ?? []).find((o: { value: string }) => o.value !== ".")?.value;
|
||||
return val ? parseFloat(val) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function ecbBund10Y(): Promise<number | null> {
|
||||
try {
|
||||
const url = "https://data-api.ecb.europa.eu/service/data/YC/B.U2.EUR.4F.G_N_A.SV_C_YM.SR_10Y?format=jsondata&lastNObservations=1";
|
||||
const res = await fetch(url, { next: { revalidate: 86400 } });
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const obs = data?.dataSets?.[0]?.series?.["0:0:0:0:0:0:0"]?.observations;
|
||||
if (!obs) return null;
|
||||
const last = Object.values(obs).at(-1) as number[] | undefined;
|
||||
return last?.[0] ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function boeGilt10Y(): Promise<number | null> {
|
||||
try {
|
||||
// BoE API series IUDMNPY = UK Nominal Par Yield 10Y
|
||||
const url = "https://www.bankofengland.co.uk/boeapps/database/_iadb-FromShowColumns.asp?csv.x=yes&Datefrom=01/Jan/2024&Dateto=now&SeriesCodes=IUDMNPY&CSVF=TN&UsingCodes=Y";
|
||||
const res = await fetch(url, { next: { revalidate: 86400 } });
|
||||
if (!res.ok) return null;
|
||||
const text = await res.text();
|
||||
const lines = text.trim().split("\n").filter((l) => l.trim());
|
||||
const last = lines.at(-1)?.split(",");
|
||||
const val = last?.at(-1)?.trim();
|
||||
return val ? parseFloat(val) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function bocYield10Y(): Promise<number | null> {
|
||||
try {
|
||||
const url = "https://www.bankofcanada.ca/valet/observations/BD.CDN.10YR.DQ.YLD/json?recent=5";
|
||||
const res = await fetch(url, { next: { revalidate: 86400 } });
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const obs: { d: string; "BD.CDN.10YR.DQ.YLD": { v: string } }[] =
|
||||
data?.observations ?? [];
|
||||
const last = obs.findLast((o) => o["BD.CDN.10YR.DQ.YLD"]?.v);
|
||||
return last ? parseFloat(last["BD.CDN.10YR.DQ.YLD"].v) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 10Y sovereign yields — source unique : tradingeconomics.com/bonds (HTML statique)
|
||||
// Remplace les sources précédentes (FRED DGS10 + IRLTLT01XXM156N mensuel + ECB/BoE APIs)
|
||||
// qui avaient des décalages allant de 1 jour (FRED daily) à 1 mois (FRED monthly JPY/CHF/AUD/NZD).
|
||||
// TE bonds = données du jour pour les 8 devises, cache 1h.
|
||||
|
||||
export async function GET() {
|
||||
const [usd, eur, gbp, jpy, chf, cad, aud, nzd] = await Promise.all([
|
||||
fredObs("DGS10"),
|
||||
ecbBund10Y(),
|
||||
boeGilt10Y(),
|
||||
fredObs("IRLTLT01JPM156N"),
|
||||
fredObs("IRLTLT01CHM156N"),
|
||||
bocYield10Y(),
|
||||
fredObs("IRLTLT01AUM156N"),
|
||||
fredObs("IRLTLT01NZM156N"),
|
||||
]);
|
||||
const bondData = await fetchTEBondYields();
|
||||
|
||||
const yields = { USD: usd, EUR: eur, GBP: gbp, JPY: jpy, CHF: chf, CAD: cad, AUD: aud, NZD: nzd };
|
||||
const yields: Record<string, number | null> = {
|
||||
USD: bondData.USD?.yield10y ?? null,
|
||||
EUR: bondData.EUR?.yield10y ?? null,
|
||||
GBP: bondData.GBP?.yield10y ?? null,
|
||||
JPY: bondData.JPY?.yield10y ?? null,
|
||||
CHF: bondData.CHF?.yield10y ?? null,
|
||||
CAD: bondData.CAD?.yield10y ?? null,
|
||||
AUD: bondData.AUD?.yield10y ?? null,
|
||||
NZD: bondData.NZD?.yield10y ?? null,
|
||||
};
|
||||
|
||||
// Compute spreads vs USD
|
||||
const dayDeltas: Record<string, number | null> = {
|
||||
USD: bondData.USD?.dayDelta ?? null,
|
||||
EUR: bondData.EUR?.dayDelta ?? null,
|
||||
GBP: bondData.GBP?.dayDelta ?? null,
|
||||
JPY: bondData.JPY?.dayDelta ?? null,
|
||||
CHF: bondData.CHF?.dayDelta ?? null,
|
||||
CAD: bondData.CAD?.dayDelta ?? null,
|
||||
AUD: bondData.AUD?.dayDelta ?? null,
|
||||
NZD: bondData.NZD?.dayDelta ?? null,
|
||||
};
|
||||
|
||||
// Spread vs USD (bps)
|
||||
const usd = yields.USD;
|
||||
const spreads: Record<string, number | null> = {};
|
||||
for (const [ccy, yld] of Object.entries(yields)) {
|
||||
if (ccy === "USD" || yld === null || usd === null) {
|
||||
spreads[ccy] = null;
|
||||
} else {
|
||||
spreads[ccy] = Math.round((yld - usd) * 100); // bps
|
||||
spreads[ccy] = Math.round((yld - usd) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ yields, spreads, timestamp: Date.now() });
|
||||
return NextResponse.json({ yields, spreads, dayDeltas, timestamp: Date.now() });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user