From 764070a63aebac5bfb41be68d4f97c6c216696ae Mon Sep 17 00:00:00 2001 From: Capucine Gest Date: Mon, 8 Jun 2026 19:51:57 +0200 Subject: [PATCH] fix: yearEndImplied now uses last meeting of current year (not 380d window) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs fixed in fetchAllCBPaths / fetchCBPath: - yearEndImplied was taking the last meeting within 380 days (~mid-2027) instead of the last meeting of the current calendar year (Dec 2026) — caused bps to be significantly overstated (e.g. USD showed +40 instead of +29.6) - IL article bpsYearEnd was overriding rateprobability.com yearEndImplied for all currencies; now IL only overrides CHF (no RP coverage); other 7 CBs use live OIS Dashboard yearEndBps now matches rateprobability.com raw API exactly for all CBs. Co-Authored-By: Claude Sonnet 4.6 --- lib/rateprobability.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/rateprobability.ts b/lib/rateprobability.ts index d45e217..aef5924 100644 --- a/lib/rateprobability.ts +++ b/lib/rateprobability.ts @@ -127,9 +127,13 @@ async function fetchCBPath(ccy: Currency, slug: string): Promise m.probMovePct > best.probMovePct ? m : best, meetings[0]) : null; - const yearEndImplied = meetings.length > 0 - ? meetings[meetings.length - 1].impliedRate - : null; + // yearEndImplied = taux implicite à la dernière réunion de l'année en cours (pas mid-2027) + const currentYear = new Date().getFullYear(); + const yearEndIso = `${currentYear}-12-31`; + const meetsThisYear = meetings.filter(m => m.dateIso <= yearEndIso); + const yearEndImplied = meetsThisYear.length > 0 + ? meetsThisYear[meetsThisYear.length - 1].impliedRate + : meetings.length > 0 ? meetings[0].impliedRate : null; return { currency: ccy, asOf, currentRate, meetings, peakMeeting, yearEndImplied }; } catch { return null; } @@ -225,8 +229,9 @@ export async function fetchAllCBPaths(): Promise { if (snbPath) data["CHF"] = snbPath; } - // Enrichir yearEndImplied avec bpsYearEnd de IL (Giuseppe Dellamotta — source humaine) - // + calculer ilDelta (Δ vs article semaine précédente) pour les flèches de tendance. + // IL enrichment : yearEndImplied uniquement pour CHF (RP ne couvre pas la SNB). + // Pour les 7 autres devises, on garde yearEndImplied de rateprobability.com (données OIS live). + // ilDelta (flèches hebdo) reste calculé pour toutes les devises via l'article IL. for (const [ccyStr, ilEntry] of Object.entries(ilData)) { const ccy = ccyStr as keyof RateProbData; const path = data[ccy]; @@ -234,10 +239,13 @@ export async function fetchAllCBPaths(): Promise { if (typeof ilEntry.bpsYearEnd !== "number") continue; if (ilEntry.nextMeetingIsNoChange && Math.abs(ilEntry.bpsYearEnd) < 10) continue; - const ilYearEnd = parseFloat((path.currentRate + ilEntry.bpsYearEnd / 100).toFixed(4)); + // CHF only : pas de données RP → IL est la seule source pour yearEndImplied + const yearEndImplied = ccy === "CHF" + ? parseFloat((path.currentRate + ilEntry.bpsYearEnd / 100).toFixed(4)) + : path.yearEndImplied; // Delta semaine/semaine depuis l'article précédent de Giuseppe - let ilDelta: import("./rateprobability").ILWeeklyDelta | undefined; + let ilDelta: ILWeeklyDelta | undefined; const prevEntry = ilPrev[ccy]; if (prevEntry && prevDate) { ilDelta = { @@ -248,7 +256,7 @@ export async function fetchAllCBPaths(): Promise { }; } - data[ccy] = { ...path, yearEndImplied: ilYearEnd, ...(ilDelta ? { ilDelta } : {}) }; + data[ccy] = { ...path, yearEndImplied, ...(ilDelta ? { ilDelta } : {}) }; } return data;