mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-08-16 14:08:06 +00:00
feat: Atlanta Fed MPT (options SOFR) + libellé méthodologie Réunions USD
Nouveau sous-onglet "Atlanta Fed" dans le bloc OIS de la card USD, méthodologie alternative aux 30-day Fed Fund Futures demandée : distribution du niveau de taux Fed déduite des options sur futures SOFR 3 mois (CME), publiée quotidiennement par la Fed d'Atlanta (Market Probability Tracker). Affiche Cut/Hold/Hike, la fourchette de taux SOFR implicite (25e-75e percentile), et la distribution complète par fourchette de 25bps pour la fenêtre trimestrielle la plus proche. - .github/scripts/fetch-atlanta-mpt.mjs : télécharge et parse mpt_histdata.xlsx (Atlanta Fed) avec un lecteur ZIP/OOXML maison — le paquet npm "xlsx" a des CVE critiques non patchées sur le registre public (SheetJS a arrêté d'y publier), donc pas de dépendance ajoutée pour ça. - .github/workflows/fetch-atlanta-mpt.yml : quotidien (donnée mise à jour 1x/jour par l'Atlanta Fed), + déclenchement manuel. - lib/atlantaFedMpt.ts + app/api/macro/route.ts : expose la donnée (USD only) via l'API macro existante. Sous-onglet "Réunions" (USD) : ajout du libellé de méthodologie demandé — "Probabilités = somme des % associés à chaque fourchette au-dessus/en-dessous de la fourchette actuelle · Investing.com Fed Rate Monitor, calculées à partir des 30-day Fed Fund Futures (CME)". Vérifié en direct : les deux sous-onglets s'affichent et les chiffres correspondent aux données brutes (72.4% hike / 1.1% cut / distribution 27.2-45.7-21.4-5.7% pour la fenêtre Sep 2026). Note en marge (hors scope, découverte pendant les tests) : /api/expectations prend ~24s à répondre, ce qui bloque l'affichage complet du dashboard (Promise.allSettled attend les 8 fetches). À investiguer séparément. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8c6f96efd7
commit
274c7f534e
@@ -16,6 +16,7 @@ import { biasLabel, calcMacroScore } from "@/lib/scoring";
|
||||
import { saveCache, loadCache, formatCacheDate } from "@/lib/localCache";
|
||||
import type { Currency, BiasPhase, RateExpectation, MacroSection } from "@/lib/types";
|
||||
import type { CBRatePath, ILWeeklyDelta } from "@/lib/rateprobability";
|
||||
import type { AtlantaFedMpt } from "@/lib/atlantaFedMpt";
|
||||
import type { SentimentEntry, CotEntry } from "@/lib/types";
|
||||
import type { CalendarEvent } from "@/app/api/calendar/route";
|
||||
import NarrativeButton from "./NarrativeButton";
|
||||
@@ -48,6 +49,7 @@ interface MacroData {
|
||||
indicators: Record<string, Ind | null>;
|
||||
forecasts?: MacroForecasts | null;
|
||||
moneySupplyM3?: MoneySupplyM3 | null;
|
||||
atlantaFedMpt?: AtlantaFedMpt | null;
|
||||
fetchedAt: string;
|
||||
}
|
||||
|
||||
@@ -67,8 +69,8 @@ interface Props {
|
||||
onCardTabChange?: (id: "overview" | "mispricing" | "focus") => void;
|
||||
syncSignauxSlide?: "ois" | "cot" | "sent";
|
||||
onSignauxSlideChange?: (id: "ois" | "cot" | "sent") => void;
|
||||
syncOisChartTab?: "curve" | "probas" | "meetings";
|
||||
onOisChartTabChange?: (id: "curve" | "probas" | "meetings") => void;
|
||||
syncOisChartTab?: "curve" | "probas" | "meetings" | "atlanta";
|
||||
onOisChartTabChange?: (id: "curve" | "probas" | "meetings" | "atlanta") => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
@@ -504,14 +506,15 @@ const STIR_INSTRUMENT: Partial<Record<string, { instrument: string; exchange: st
|
||||
NZD: { instrument: "OIS NZD (swaps)", exchange: "ASX OTC / Bloomberg NDOIS1M", convention: "maturité exacte / réunion", note: "Pas de futures standardisés. RBNZ publie les probas dans ses MPS." },
|
||||
};
|
||||
|
||||
function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
|
||||
function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange, atlantaMpt }: {
|
||||
ratePath: CBRatePath;
|
||||
syncChartTab?: "curve" | "probas" | "meetings";
|
||||
onChartTabChange?: (id: "curve" | "probas" | "meetings") => void;
|
||||
syncChartTab?: "curve" | "probas" | "meetings" | "atlanta";
|
||||
onChartTabChange?: (id: "curve" | "probas" | "meetings" | "atlanta") => void;
|
||||
atlantaMpt?: AtlantaFedMpt | null;
|
||||
}) {
|
||||
const [localChartTab, setLocalChartTab] = useState<"curve" | "probas" | "meetings">("curve");
|
||||
const [localChartTab, setLocalChartTab] = useState<"curve" | "probas" | "meetings" | "atlanta">("curve");
|
||||
const chartTab = syncChartTab ?? localChartTab;
|
||||
const setChartTab = (id: "curve" | "probas" | "meetings") => {
|
||||
const setChartTab = (id: "curve" | "probas" | "meetings" | "atlanta") => {
|
||||
setLocalChartTab(id);
|
||||
onChartTabChange?.(id);
|
||||
};
|
||||
@@ -623,6 +626,7 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
|
||||
{ id: "curve" as const, label: "Courbe" },
|
||||
{ id: "probas" as const, label: "Probabilités" },
|
||||
{ id: "meetings" as const, label: "Réunions" },
|
||||
...(ratePath.currency === "USD" && atlantaMpt ? [{ id: "atlanta" as const, label: "Atlanta Fed" }] : []),
|
||||
]).map(t => (
|
||||
<button
|
||||
key={t.id}
|
||||
@@ -811,9 +815,77 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{ratePath.currency === "USD" && (
|
||||
<p className="mt-1.5 text-[7px] text-slate-700 leading-snug">
|
||||
Probabilités = somme des % associés à chaque fourchette de taux au-dessus/en-dessous de la fourchette actuelle · Investing.com Fed Rate Monitor, calculées à partir des 30-day Fed Fund Futures (CME).
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Atlanta Fed MPT — méthodologie alternative (options sur futures SOFR) */}
|
||||
{chartTab === "atlanta" && atlantaMpt && (() => {
|
||||
const front = atlantaMpt.windows[0];
|
||||
if (!front) return null;
|
||||
const holdPct = front.probHikePct !== null && front.probCutPct !== null
|
||||
? Math.max(0, +(100 - front.probHikePct - front.probCutPct).toFixed(1))
|
||||
: null;
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<span className="text-[7px] text-slate-600 uppercase tracking-wider">Fenêtre SOFR 3M — {front.windowStartIso}</span>
|
||||
<span className="text-[7px] text-slate-700">au {atlantaMpt.asOf}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex h-[6px] rounded-full overflow-hidden bg-slate-700/20 mb-1.5">
|
||||
{front.probCutPct !== null && front.probCutPct > 0 && (
|
||||
<div className="h-full bg-sky-500/60" style={{ width: `${front.probCutPct}%` }} title={`Cut ${front.probCutPct}%`} />
|
||||
)}
|
||||
{holdPct !== null && holdPct > 0 && (
|
||||
<div className="h-full bg-slate-500/40" style={{ width: `${holdPct}%` }} title={`Hold ${holdPct}%`} />
|
||||
)}
|
||||
{front.probHikePct !== null && front.probHikePct > 0 && (
|
||||
<div className="h-full bg-red-500/60" style={{ width: `${front.probHikePct}%` }} title={`Hike ${front.probHikePct}%`} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[9px] mb-2">
|
||||
<span className="text-sky-400 font-semibold">Cut {front.probCutPct?.toFixed(1) ?? "—"}%</span>
|
||||
<span className="text-slate-400 font-semibold">Hold {holdPct?.toFixed(1) ?? "—"}%</span>
|
||||
<span className="text-red-400 font-semibold">Hike {front.probHikePct?.toFixed(1) ?? "—"}%</span>
|
||||
</div>
|
||||
|
||||
{front.rate25 !== null && front.rate75 !== null && (
|
||||
<div className="text-[9px] text-slate-500 mb-2">
|
||||
Taux SOFR composé implicite : <span className="text-slate-200 font-mono font-semibold">{(front.rate25 / 100).toFixed(2)}%</span> – <span className="text-slate-200 font-mono font-semibold">{(front.rate75 / 100).toFixed(2)}%</span>
|
||||
<span className="text-slate-600"> (25e–75e percentile{front.rateMode !== null ? `, mode ${(front.rateMode / 100).toFixed(2)}%` : ""})</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span className="text-[7px] text-slate-600 uppercase tracking-wider">Distribution complète (fenêtre)</span>
|
||||
<div className="space-y-[2px] mt-1">
|
||||
{front.distribution.map(b => {
|
||||
const m = b.rangeLabel.match(/(\d+)bps\s*-\s*(\d+)bps/);
|
||||
const label = m ? `${(parseInt(m[1]) / 100).toFixed(2)}-${(parseInt(m[2]) / 100).toFixed(2)}%` : b.rangeLabel;
|
||||
const isAnchor = b.rangeLabel === front.anchorRange;
|
||||
return (
|
||||
<div key={b.rangeLabel} className={`flex items-center gap-1.5 px-1 py-[2px] rounded ${isAnchor ? "bg-amber-500/8" : ""}`}>
|
||||
<span className={`text-[8px] w-[70px] shrink-0 font-mono tabular-nums ${isAnchor ? "text-amber-300 font-bold" : "text-slate-500"}`}>{label}</span>
|
||||
<div className="flex-1 bg-slate-700/20 rounded-full h-[4px] overflow-hidden">
|
||||
<div className="h-full rounded-full bg-amber-500/50" style={{ width: `${b.probPct}%` }} />
|
||||
</div>
|
||||
<span className="text-[8px] text-slate-400 w-[32px] text-right tabular-nums shrink-0">{b.probPct.toFixed(1)}%</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<p className="mt-1.5 text-[7px] text-slate-700 leading-snug">
|
||||
Distribution déduite des options sur futures SOFR 3 mois (CME), taux SOFR composé sur la fenêtre — méthodologie Atlanta Fed (Market Probability Tracker), alternative aux 30-day Fed Fund Futures. Mise à jour quotidienne.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
</div>
|
||||
|
||||
{/* ── IL footer (analyste InvestingLive) + source STIR ───────────────── */}
|
||||
@@ -1809,6 +1881,7 @@ export default function CurrencyCard({
|
||||
ratePath={ratePath}
|
||||
syncChartTab={syncOisChartTab}
|
||||
onChartTabChange={onOisChartTabChange}
|
||||
atlantaMpt={data?.atlantaFedMpt}
|
||||
/>
|
||||
)}
|
||||
{/* OIS — état indisponible */}
|
||||
|
||||
Reference in New Issue
Block a user