mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-07-27 20:37:45 +00:00
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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 yields = { USD: usd, EUR: eur, GBP: gbp, JPY: jpy, CHF: chf, CAD: cad, AUD: aud, NZD: nzd };
|
|
|
|
// Compute spreads vs 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
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ yields, spreads, timestamp: Date.now() });
|
|
}
|