mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-07-27 20:37:45 +00:00
auto: sync 2026-06-02 00:03
This commit is contained in:
+21
-2
@@ -6,6 +6,7 @@ import rateDecisionsRaw from "@/data/rate_decisions.json";
|
||||
import { fetchFFThisWeek, fetchFFEvents } from "@/lib/forexfactory";
|
||||
import type { FFEvent } from "@/lib/forexfactory";
|
||||
import { fetchTECoreInflation, fetchTEMoMInflation } from "@/lib/tecpi";
|
||||
import { fetchTEInflationForecasts } from "@/lib/tradingeconomics";
|
||||
|
||||
const FRED_BASE = "https://api.stlouisfed.org/fred/series/observations";
|
||||
const REVALIDATE = 86400; // cache 24h
|
||||
@@ -605,6 +606,13 @@ function toPmiIndicator(raw: { value: number | null; prev: number | null }): Ind
|
||||
};
|
||||
}
|
||||
|
||||
// ── Parser forecast string TE ("2.8%" ou "0.4") → number ─────────────────────
|
||||
function parseTeF(s: string | null | undefined): number | null {
|
||||
if (!s) return null;
|
||||
const n = parseFloat(s.replace("%", "").trim());
|
||||
return isNaN(n) ? null : n;
|
||||
}
|
||||
|
||||
// ── Server-side cache ─────────────────────────────────────────────────────────
|
||||
|
||||
const _cache = new Map<string, { data: unknown; ts: number }>();
|
||||
@@ -948,13 +956,20 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
|
||||
// ── PMI (Mfg + Services + Composite) + consensus FF ──────────────────────────
|
||||
const [ffPMI, pmiMfgRaw, pmiSvcRaw, pmiCompositeRaw, ffForecasts] = await Promise.all([
|
||||
const toDateObj = new Date();
|
||||
toDateObj.setDate(toDateObj.getDate() + 21);
|
||||
const toDate = toDateObj.toISOString().slice(0, 10);
|
||||
|
||||
const [ffPMI, pmiMfgRaw, pmiSvcRaw, pmiCompositeRaw, ffForecasts, teForecastMap] = await Promise.all([
|
||||
fetchFFPMI(currency),
|
||||
scrapePMI(currency, "manufacturing-pmi"),
|
||||
scrapePMI(currency, "services-pmi"),
|
||||
scrapePMI(currency, "composite-pmi"),
|
||||
fetchFFForecasts(currency),
|
||||
fetchTEInflationForecasts(today, toDate),
|
||||
]);
|
||||
|
||||
const teCpiForecast = teForecastMap[currency];
|
||||
// FF en priorité (forecast + actual) ; TE en fallback
|
||||
indicators.pmiMfg = ffPMI.mfg ? toPmiIndicator(ffPMI.mfg) : toPmiIndicator(pmiMfgRaw);
|
||||
indicators.pmiServices = ffPMI.svc ? toPmiIndicator(ffPMI.svc) : toPmiIndicator(pmiSvcRaw);
|
||||
@@ -1047,7 +1062,11 @@ export async function GET(req: NextRequest) {
|
||||
const data = {
|
||||
currency, indicators,
|
||||
forecasts: {
|
||||
cpi: ffForecasts.cpi,
|
||||
// CPI — TE calendar forecast (priorité) puis ForexFactory
|
||||
// Les forecasts TE sont des strings "2.8%" → parseFloat les convertit en number
|
||||
cpi: parseTeF(teCpiForecast?.cpiYoY) ?? ffForecasts.cpi ?? null,
|
||||
cpiCore: parseTeF(teCpiForecast?.cpiCore) ?? null,
|
||||
cpiMoM: parseTeF(teCpiForecast?.cpiMoM) ?? null,
|
||||
cpiSurprise: ffForecasts.cpiSurprise,
|
||||
unemployment: ffForecasts.unemployment,
|
||||
unemploymentSurprise: ffForecasts.unemploymentSurprise,
|
||||
|
||||
@@ -13,6 +13,8 @@ import NarrativeButton from "./NarrativeButton";
|
||||
interface Ind { value: number | null; prev: number | null; surprise: number | null; trend: "up"|"down"|"flat"|null; lastUpdated: string | null }
|
||||
interface MacroForecasts {
|
||||
cpi: number | null; cpiSurprise: number | null;
|
||||
cpiCore: number | null;
|
||||
cpiMoM: number | null;
|
||||
unemployment: number | null; unemploymentSurprise: number | null;
|
||||
pmiMfg: number | null; pmiMfgSurprise: number | null;
|
||||
pmiSvc: number | null; pmiSvcSurprise: number | null;
|
||||
@@ -356,8 +358,8 @@ export default function CurrencyCard({ currency, expectations, yields, sentiment
|
||||
|
||||
{/* ── INFLATION ───────────────────────────────────────────────────── */}
|
||||
<SectionHeader label="Inflation" />
|
||||
<Row label="CPI Core YoY" ind={inds?.cpiCore ?? null} unit="%" consensus={fc?.cpi ?? null} surpriseVsCons={fc?.cpiSurprise ?? null} />
|
||||
<Row label="CPI MoM" ind={inds?.cpiMoM ?? null} unit="%" />
|
||||
<Row label="CPI Core YoY" ind={inds?.cpiCore ?? null} unit="%" consensus={fc?.cpiCore ?? fc?.cpi ?? null} surpriseVsCons={fc?.cpiSurprise ?? null} />
|
||||
<Row label="CPI MoM" ind={inds?.cpiMoM ?? null} unit="%" consensus={fc?.cpiMoM ?? null} />
|
||||
|
||||
{/* ── CROISSANCE ──────────────────────────────────────────────────── */}
|
||||
<SectionHeader label="Croissance" />
|
||||
|
||||
@@ -215,6 +215,46 @@ function parseCalendarHTML(html: string): TECalendarEvent[] {
|
||||
return events;
|
||||
}
|
||||
|
||||
// ── Forecasts CPI depuis le calendrier TE ────────────────────────────────────
|
||||
// Lit les events inflation futurs du calendrier TE et retourne les forecasts
|
||||
// par devise pour cpiCore YoY, cpiMoM et cpiHeadline YoY.
|
||||
// Utilisé par la macro route pour remplir forecasts.cpiCore / forecasts.cpiMoM.
|
||||
|
||||
export interface TEInflationForecasts {
|
||||
cpiYoY: string | null;
|
||||
cpiCore: string | null;
|
||||
cpiMoM: string | null;
|
||||
}
|
||||
|
||||
export async function fetchTEInflationForecasts(
|
||||
fromDate?: string,
|
||||
toDate?: string,
|
||||
): Promise<Partial<Record<Currency, TEInflationForecasts>>> {
|
||||
const events = await fetchTECalendarHTML(fromDate, toDate);
|
||||
const result: Partial<Record<Currency, TEInflationForecasts>> = {};
|
||||
const now = fromDate ? new Date(fromDate + "T00:00:00Z") : new Date();
|
||||
|
||||
for (const ev of events) {
|
||||
if (ev.category !== "inflation") continue;
|
||||
if (new Date(ev.date) <= now) continue;
|
||||
if (!ev.forecast) continue;
|
||||
|
||||
const ccy = ev.currency;
|
||||
const title = ev.title.toLowerCase();
|
||||
if (!result[ccy]) result[ccy] = { cpiYoY: null, cpiCore: null, cpiMoM: null };
|
||||
|
||||
const r = result[ccy]!;
|
||||
if (!r.cpiCore && /core.*inflation.*yoy|core.*cpi.*yoy|core.*rate.*yoy/i.test(title)) {
|
||||
r.cpiCore = ev.forecast;
|
||||
} else if (!r.cpiMoM && /inflation.*mom|cpi.*mom|rate.*mom/i.test(title)) {
|
||||
r.cpiMoM = ev.forecast;
|
||||
} else if (!r.cpiYoY && /inflation.*yoy|cpi.*yoy|inflation\s+rate.*yoy/i.test(title)) {
|
||||
r.cpiYoY = ev.forecast;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Paid API (when TRADING_ECONOMICS_API_KEY is set) ─────────────────────────
|
||||
|
||||
export async function fetchTECalendar(
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user