2026-05-27 13:18:45 +02:00
|
|
|
|
import { NextResponse } from "next/server";
|
2026-06-01 23:16:18 +02:00
|
|
|
|
import { fetchTEBondYields } from "@/lib/tebonds";
|
2026-05-27 13:18:45 +02:00
|
|
|
|
|
2026-06-29 12:17:11 +02:00
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
|
|
|
2026-06-01 23:16:18 +02:00
|
|
|
|
// ── FRED (spreads crédit + rendements) ───────────────────────────────────────
|
|
|
|
|
|
// VIX et S&P500 ne passent plus par FRED (lag 1-2j) → Yahoo Finance (temps réel)
|
2026-05-27 13:18:45 +02:00
|
|
|
|
|
|
|
|
|
|
async function fredObs(series: string, apiKey: string): Promise<number | null> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const url = `https://api.stlouisfed.org/fred/series/observations?series_id=${series}&api_key=${apiKey}&file_type=json&sort_order=desc&limit=1`;
|
2026-06-01 23:16:18 +02:00
|
|
|
|
const res = await fetch(url, { next: { revalidate: 3600 } }); // 1h — FRED publie 1x/jour mais réduit le délai d'affichage
|
2026-05-27 13:18:45 +02:00
|
|
|
|
if (!res.ok) return null;
|
|
|
|
|
|
const obs = ((await res.json())?.observations ?? []).find(
|
|
|
|
|
|
(o: { value: string }) => o.value !== "."
|
|
|
|
|
|
);
|
|
|
|
|
|
return obs ? parseFloat(obs.value) : null;
|
|
|
|
|
|
} catch { return null; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 19:43:39 +02:00
|
|
|
|
type FredResult = { value: number | null; delta: number | null; deltaPct: number | null };
|
|
|
|
|
|
|
2026-06-01 23:16:18 +02:00
|
|
|
|
// ── Yahoo Finance (VIX, S&P 500 — temps réel via regularMarketPrice) ──────────
|
|
|
|
|
|
// Query v8 chart API : meta.regularMarketPrice + meta.chartPreviousClose
|
|
|
|
|
|
// Cache 5 min → données fraîches pendant la séance boursière
|
|
|
|
|
|
|
|
|
|
|
|
async function yahooQuote(symbol: string): Promise<FredResult> {
|
2026-05-28 19:43:39 +02:00
|
|
|
|
const empty: FredResult = { value: null, delta: null, deltaPct: null };
|
|
|
|
|
|
try {
|
2026-06-01 23:16:18 +02:00
|
|
|
|
const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?interval=1d&range=2d`;
|
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
|
next: { revalidate: 300 }, // cache 5 min
|
|
|
|
|
|
headers: { "User-Agent": "Mozilla/5.0 (compatible; ForexDashboard/1.0)" },
|
|
|
|
|
|
});
|
2026-05-28 19:43:39 +02:00
|
|
|
|
if (!res.ok) return empty;
|
2026-06-01 23:16:18 +02:00
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
const meta = data?.chart?.result?.[0]?.meta as {
|
|
|
|
|
|
regularMarketPrice?: number;
|
|
|
|
|
|
chartPreviousClose?: number;
|
|
|
|
|
|
} | undefined;
|
|
|
|
|
|
const current = meta?.regularMarketPrice ?? null;
|
|
|
|
|
|
const prevClose = meta?.chartPreviousClose ?? null;
|
|
|
|
|
|
if (current === null) return empty;
|
|
|
|
|
|
const delta = prevClose !== null ? parseFloat((current - prevClose).toFixed(2)) : null;
|
|
|
|
|
|
const deltaPct = prevClose !== null && prevClose > 0
|
|
|
|
|
|
? parseFloat(((current - prevClose) / prevClose * 100).toFixed(2))
|
|
|
|
|
|
: null;
|
|
|
|
|
|
return { value: parseFloat(current.toFixed(2)), delta, deltaPct };
|
2026-05-28 19:43:39 +02:00
|
|
|
|
} catch { return empty; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-28 20:27:44 +02:00
|
|
|
|
|
2026-05-28 19:43:39 +02:00
|
|
|
|
// ── Binance (Bitcoin — gratuit, sans clé, temps réel) ────────────────────────
|
2026-06-01 23:16:18 +02:00
|
|
|
|
// ticker/price = prix spot instantané (plus précis que ticker/24hr lastPrice)
|
|
|
|
|
|
// ticker/24hr = stats 24h pour le % de variation
|
2026-05-28 19:43:39 +02:00
|
|
|
|
async function binanceBTC(): Promise<{ value: number | null; change24h: number | null }> {
|
|
|
|
|
|
try {
|
2026-06-01 23:16:18 +02:00
|
|
|
|
const [priceRes, statsRes] = await Promise.all([
|
|
|
|
|
|
fetch("https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT", { cache: "no-store" }),
|
|
|
|
|
|
fetch("https://api.binance.com/api/v3/ticker/24hr?symbol=BTCUSDT", { cache: "no-store" }),
|
|
|
|
|
|
]);
|
|
|
|
|
|
const price = priceRes.ok ? parseFloat((await priceRes.json()).price) : NaN;
|
|
|
|
|
|
const pctChg = statsRes.ok ? parseFloat((await statsRes.json()).priceChangePercent) : NaN;
|
2026-05-28 19:43:39 +02:00
|
|
|
|
return {
|
2026-06-01 23:16:18 +02:00
|
|
|
|
value: isNaN(price) ? null : parseFloat(price.toFixed(2)),
|
2026-05-28 19:43:39 +02:00
|
|
|
|
change24h: isNaN(pctChg) ? null : parseFloat(pctChg.toFixed(2)),
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch { return { value: null, change24h: null }; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── CoinGecko (Bitcoin fallback) ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
async function coingeckoBTC(): Promise<{ value: number | null; change24h: number | null }> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
|
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true",
|
|
|
|
|
|
{ cache: "no-store" }
|
|
|
|
|
|
);
|
|
|
|
|
|
if (!res.ok) return { value: null, change24h: null };
|
|
|
|
|
|
const d = await res.json();
|
|
|
|
|
|
return {
|
|
|
|
|
|
value: d?.bitcoin?.usd ?? null,
|
|
|
|
|
|
change24h: d?.bitcoin?.usd_24h_change ?? null,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch { return { value: null, change24h: null }; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 14:12:05 +02:00
|
|
|
|
// ── investing.com — BTC/USD temps réel (data-test attributes, cache 1 min) ────
|
|
|
|
|
|
// Sélecteurs stables : data-test="instrument-price-last/change/change-percent"
|
|
|
|
|
|
async function investingBTC(): Promise<{ value: number | null; delta: number | null; deltaPct: number | null }> {
|
|
|
|
|
|
const empty = { value: null, delta: null, deltaPct: null };
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch("https://www.investing.com/crypto/bitcoin/btc-usd", {
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
|
|
|
|
"Accept-Language": "en-US,en;q=0.9",
|
|
|
|
|
|
},
|
|
|
|
|
|
next: { revalidate: 60 },
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!res.ok) return empty;
|
|
|
|
|
|
const html = await res.text();
|
|
|
|
|
|
|
|
|
|
|
|
// "61,307.0" → 61307
|
|
|
|
|
|
const priceMatch = html.match(/data-test="instrument-price-last">([^<]+)/);
|
|
|
|
|
|
const deltaMatch = html.match(/data-test="instrument-price-change">([^<]+)/);
|
|
|
|
|
|
// "(-0.75%)" → -0.75
|
|
|
|
|
|
const pctMatch = html.match(/data-test="instrument-price-change-percent">\(([^)%]+)%\)/);
|
|
|
|
|
|
|
|
|
|
|
|
if (!priceMatch) return empty;
|
|
|
|
|
|
const value = parseFloat(priceMatch[1].replace(/,/g, ""));
|
|
|
|
|
|
if (isNaN(value)) return empty;
|
|
|
|
|
|
|
|
|
|
|
|
const delta = deltaMatch ? parseFloat(deltaMatch[1].replace(/,/g, "")) : null;
|
|
|
|
|
|
const deltaPct = pctMatch ? parseFloat(pctMatch[1])
|
|
|
|
|
|
: delta !== null && value > 0 ? parseFloat(((delta / (value - delta)) * 100).toFixed(2))
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
value: Math.round(value),
|
|
|
|
|
|
delta: delta !== null && !isNaN(delta) ? Math.round(delta) : null,
|
|
|
|
|
|
deltaPct: deltaPct !== null && !isNaN(deltaPct) ? deltaPct : null,
|
|
|
|
|
|
};
|
|
|
|
|
|
} catch { return empty; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Business Insider Markets — WTI & S&P 500 (JSON inline, cache 1 min) ──────
|
|
|
|
|
|
// JSON pattern dans le HTML : "currentValue":XX.XX et "previousClose":XX.XX
|
|
|
|
|
|
async function biMarket(url: string): Promise<{ value: number | null; delta: number | null; deltaPct: number | null }> {
|
|
|
|
|
|
const empty = { value: null, delta: null, deltaPct: null };
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(url, {
|
|
|
|
|
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" },
|
|
|
|
|
|
next: { revalidate: 60 },
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!res.ok) return empty;
|
|
|
|
|
|
const html = await res.text();
|
|
|
|
|
|
const curMatch = html.match(/"currentValue":([\d.]+)/);
|
|
|
|
|
|
const prevMatch = html.match(/"previousClose":([\d.]+)/);
|
|
|
|
|
|
if (!curMatch) return empty;
|
|
|
|
|
|
const value = parseFloat(curMatch[1]);
|
|
|
|
|
|
const prev = prevMatch ? parseFloat(prevMatch[1]) : null;
|
|
|
|
|
|
if (isNaN(value)) return empty;
|
|
|
|
|
|
const delta = prev !== null ? parseFloat((value - prev).toFixed(2)) : null;
|
|
|
|
|
|
const deltaPct = delta !== null && prev !== null && prev > 0
|
|
|
|
|
|
? parseFloat(((delta / prev) * 100).toFixed(2)) : null;
|
|
|
|
|
|
return { value: parseFloat(value.toFixed(2)), delta, deltaPct };
|
|
|
|
|
|
} catch { return empty; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── abcbourse.com — Brent spot temps réel (Six Financial Information) ────────
|
|
|
|
|
|
// Sélecteurs HTML stables : id="lastcx" (cours), id="veille" (clôture J-1), id="varcx" (%)
|
|
|
|
|
|
async function abcbourseBrent(): Promise<{ value: number | null; delta: number | null; deltaPct: number | null }> {
|
|
|
|
|
|
const empty = { value: null, delta: null, deltaPct: null };
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch("https://www.abcbourse.com/cotation/XBRUSDu", {
|
|
|
|
|
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" },
|
|
|
|
|
|
next: { revalidate: 60 },
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!res.ok) return empty;
|
|
|
|
|
|
const html = await res.text();
|
|
|
|
|
|
|
|
|
|
|
|
const priceMatch = html.match(/id="lastcx">([^<]+)/);
|
|
|
|
|
|
const prevMatch = html.match(/id="veille">([^<]+)/);
|
|
|
|
|
|
const pctMatch = html.match(/id="varcx"[^>]*>([^<]+)/);
|
|
|
|
|
|
|
|
|
|
|
|
if (!priceMatch) return empty;
|
|
|
|
|
|
|
|
|
|
|
|
// "96,70 $" → 96.70
|
|
|
|
|
|
const value = parseFloat(priceMatch[1].replace(/[^\d,]/g, "").replace(",", "."));
|
|
|
|
|
|
if (isNaN(value)) return empty;
|
|
|
|
|
|
|
|
|
|
|
|
const prev = prevMatch ? parseFloat(prevMatch[1].replace(",", ".").trim()) : null;
|
|
|
|
|
|
const delta = prev !== null && !isNaN(prev) ? parseFloat((value - prev).toFixed(2)) : null;
|
|
|
|
|
|
|
|
|
|
|
|
let deltaPct: number | null = null;
|
|
|
|
|
|
if (pctMatch) {
|
|
|
|
|
|
// + = "+" ; − ou − = "−"
|
|
|
|
|
|
const pctStr = pctMatch[1]
|
|
|
|
|
|
.replace(/[Bb];/g, "+")
|
|
|
|
|
|
.replace(/−|−/g, "-")
|
|
|
|
|
|
.replace("%", "")
|
|
|
|
|
|
.replace(",", ".")
|
|
|
|
|
|
.trim();
|
|
|
|
|
|
const pctNum = parseFloat(pctStr);
|
|
|
|
|
|
if (!isNaN(pctNum)) deltaPct = pctNum;
|
|
|
|
|
|
} else if (delta !== null && prev !== null && prev > 0) {
|
|
|
|
|
|
deltaPct = parseFloat(((delta / prev) * 100).toFixed(2));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { value, delta, deltaPct };
|
|
|
|
|
|
} catch { return empty; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-27 13:18:45 +02:00
|
|
|
|
// ── GET ───────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export async function GET() {
|
|
|
|
|
|
const fredKey = process.env.FRED_API_KEY;
|
|
|
|
|
|
if (!fredKey) return NextResponse.json({ error: "FRED_API_KEY missing" }, { status: 500 });
|
|
|
|
|
|
|
2026-06-27 14:12:05 +02:00
|
|
|
|
// 1. Indices — Business Insider (JSON inline, cache 1 min) + fallback Yahoo Finance
|
|
|
|
|
|
// VIX reste Yahoo (Business Insider n'a pas VIX)
|
|
|
|
|
|
const [vixQ, sp500Raw] = await Promise.all([
|
2026-06-01 23:16:18 +02:00
|
|
|
|
yahooQuote("^VIX"),
|
2026-06-27 14:12:05 +02:00
|
|
|
|
biMarket("https://markets.businessinsider.com/index/s%26p_500"),
|
2026-05-28 22:52:49 +02:00
|
|
|
|
]);
|
2026-06-27 14:12:05 +02:00
|
|
|
|
const sp500Q = sp500Raw.value !== null ? sp500Raw : await yahooQuote("^GSPC");
|
2026-05-28 22:52:49 +02:00
|
|
|
|
|
2026-06-27 14:12:05 +02:00
|
|
|
|
// Brent — abcbourse.com (Six Financial Information, temps réel, cache 1 min)
|
|
|
|
|
|
// Fallback : Yahoo Finance BZ=F si le scraping échoue
|
|
|
|
|
|
// WTI — Business Insider (JSON inline, cache 1 min) + fallback Yahoo Finance CL=F
|
|
|
|
|
|
const [brentRaw, wtiRaw] = await Promise.all([
|
|
|
|
|
|
abcbourseBrent(),
|
|
|
|
|
|
biMarket("https://markets.businessinsider.com/commodities/oil-price?type=wti"),
|
2026-05-28 19:43:39 +02:00
|
|
|
|
]);
|
2026-06-27 14:12:05 +02:00
|
|
|
|
const brentQ = brentRaw.value !== null ? brentRaw : await yahooQuote("BZ=F").then(q => ({
|
|
|
|
|
|
value: q.value, delta: q.delta, deltaPct: q.deltaPct,
|
|
|
|
|
|
}));
|
|
|
|
|
|
const wtiQ = wtiRaw.value !== null ? wtiRaw : await yahooQuote("CL=F");
|
2026-05-27 13:18:45 +02:00
|
|
|
|
|
2026-06-27 14:12:05 +02:00
|
|
|
|
// 2. Métaux précieux — Business Insider (JSON inline, cache 1 min) + fallback Yahoo Finance
|
|
|
|
|
|
const [goldRaw, silverRaw] = await Promise.all([
|
|
|
|
|
|
biMarket("https://markets.businessinsider.com/commodities/gold-price"),
|
|
|
|
|
|
biMarket("https://markets.businessinsider.com/commodities/silver-price"),
|
2026-05-28 20:27:44 +02:00
|
|
|
|
]);
|
2026-06-27 14:12:05 +02:00
|
|
|
|
const goldQ = goldRaw.value !== null ? goldRaw : await yahooQuote("GC=F");
|
|
|
|
|
|
const silverQ = silverRaw.value !== null ? silverRaw : await yahooQuote("SI=F");
|
2026-05-28 20:27:44 +02:00
|
|
|
|
|
2026-06-27 14:12:05 +02:00
|
|
|
|
// 3. BTC/USD — investing.com (data-test attrs, cache 1 min) + fallback Binance/CoinGecko
|
|
|
|
|
|
const btcRaw = await investingBTC();
|
|
|
|
|
|
const btcBin = btcRaw.value === null ? await binanceBTC() : { value: null, change24h: null };
|
|
|
|
|
|
const btcCg = btcRaw.value === null && btcBin.value === null ? await coingeckoBTC() : { value: null, change24h: null };
|
2026-05-27 13:18:45 +02:00
|
|
|
|
|
2026-06-01 23:16:18 +02:00
|
|
|
|
// 4. FRED — spreads crédit (cache 1h)
|
|
|
|
|
|
// Yields 10Y — TE bonds (cache 1h, données du jour)
|
|
|
|
|
|
// US 2Y — FRED DGS2 (garde pour la courbe 2-10)
|
|
|
|
|
|
const [hyRaw, igRaw, us2y, bondYields] = await Promise.all([
|
2026-05-27 13:18:45 +02:00
|
|
|
|
fredObs("BAMLH0A0HYM2", fredKey),
|
|
|
|
|
|
fredObs("BAMLC0A0CM", fredKey),
|
|
|
|
|
|
fredObs("DGS2", fredKey),
|
2026-06-01 23:16:18 +02:00
|
|
|
|
fetchTEBondYields(),
|
2026-05-27 13:18:45 +02:00
|
|
|
|
]);
|
|
|
|
|
|
|
2026-06-01 23:16:18 +02:00
|
|
|
|
const us10y = bondYields["USD"]?.yield10y ?? null;
|
|
|
|
|
|
|
2026-05-27 13:18:45 +02:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
// Sentiment / Risk-On
|
2026-05-27 13:45:06 +02:00
|
|
|
|
vix: vixQ.value,
|
2026-05-28 20:27:44 +02:00
|
|
|
|
vixDelta: vixQ.delta,
|
2026-05-27 13:45:06 +02:00
|
|
|
|
sp500: sp500Q.value,
|
|
|
|
|
|
sp500Change: sp500Q.delta,
|
|
|
|
|
|
sp500ChangePct: sp500Q.deltaPct,
|
2026-06-27 14:12:05 +02:00
|
|
|
|
btc: btcRaw.value ?? btcBin.value ?? btcCg.value,
|
2026-05-27 13:45:06 +02:00
|
|
|
|
btcChange24h: btcBin.change24h ?? btcCg.change24h,
|
2026-06-27 14:12:05 +02:00
|
|
|
|
btcDeltaPct: btcRaw.deltaPct,
|
2026-06-01 23:16:18 +02:00
|
|
|
|
// Crédit (FRED, bps)
|
2026-05-27 13:18:45 +02:00
|
|
|
|
hySpread: hyRaw != null ? Math.round(hyRaw * 100) : null,
|
|
|
|
|
|
igSpread: igRaw != null ? Math.round(igRaw * 100) : null,
|
2026-06-01 23:16:18 +02:00
|
|
|
|
// Yields 10Y par devise — TE bonds (temps réel, cache 1h)
|
|
|
|
|
|
bondYields10y: {
|
|
|
|
|
|
USD: bondYields["USD"]?.yield10y ?? null,
|
|
|
|
|
|
EUR: bondYields["EUR"]?.yield10y ?? null,
|
|
|
|
|
|
GBP: bondYields["GBP"]?.yield10y ?? null,
|
|
|
|
|
|
JPY: bondYields["JPY"]?.yield10y ?? null,
|
|
|
|
|
|
AUD: bondYields["AUD"]?.yield10y ?? null,
|
|
|
|
|
|
CAD: bondYields["CAD"]?.yield10y ?? null,
|
|
|
|
|
|
CHF: bondYields["CHF"]?.yield10y ?? null,
|
|
|
|
|
|
NZD: bondYields["NZD"]?.yield10y ?? null,
|
|
|
|
|
|
},
|
|
|
|
|
|
bondYieldsDay: {
|
|
|
|
|
|
USD: bondYields["USD"]?.dayDelta ?? null,
|
|
|
|
|
|
EUR: bondYields["EUR"]?.dayDelta ?? null,
|
|
|
|
|
|
GBP: bondYields["GBP"]?.dayDelta ?? null,
|
|
|
|
|
|
JPY: bondYields["JPY"]?.dayDelta ?? null,
|
|
|
|
|
|
AUD: bondYields["AUD"]?.dayDelta ?? null,
|
|
|
|
|
|
CAD: bondYields["CAD"]?.dayDelta ?? null,
|
|
|
|
|
|
CHF: bondYields["CHF"]?.dayDelta ?? null,
|
|
|
|
|
|
NZD: bondYields["NZD"]?.dayDelta ?? null,
|
|
|
|
|
|
},
|
|
|
|
|
|
// Courbe USD (2Y de FRED, 10Y de TE)
|
2026-05-27 13:18:45 +02:00
|
|
|
|
us10y,
|
|
|
|
|
|
us2y,
|
|
|
|
|
|
curveSlope: us10y !== null && us2y !== null ? Math.round((us10y - us2y) * 100) : null,
|
2026-06-27 14:12:05 +02:00
|
|
|
|
// Commodités — Business Insider (cache 1 min) + fallback Yahoo Finance
|
|
|
|
|
|
gold: goldQ.value,
|
|
|
|
|
|
goldDelta: goldQ.delta,
|
|
|
|
|
|
goldDeltaPct: goldQ.deltaPct,
|
|
|
|
|
|
silver: silverQ.value,
|
|
|
|
|
|
silverDelta: silverQ.delta,
|
|
|
|
|
|
silverDeltaPct: silverQ.deltaPct,
|
|
|
|
|
|
brent: brentQ.value,
|
|
|
|
|
|
brentDelta: brentQ.delta,
|
|
|
|
|
|
brentDeltaPct: brentQ.deltaPct,
|
2026-05-27 13:45:06 +02:00
|
|
|
|
wti: wtiQ.value,
|
|
|
|
|
|
wtiDelta: wtiQ.delta,
|
2026-06-27 14:12:05 +02:00
|
|
|
|
wtiDeltaPct: wtiQ.deltaPct,
|
2026-05-27 13:18:45 +02:00
|
|
|
|
// Compat
|
|
|
|
|
|
copper: null,
|
|
|
|
|
|
timestamp: Date.now(),
|
2026-06-29 12:17:11 +02:00
|
|
|
|
}, { headers: { "Cache-Control": "no-store" } });
|
2026-05-27 13:18:45 +02:00
|
|
|
|
}
|