fix(OIS): fusion STIR+IL pour proba 1ere reunion — IL override si ecart >8pp, STIR affiche en tooltip/parenthese

This commit is contained in:
caty21
2026-06-29 13:49:52 +02:00
parent 014be8f4e8
commit 654958ec4d
2 changed files with 60 additions and 11 deletions
+16 -3
View File
@@ -816,7 +816,7 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
{/* Liste des réunions */}
<div className="mt-2 pt-2 border-t border-slate-700/30 space-y-[3px]">
{scenariosData.map(d => {
{scenariosData.map((d, i) => {
const isPeak = ratePath.peakMeeting?.dateIso === d.dateIso;
const isDown = d.rate < currentRate - 0.001;
const isUp = d.rate > currentRate + 0.001;
@@ -824,6 +824,9 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
const barW = Math.max(4, Math.min(100, ((d.rate - minR2) / range) * 100));
const cumBps = d.cumulBps;
const cumStr = cumBps === 0 ? null : `${cumBps > 0 ? "+" : ""}${cumBps}`;
// Pour la 1ère réunion : montrer STIR original si IL a surchargé la valeur
const stirProb = i === 0 ? ratePath.ilCurrent?.stirProbPct : undefined;
const ilFused = stirProb !== undefined && Math.abs(d.prob - stirProb) > 5;
return (
<div key={d.label}
className={`flex items-center gap-1.5 rounded-md px-1 py-[2px] ${isPeak ? "bg-amber-500/8" : ""}`}
@@ -842,9 +845,19 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
{cumStr}bps
</span>
)}
{d.prob > 0 && (
{/* Probabilité : si fusion IL/STIR, afficher les deux sources */}
{ilFused ? (
<span className="flex items-center gap-0.5 shrink-0">
<span className={`text-[8px] font-bold tabular-nums ${isDown ? "text-sky-400" : "text-red-400"}`} title="Probabilité analyste InvestingLive">
{d.prob.toFixed(0)}%
</span>
<span className="text-[6px] text-slate-700" title={`Probabilité STIR/IC : ${stirProb?.toFixed(0)}%`}>
({stirProb?.toFixed(0)}%)
</span>
</span>
) : d.prob > 0 ? (
<span className="text-[7px] text-slate-600 w-5 text-right shrink-0">{d.prob.toFixed(0)}%</span>
)}
) : null}
</div>
);
})}
+44 -8
View File
@@ -29,7 +29,8 @@ export interface ILWeeklyDelta {
export interface ILCurrent {
bpsYearEnd: number; // bps fin d'an selon l'article IL courant
probPct: number; // probabilité de move à la prochaine réunion (IL)
probPct: number; // probabilité de move à la prochaine réunion (IL analyste)
stirProbPct?: number; // probabilité originale STIR/IC (avant fusion IL)
isNoChange: boolean; // l'analyste anticipe un statu quo
isCut: boolean; // l'analyste anticipe une baisse
articleDate: string; // date de publication de l'article (YYYY-MM-DD)
@@ -351,7 +352,7 @@ export async function fetchAllCBPaths(): Promise<RateProbData> {
if (rbnzPath) data["NZD"] = rbnzPath;
}
// Enrichissement IL : deltas hebdo pour toutes les devises
// Enrichissement IL : fusion proba première réunion + deltas hebdo
for (const [ccyStr, ilEntry] of Object.entries(ilData)) {
const ccy = ccyStr as keyof RateProbData;
const path = data[ccy];
@@ -374,15 +375,50 @@ export async function fetchAllCBPaths(): Promise<RateProbData> {
};
}
const ilProb = ilEntry.nextMeetingProbPct;
const ilIsCut = !ilEntry.nextMeetingIsHike && !ilEntry.nextMeetingIsNoChange;
const m0 = path.meetings[0];
const stirProb = m0?.probMovePct ?? 0;
const ilCurrent: ILCurrent = {
bpsYearEnd: ilEntry.bpsYearEnd,
probPct: ilEntry.nextMeetingProbPct,
isNoChange: ilEntry.nextMeetingIsNoChange,
isCut: !ilEntry.nextMeetingIsHike && !ilEntry.nextMeetingIsNoChange,
articleDate: ilEntry.publishedDate,
bpsYearEnd: ilEntry.bpsYearEnd,
probPct: ilProb,
stirProbPct: stirProb || undefined,
isNoChange: ilEntry.nextMeetingIsNoChange,
isCut: ilIsCut,
articleDate: ilEntry.publishedDate,
};
data[ccy] = { ...path, yearEndImplied, ilCurrent, ...(ilDelta ? { ilDelta } : {}) };
// Fusion STIR + IL pour la première réunion :
// Si l'IL a une proba valide ET qu'elle diffère du STIR de plus de 8pp → on fusionne
// (le STIR IC peut avoir des artefacts de parsing ; l'analyste IL lit la même donnée proprement)
let updatedMeetings = path.meetings;
if (m0 && ilProb > 0 && !ilEntry.nextMeetingIsNoChange && Math.abs(ilProb - stirProb) > 8) {
const updatedM0: RateProbMeeting = {
...m0,
probMovePct: ilProb,
probIsCut: ilIsCut,
changeBps: ilProb > 50 ? (ilIsCut ? -25 : 25) : 0,
impliedRate: ilProb > 50
? parseFloat((path.currentRate + (ilIsCut ? -0.25 : 0.25)).toFixed(4))
: path.currentRate,
};
updatedMeetings = [updatedM0, ...path.meetings.slice(1)];
}
// Recalcule peakMeeting après fusion
const peakMeeting = updatedMeetings.length
? updatedMeetings.reduce((best, m) => m.probMovePct > best.probMovePct ? m : best, updatedMeetings[0])
: null;
data[ccy] = {
...path,
meetings: updatedMeetings,
peakMeeting: peakMeeting && peakMeeting.probMovePct > 0 ? peakMeeting : path.peakMeeting,
yearEndImplied,
ilCurrent,
...(ilDelta ? { ilDelta } : {}),
};
}
return data;