mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-08-25 02:18:07 +00:00
Fix gold/silver: switch from invalid FRED series to Stooq real-time API
FRED series GOLDPMGBD228NLBM and SLVPRUSD do not exist on the free tier. Replace with Stooq XAU/USD and XAG/USD free CSV endpoint (no API key needed). Delta = intraday change vs session open. Cache 5 min on Vercel. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ef45a30f2c
commit
ac6ad0da60
+47
-17
@@ -1,7 +1,7 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
// ── FRED (toutes données marché + macro) ──────────────────────────────────────
|
// ── FRED (macro + taux + spread) ──────────────────────────────────────────────
|
||||||
// limit=1 → valeur seule. limit=2 → valeur + précédente (pour calculer le delta).
|
// limit=1 → valeur seule. limit=10 → filtre les "." pour trouver la dernière valeur valide.
|
||||||
|
|
||||||
async function fredObs(series: string, apiKey: string): Promise<number | null> {
|
async function fredObs(series: string, apiKey: string): Promise<number | null> {
|
||||||
try {
|
try {
|
||||||
@@ -17,7 +17,7 @@ async function fredObs(series: string, apiKey: string): Promise<number | null> {
|
|||||||
|
|
||||||
type FredResult = { value: number | null; delta: number | null; deltaPct: number | null };
|
type FredResult = { value: number | null; delta: number | null; deltaPct: number | null };
|
||||||
|
|
||||||
/** Fetche les 2 dernières obs pour calculer valeur + delta vs session précédente */
|
/** Fetche les 10 dernières obs pour calculer valeur + delta vs session précédente */
|
||||||
async function fredObsDelta(series: string, apiKey: string): Promise<FredResult> {
|
async function fredObsDelta(series: string, apiKey: string): Promise<FredResult> {
|
||||||
const empty: FredResult = { value: null, delta: null, deltaPct: null };
|
const empty: FredResult = { value: null, delta: null, deltaPct: null };
|
||||||
try {
|
try {
|
||||||
@@ -36,6 +36,31 @@ async function fredObsDelta(series: string, apiKey: string): Promise<FredResult>
|
|||||||
} catch { return empty; }
|
} catch { return empty; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Stooq (Or XAU/USD, Argent XAG/USD — gratuit, sans clé, quasi temps réel) ─
|
||||||
|
// delta = Close - Open = variation intraday vs ouverture de session
|
||||||
|
|
||||||
|
async function stooqMetal(symbol: string): Promise<FredResult> {
|
||||||
|
const empty: FredResult = { value: null, delta: null, deltaPct: null };
|
||||||
|
try {
|
||||||
|
const url = `https://stooq.com/q/l/?s=${symbol}&f=sd2t2ohlcv&h&e=csv`;
|
||||||
|
const res = await fetch(url, { next: { revalidate: 300 } }); // cache 5 min
|
||||||
|
if (!res.ok) return empty;
|
||||||
|
const text = await res.text();
|
||||||
|
const lines = text.trim().split("\n");
|
||||||
|
if (lines.length < 2) return empty;
|
||||||
|
const cols = lines[1].split(",");
|
||||||
|
// CSV header: Symbol,Date,Time,Open,High,Low,Close,Volume
|
||||||
|
const open = parseFloat(cols[3]);
|
||||||
|
const close = parseFloat(cols[6]);
|
||||||
|
if (isNaN(close)) return empty;
|
||||||
|
const delta = !isNaN(open) ? parseFloat((close - open).toFixed(2)) : null;
|
||||||
|
const deltaPct = (!isNaN(open) && open > 0)
|
||||||
|
? parseFloat(((close - open) / open * 100).toFixed(2))
|
||||||
|
: null;
|
||||||
|
return { value: parseFloat(close.toFixed(2)), delta, deltaPct };
|
||||||
|
} catch { return empty; }
|
||||||
|
}
|
||||||
|
|
||||||
// ── Binance (Bitcoin — gratuit, sans clé, temps réel) ────────────────────────
|
// ── Binance (Bitcoin — gratuit, sans clé, temps réel) ────────────────────────
|
||||||
|
|
||||||
async function binanceBTC(): Promise<{ value: number | null; change24h: number | null }> {
|
async function binanceBTC(): Promise<{ value: number | null; change24h: number | null }> {
|
||||||
@@ -75,23 +100,28 @@ export async function GET() {
|
|||||||
const fredKey = process.env.FRED_API_KEY;
|
const fredKey = process.env.FRED_API_KEY;
|
||||||
if (!fredKey) return NextResponse.json({ error: "FRED_API_KEY missing" }, { status: 500 });
|
if (!fredKey) return NextResponse.json({ error: "FRED_API_KEY missing" }, { status: 500 });
|
||||||
|
|
||||||
// 1. Marchés — FRED (données fin de journée, cache 24h)
|
// 1. Marchés indices — FRED (données fin de journée, cache 24h)
|
||||||
// VIXCLS = VIX clôture CBOE | SP500 = S&P 500 | GOLDPMGBD228NLBM = Or LBMA
|
// VIXCLS = VIX clôture CBOE | SP500 = S&P 500
|
||||||
// SLVPRUSD = Argent LBMA | DCOILBRENTEU = Brent | DCOILWTICO = WTI
|
// DCOILBRENTEU = Brent | DCOILWTICO = WTI
|
||||||
const [vixQ, sp500Q, goldQ, silverQ, brentQ, wtiQ] = await Promise.all([
|
const [vixQ, sp500Q, brentQ, wtiQ] = await Promise.all([
|
||||||
fredObsDelta("VIXCLS", fredKey),
|
fredObsDelta("VIXCLS", fredKey),
|
||||||
fredObsDelta("SP500", fredKey),
|
fredObsDelta("SP500", fredKey),
|
||||||
fredObsDelta("GOLDPMGBD228NLBM", fredKey),
|
fredObsDelta("DCOILBRENTEU", fredKey),
|
||||||
fredObsDelta("SLVPRUSD", fredKey),
|
fredObsDelta("DCOILWTICO", fredKey),
|
||||||
fredObsDelta("DCOILBRENTEU", fredKey),
|
|
||||||
fredObsDelta("DCOILWTICO", fredKey),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 2. Bitcoin — Binance (temps réel), fallback CoinGecko
|
// 2. Métaux précieux — Stooq (quasi temps réel, cache 5 min, sans clé API)
|
||||||
|
// delta = variation intraday vs ouverture de session
|
||||||
|
const [goldQ, silverQ] = await Promise.all([
|
||||||
|
stooqMetal("xauusd"),
|
||||||
|
stooqMetal("xagusd"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 3. Bitcoin — Binance (temps réel), fallback CoinGecko
|
||||||
const btcBin = await binanceBTC();
|
const btcBin = await binanceBTC();
|
||||||
const btcCg = btcBin.value === null ? await coingeckoBTC() : { value: null, change24h: null };
|
const btcCg = btcBin.value === null ? await coingeckoBTC() : { value: null, change24h: null };
|
||||||
|
|
||||||
// 3. FRED — spreads crédit + taux directeurs (cache 24h)
|
// 4. FRED — spreads crédit + taux directeurs (cache 24h)
|
||||||
const [hyRaw, igRaw, us10y, us2y] = await Promise.all([
|
const [hyRaw, igRaw, us10y, us2y] = await Promise.all([
|
||||||
fredObs("BAMLH0A0HYM2", fredKey),
|
fredObs("BAMLH0A0HYM2", fredKey),
|
||||||
fredObs("BAMLC0A0CM", fredKey),
|
fredObs("BAMLC0A0CM", fredKey),
|
||||||
@@ -102,7 +132,7 @@ export async function GET() {
|
|||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
// Sentiment / Risk-On
|
// Sentiment / Risk-On
|
||||||
vix: vixQ.value,
|
vix: vixQ.value,
|
||||||
vixDelta: vixQ.delta, // pts vs clôture j-1
|
vixDelta: vixQ.delta,
|
||||||
sp500: sp500Q.value,
|
sp500: sp500Q.value,
|
||||||
sp500Change: sp500Q.delta,
|
sp500Change: sp500Q.delta,
|
||||||
sp500ChangePct: sp500Q.deltaPct,
|
sp500ChangePct: sp500Q.deltaPct,
|
||||||
@@ -115,7 +145,7 @@ export async function GET() {
|
|||||||
us10y,
|
us10y,
|
||||||
us2y,
|
us2y,
|
||||||
curveSlope: us10y !== null && us2y !== null ? Math.round((us10y - us2y) * 100) : null,
|
curveSlope: us10y !== null && us2y !== null ? Math.round((us10y - us2y) * 100) : null,
|
||||||
// Commodités — delta = variation vs clôture j-1
|
// Commodités — Or/Argent intraday (Stooq), Pétrole j-1 (FRED)
|
||||||
gold: goldQ.value,
|
gold: goldQ.value,
|
||||||
goldDelta: goldQ.delta,
|
goldDelta: goldQ.delta,
|
||||||
silver: silverQ.value,
|
silver: silverQ.value,
|
||||||
|
|||||||
Reference in New Issue
Block a user