feat: remplace Atlanta Fed par des futures Euribor/SONIA réels pour EUR/GBP

- Retire l'onglet Atlanta Fed MPT (USD) : jugé sans valeur ajoutée réelle.
- EUR/GBP n'avaient qu'un unique point synthétique (agrégat year-end
  InvestingLive) gonflé en fausses réunions plates à 0% sur Courbe/
  Probabilités/Réunions — d'où l'aspect cassé. Investing.com n'a de Rate
  Monitor pré-calculé (comme pour la Fed) ni pour la BCE ni pour la BoE
  (404 confirmé), donc pas d'équivalent direct au pipeline USD.
- Remplace ce point unique par un vrai scrape des futures Euribor 3M
  (Eurex) et Three-Month SONIA (ICE), cotés sur investing.com (delayed,
  gratuit, pas de login) : conversion prix→taux (100−prix), mapping sur
  le calendrier réel BCE/BoE (échéance la plus proche ≥ date de réunion,
  fenêtre de grâce de 60j au-delà du dernier contrat coté, sinon on
  s'arrête plutôt que d'inventer), probabilité approximée avec la
  convention bps/25 déjà utilisée pour le fallback InvestingLive.
- Résolution plus grossière que l'USD (mensuelle EUR, trimestrielle GBP)
  et clairement étiquetée comme telle dans un footnote dédié.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
caty21
2026-07-09 11:55:35 +02:00
co-authored by Claude Sonnet 5
parent 003ead0189
commit 50ae11c3c4
11 changed files with 285 additions and 703 deletions
+12 -75
View File
@@ -16,7 +16,6 @@ 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";
@@ -49,7 +48,6 @@ interface MacroData {
indicators: Record<string, Ind | null>;
forecasts?: MacroForecasts | null;
moneySupplyM3?: MoneySupplyM3 | null;
atlantaFedMpt?: AtlantaFedMpt | null;
fetchedAt: string;
}
@@ -69,8 +67,8 @@ interface Props {
onCardTabChange?: (id: "overview" | "mispricing" | "focus") => void;
syncSignauxSlide?: "ois" | "cot" | "sent";
onSignauxSlideChange?: (id: "ois" | "cot" | "sent") => void;
syncOisChartTab?: "curve" | "probas" | "meetings" | "atlanta";
onOisChartTabChange?: (id: "curve" | "probas" | "meetings" | "atlanta") => void;
syncOisChartTab?: "curve" | "probas" | "meetings";
onOisChartTabChange?: (id: "curve" | "probas" | "meetings") => void;
isLoading?: boolean;
}
@@ -506,15 +504,14 @@ 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, atlantaMpt }: {
function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
ratePath: CBRatePath;
syncChartTab?: "curve" | "probas" | "meetings" | "atlanta";
onChartTabChange?: (id: "curve" | "probas" | "meetings" | "atlanta") => void;
atlantaMpt?: AtlantaFedMpt | null;
syncChartTab?: "curve" | "probas" | "meetings";
onChartTabChange?: (id: "curve" | "probas" | "meetings") => void;
}) {
const [localChartTab, setLocalChartTab] = useState<"curve" | "probas" | "meetings" | "atlanta">("curve");
const [localChartTab, setLocalChartTab] = useState<"curve" | "probas" | "meetings">("curve");
const chartTab = syncChartTab ?? localChartTab;
const setChartTab = (id: "curve" | "probas" | "meetings" | "atlanta") => {
const setChartTab = (id: "curve" | "probas" | "meetings") => {
setLocalChartTab(id);
onChartTabChange?.(id);
};
@@ -626,7 +623,6 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange, atlantaMpt
{ 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}
@@ -824,72 +820,14 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange, atlantaMpt
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>
)}
{(ratePath.currency === "EUR" || ratePath.currency === "GBP") && (
<p className="mt-1.5 text-[7px] text-slate-700 leading-snug">
Taux implicite déduit des futures {ratePath.currency === "EUR" ? "Euribor 3M (Eurex)" : "SONIA 3M (ICE)"}, cotés sur Investing.com pas de Rate Monitor pré-calculé comme pour l&apos;USD (inexistant pour la BCE/BoE), donc résolution {ratePath.currency === "EUR" ? "mensuelle (4 échéances cotées)" : "trimestrielle (2 échéances cotées)"} plutôt que par réunion : les réunions rapprochées peuvent partager la même valeur. Probabilité approximée (variation cumulée / 25bps), pas une distribution officielle.
</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"> (25e75e 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 ───────────────── */}
@@ -1885,7 +1823,6 @@ export default function CurrencyCard({
ratePath={ratePath}
syncChartTab={syncOisChartTab}
onChartTabChange={onOisChartTabChange}
atlantaMpt={data?.atlantaFedMpt}
/>
)}
{/* OIS — état indisponible */}