mirror of
https://github.com/caty21/forex-dashboard.git
synced 2026-07-27 20:37:45 +00:00
fix(OIS): fusion STIR+IL pour proba 1ere reunion — IL override si ecart >8pp, STIR affiche en tooltip/parenthese
This commit is contained in:
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user