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:
caty21
2026-07-08 22:05:35 +02:00
parent 8c6f96efd7
commit 274c7f534e
8 changed files with 650 additions and 9 deletions
+166
View File
@@ -0,0 +1,166 @@
// Atlanta Fed Market Probability Tracker (MPT) — méthodologie alternative à
// Investing.com Fed Rate Monitor : au lieu des 30-day Fed Fund Futures, la
// Fed d'Atlanta déduit une distribution de probabilité du niveau du taux Fed
// à partir des OPTIONS sur futures SOFR 3 mois cotées au CME (le rationnel :
// la Fed conduit sa politique via des opérations repo, donc la distribution
// implicite du SOFR composé sur la fenêtre de référence du contrat permet de
// déduire les anticipations sur la fourchette cible du FOMC).
// Doc + téléchargements : https://www.atlantafed.org/cenfis/market-probability-tracker
//
// Format du fichier xl/worksheets/sheet3.xml (vérifié 2026-07-08) — format
// long, une ligne par (date, reference_start, target_range, field) :
// date : date d'observation (YYYY-MM-DD), publiée quotidiennement
// reference_start : date de début de la fenêtre de 3 mois référencée par le
// contrat SOFR (trimestrielle, style IMM)
// target_range : pour les champs agrégés (Rate:*, Prob: cut, Prob: hike)
// = fourchette cible ACTUELLE (contexte) ; pour les champs
// "Prob: XXXbps - YYYbps" = LA fourchette évaluée
// field / value : nom du champ + valeur (en bps, ex. "373.91" = 3.7391%)
//
// Pas de dépendance xlsx npm (le paquet du registre public a des CVE critiques
// non patchées depuis que SheetJS a arrêté d'y publier) — on lit le zip et le
// XML OOXML directement avec les modules Node natifs (zlib + regex).
import { writeFileSync, mkdirSync } from "fs";
import { inflateRawSync } from "zlib";
const XLSX_URL = "https://www.atlantafed.org/-/media/Project/Atlanta/FRBA/Documents/cenfis/market-probability-tracker/mpt_histdata.xlsx";
// ── Lecteur ZIP minimal (central directory + inflate raw deflate) ────────────
function readZip(buf) {
let eocdOff = -1;
for (let i = buf.length - 22; i >= Math.max(0, buf.length - 22 - 65536); i--) {
if (buf.readUInt32LE(i) === 0x06054b50) { eocdOff = i; break; }
}
if (eocdOff === -1) throw new Error("ZIP: End Of Central Directory introuvable");
const cdEntries = buf.readUInt16LE(eocdOff + 10);
const cdOffset = buf.readUInt32LE(eocdOff + 16);
const entries = {};
let p = cdOffset;
for (let i = 0; i < cdEntries; i++) {
if (buf.readUInt32LE(p) !== 0x02014b50) throw new Error(`ZIP: central directory corrompue à l'offset ${p}`);
const compMethod = buf.readUInt16LE(p + 10);
const compSize = buf.readUInt32LE(p + 20);
const nameLen = buf.readUInt16LE(p + 28);
const extraLen = buf.readUInt16LE(p + 30);
const commentLen = buf.readUInt16LE(p + 32);
const lfhOffset = buf.readUInt32LE(p + 42);
const name = buf.toString("utf8", p + 46, p + 46 + nameLen);
entries[name] = { compMethod, compSize, lfhOffset };
p += 46 + nameLen + extraLen + commentLen;
}
return {
read(name) {
const e = entries[name];
if (!e) return null;
const nameLen = buf.readUInt16LE(e.lfhOffset + 26);
const extraLen = buf.readUInt16LE(e.lfhOffset + 28);
const dataStart = e.lfhOffset + 30 + nameLen + extraLen;
const raw = buf.subarray(dataStart, dataStart + e.compSize);
return e.compMethod === 0 ? raw : inflateRawSync(raw);
},
};
}
function parseSharedStrings(xml) {
return Array.from(xml.matchAll(/<si>(?:<t[^>]*>([^<]*)<\/t>|<r>[\s\S]*?<\/r>)*<\/si>/g))
.map(m => m[0].match(/<t[^>]*>([^<]*)<\/t>/g)?.map(t => t.replace(/<[^>]+>/g, "")).join("") ?? "");
}
function parseSheetRows(xml, strings) {
const rowRe = /<row r="\d+"[^>]*>([\s\S]*?)<\/row>/g;
const cellRe = /<c r="([A-Z]+)\d+"(?:\s+t="([a-z]+)")?[^>]*>(?:<v>([^<]*)<\/v>)?<\/c>/g;
const rows = [];
let rm, first = true;
while ((rm = rowRe.exec(xml)) !== null) {
if (first) { first = false; continue; } // ligne d'en-tête
const cells = {};
let cm; cellRe.lastIndex = 0;
while ((cm = cellRe.exec(rm[1])) !== null) {
const [, ref, type, val] = cm;
cells[ref] = val === undefined ? null : (type === "s" ? strings[+val] : val);
}
rows.push(cells);
}
return rows;
}
function excelSerialToIso(serial) {
const epoch = Date.UTC(1899, 11, 30);
return new Date(epoch + Number(serial) * 86400000).toISOString().slice(0, 10);
}
// ── Fetch + parse ─────────────────────────────────────────────────────────────
async function fetchAtlantaMpt() {
const res = await fetch(XLSX_URL, {
headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const buf = Buffer.from(await res.arrayBuffer());
const zip = readZip(buf);
const strings = parseSharedStrings(zip.read("xl/sharedStrings.xml").toString("utf8"));
const rows = parseSheetRows(zip.read("xl/worksheets/sheet3.xml").toString("utf8"), strings);
// { A: date, B: reference_start (serial), C: target_range, D: field, E: value }
const records = rows
.filter(r => r.A && r.B && r.D && r.E !== null)
.map(r => ({ date: r.A, ref: Number(r.B), range: r.C, field: r.D, value: parseFloat(String(r.E).trim()) }));
if (!records.length) throw new Error("aucune ligne exploitable trouvée dans sheet3");
const maxDate = records.reduce((m, r) => (r.date > m ? r.date : m), "");
const latest = records.filter(r => r.date === maxDate);
const refStarts = [...new Set(latest.map(r => r.ref))].sort((a, b) => a - b);
const windows = refStarts.map(ref => {
const windowRows = latest.filter(r => r.ref === ref);
const get = (field) => windowRows.find(r => r.field === field)?.value ?? null;
const anchorRange = windowRows.find(r => r.field === "Prob: hike")?.range ?? null;
// La colonne "range" (C) vaut toujours la fourchette ACTUELLE/ancre pour
// toutes les lignes de la fenêtre (y compris les lignes de distribution) —
// la fourchette évaluée par chaque bucket est encodée dans le nom du champ
// lui-même ("Prob: 375bps - 400bps"), pas dans la colonne range.
const distribution = windowRows
.filter(r => r.field.startsWith("Prob: ") && r.field !== "Prob: cut" && r.field !== "Prob: hike")
.map(r => ({ rangeLabel: r.field.replace(/^Prob:\s*/, ""), probPct: r.value }))
.sort((a, b) => parseInt(a.rangeLabel) - parseInt(b.rangeLabel));
return {
windowStartIso: excelSerialToIso(ref),
anchorRange,
probCutPct: get("Prob: cut"),
probHikePct: get("Prob: hike"),
rate25: get("Rate: 25th percentile"),
rateMean: get("Rate: mean"),
rateMode: get("Rate: mode"),
rate75: get("Rate: 75th percentile"),
distribution,
};
});
return { asOf: maxDate, windows };
}
// ── Main ──────────────────────────────────────────────────────────────────────
try {
console.log("Fetching Atlanta Fed MPT historical data…");
const data = await fetchAtlantaMpt();
console.log(`✓ asOf=${data.asOf}, ${data.windows.length} fenêtres trimestrielles`);
console.log(` front window: ${data.windows[0].windowStartIso} — hike=${data.windows[0].probHikePct}% cut=${data.windows[0].probCutPct}%`);
mkdirSync("data", { recursive: true });
writeFileSync("data/atlanta-fed-mpt.json", JSON.stringify({
updated_at: new Date().toISOString().slice(0, 10),
source: "https://www.atlantafed.org/cenfis/market-probability-tracker",
note: "Probabilités dérivées des options sur futures SOFR 3 mois (CME) — méthodologie Atlanta Fed, mise à jour quotidienne. Fourchettes exprimées en bps (350bps-375bps = 3.50%-3.75%). Alternative aux 30-day Fed Fund Futures (Investing.com Fed Rate Monitor).",
...data,
}, null, 2) + "\n");
console.log("✓ Saved data/atlanta-fed-mpt.json");
} catch (e) {
console.error(`✗ Atlanta Fed MPT fetch failed: ${e.message}`);
process.exit(0); // échec silencieux — ne bloque pas le workflow, données précédentes conservées
}
+33
View File
@@ -0,0 +1,33 @@
name: Fetch Atlanta Fed Market Probability Tracker
on:
schedule:
- cron: '30 11 * * *' # quotidien 11h30 UTC (donnée mise à jour ~1x/jour par l'Atlanta Fed)
workflow_dispatch: # déclenchement manuel
permissions:
contents: write
jobs:
fetch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Fetch Atlanta Fed MPT (options SOFR)
run: node .github/scripts/fetch-atlanta-mpt.mjs
- name: Commit updated data (if changed)
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add data/atlanta-fed-mpt.json
git diff --staged --quiet || git commit -m "chore: update Atlanta Fed MPT data [skip ci]"
git push
+2
View File
@@ -6,6 +6,7 @@ export const dynamic = "force-dynamic";
import cpiOverridesRaw from "@/data/cpi_overrides.json";
import rateDecisionsRaw from "@/data/rate_decisions.json";
import moneySupplyM3Raw from "@/data/money-supply-m3.json";
import { getAtlantaFedMpt } from "@/lib/atlantaFedMpt";
import { fetchFFThisWeek, fetchFFEvents } from "@/lib/forexfactory";
import type { FFEvent } from "@/lib/forexfactory";
import { fetchTECoreInflation, fetchTEMoMInflation, fetchTEInflationYoY, fetchTECoreCPIMoM, fetchTECoreConsumerPricesIndex, fetchTEPPIMoM, fetchTECoreInflationPages, fetchTEInflationYoYPages, fetchTEAUDCommodityYoY, fetchTEGDPGrowthRate, fetchTEUnemploymentRate, fetchTESTIRRate, fetchTEEmploymentChange } from "@/lib/tecpi";
@@ -1426,6 +1427,7 @@ export async function GET(req: NextRequest) {
const data = {
currency, indicators,
moneySupplyM3: getMoneySupplyM3(currency),
atlantaFedMpt: currency === "USD" ? getAtlantaFedMpt() : null,
forecasts: {
// CPI — TE calendar forecast (priorité) puis ForexFactory
cpi: parseTeF(teCpiForecast?.cpiYoY) ?? ffForecasts.cpi ?? null,
+1 -1
View File
@@ -48,7 +48,7 @@ export default function Dashboard() {
const [globalMacroSlide, setGlobalMacroSlide] = useState<"mon"|"infl"|"cro"|"empl">("mon");
const [globalCardTab, setGlobalCardTab] = useState<"overview"|"mispricing"|"focus">("overview");
const [globalSignauxSlide, setGlobalSignauxSlide] = useState<"ois"|"cot"|"sent">("ois");
const [globalOisChartTab, setGlobalOisChartTab] = useState<"curve"|"probas"|"meetings">("curve");
const [globalOisChartTab, setGlobalOisChartTab] = useState<"curve"|"probas"|"meetings"|"atlanta">("curve");
const [macroSyncEnabled, setMacroSyncEnabled] = useState(false);
// ── Sentiment multi-paires Myfxbook → {CCY: {longPct, shortPct, pair}} ──────
+80 -7
View File
@@ -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"> (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 ───────────────── */}
@@ -1809,6 +1881,7 @@ export default function CurrencyCard({
ratePath={ratePath}
syncChartTab={syncOisChartTab}
onChartTabChange={onOisChartTabChange}
atlantaMpt={data?.atlantaFedMpt}
/>
)}
{/* OIS — état indisponible */}
+324
View File
@@ -0,0 +1,324 @@
{
"updated_at": "2026-07-08",
"source": "https://www.atlantafed.org/cenfis/market-probability-tracker",
"note": "Probabilités dérivées des options sur futures SOFR 3 mois (CME) — méthodologie Atlanta Fed, mise à jour quotidienne. Fourchettes exprimées en bps (350bps-375bps = 3.50%-3.75%). Alternative aux 30-day Fed Fund Futures (Investing.com Fed Rate Monitor).",
"asOf": "2026-07-07",
"windows": [
{
"windowStartIso": "2026-09-16",
"anchorRange": "350bps - 375bps",
"probCutPct": 1.11,
"probHikePct": 72.36,
"rate25": 373.91,
"rateMean": 389.01,
"rateMode": 377.79,
"rate75": 402.99,
"distribution": [
{
"rangeLabel": "350bps - 375bps",
"probPct": 27.21
},
{
"rangeLabel": "375bps - 400bps",
"probPct": 45.73
},
{
"rangeLabel": "400bps - 425bps",
"probPct": 21.41
},
{
"rangeLabel": "425bps - 450bps",
"probPct": 5.65
}
]
},
{
"windowStartIso": "2026-12-16",
"anchorRange": "350bps - 375bps",
"probCutPct": 6.94,
"probHikePct": 74.76,
"rate25": 374.76,
"rateMean": 402.98,
"rateMode": 380.27,
"rate75": 430.76,
"distribution": [
{
"rangeLabel": "325bps - 350bps",
"probPct": 5.09
},
{
"rangeLabel": "350bps - 375bps",
"probPct": 19.04
},
{
"rangeLabel": "375bps - 400bps",
"probPct": 24.57
},
{
"rangeLabel": "400bps - 425bps",
"probPct": 22.23
},
{
"rangeLabel": "425bps - 450bps",
"probPct": 18.38
},
{
"rangeLabel": "450bps - 475bps",
"probPct": 7.52
},
{
"rangeLabel": "475bps - 500bps",
"probPct": 2.16
},
{
"rangeLabel": "500bps - 525bps",
"probPct": 1.01
}
]
},
{
"windowStartIso": "2027-03-17",
"anchorRange": "350bps - 375bps",
"probCutPct": 12.73,
"probHikePct": 74.32,
"rate25": 373.96,
"rateMean": 408.93,
"rateMode": 397.83,
"rate75": 447.02,
"distribution": [
{
"rangeLabel": "225bps - 250bps",
"probPct": 0.55
},
{
"rangeLabel": "250bps - 275bps",
"probPct": 0.81
},
{
"rangeLabel": "275bps - 300bps",
"probPct": 1.1
},
{
"rangeLabel": "300bps - 325bps",
"probPct": 2.25
},
{
"rangeLabel": "325bps - 350bps",
"probPct": 6.3
},
{
"rangeLabel": "350bps - 375bps",
"probPct": 13.56
},
{
"rangeLabel": "375bps - 400bps",
"probPct": 19.03
},
{
"rangeLabel": "400bps - 425bps",
"probPct": 18.87
},
{
"rangeLabel": "425bps - 450bps",
"probPct": 15.35
},
{
"rangeLabel": "450bps - 475bps",
"probPct": 10.6
},
{
"rangeLabel": "475bps - 500bps",
"probPct": 5.95
},
{
"rangeLabel": "500bps - 525bps",
"probPct": 2.97
},
{
"rangeLabel": "525bps - 550bps",
"probPct": 1.62
},
{
"rangeLabel": "550bps - 575bps",
"probPct": 1.04
}
]
},
{
"windowStartIso": "2027-06-16",
"anchorRange": "350bps - 375bps",
"probCutPct": 19.16,
"probHikePct": 69.12,
"rate25": 363.67,
"rateMean": 407,
"rateMode": 400.61,
"rate75": 452.48,
"distribution": [
{
"rangeLabel": "150bps - 175bps",
"probPct": 0.39
},
{
"rangeLabel": "175bps - 200bps",
"probPct": 0.42
},
{
"rangeLabel": "200bps - 225bps",
"probPct": 0.56
},
{
"rangeLabel": "225bps - 250bps",
"probPct": 0.88
},
{
"rangeLabel": "250bps - 275bps",
"probPct": 1.41
},
{
"rangeLabel": "275bps - 300bps",
"probPct": 2.31
},
{
"rangeLabel": "300bps - 325bps",
"probPct": 4.1
},
{
"rangeLabel": "325bps - 350bps",
"probPct": 7.54
},
{
"rangeLabel": "350bps - 375bps",
"probPct": 12.3
},
{
"rangeLabel": "375bps - 400bps",
"probPct": 16.01
},
{
"rangeLabel": "400bps - 425bps",
"probPct": 16.15
},
{
"rangeLabel": "425bps - 450bps",
"probPct": 13.07
},
{
"rangeLabel": "450bps - 475bps",
"probPct": 9.11
},
{
"rangeLabel": "475bps - 500bps",
"probPct": 5.9
},
{
"rangeLabel": "500bps - 525bps",
"probPct": 3.73
},
{
"rangeLabel": "525bps - 550bps",
"probPct": 2.41
},
{
"rangeLabel": "550bps - 575bps",
"probPct": 1.65
},
{
"rangeLabel": "575bps - 600bps",
"probPct": 1.19
},
{
"rangeLabel": "600bps - 625bps",
"probPct": 0.86
}
]
},
{
"windowStartIso": "2027-09-15",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 349.37,
"rateMean": 401.41,
"rateMode": 391.24,
"rate75": 451.98,
"distribution": []
},
{
"windowStartIso": "2027-12-15",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 336.41,
"rateMean": 394.72,
"rateMode": 386.96,
"rate75": 448.58,
"distribution": []
},
{
"windowStartIso": "2028-03-15",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 325.08,
"rateMean": 390.04,
"rateMode": 382.35,
"rate75": 450.08,
"distribution": []
},
{
"windowStartIso": "2028-06-21",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 315.28,
"rateMean": 387.51,
"rateMode": 382.48,
"rate75": 451.82,
"distribution": []
},
{
"windowStartIso": "2028-09-20",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 311.02,
"rateMean": 386.52,
"rateMode": 383.63,
"rate75": 452.56,
"distribution": []
},
{
"windowStartIso": "2028-12-20",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 307.04,
"rateMean": 386.45,
"rateMode": 376.34,
"rate75": 454.85,
"distribution": []
},
{
"windowStartIso": "2029-03-21",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 291.57,
"rateMean": 386.97,
"rateMode": 363.95,
"rate75": 461.23,
"distribution": []
},
{
"windowStartIso": "2029-06-20",
"anchorRange": null,
"probCutPct": null,
"probHikePct": null,
"rate25": 268.96,
"rateMean": 387,
"rateMode": 354.61,
"rate75": 506.12,
"distribution": []
}
]
}
+43
View File
@@ -0,0 +1,43 @@
// lib/atlantaFedMpt.ts
// Atlanta Fed Market Probability Tracker — méthodologie alternative aux 30-day
// Fed Fund Futures (Investing.com Fed Rate Monitor) : distribution du niveau
// de taux Fed déduite des options sur futures SOFR 3 mois (CME). Donnée
// statique maintenue par .github/workflows/fetch-atlanta-mpt.yml (quotidien).
// Source : https://www.atlantafed.org/cenfis/market-probability-tracker
import atlantaMptRaw from "@/data/atlanta-fed-mpt.json";
export interface AtlantaMptDistributionBucket {
rangeLabel: string; // "375bps - 400bps"
probPct: number;
}
export interface AtlantaMptWindow {
windowStartIso: string; // début de la fenêtre 3 mois référencée par le contrat SOFR
anchorRange: string | null; // fourchette cible FOMC à la date d'observation
probCutPct: number | null;
probHikePct: number | null;
rate25: number | null; // bps
rateMean: number | null; // bps
rateMode: number | null; // bps
rate75: number | null; // bps
distribution: AtlantaMptDistributionBucket[];
}
export interface AtlantaFedMpt {
updated_at: string;
asOf: string; // date d'observation des données (publication Atlanta Fed)
source: string;
note: string;
windows: AtlantaMptWindow[];
}
export function getAtlantaFedMpt(): AtlantaFedMpt | null {
try {
const data = atlantaMptRaw as AtlantaFedMpt;
if (!data?.windows?.length) return null;
return data;
} catch {
return null;
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long