feat(OIS): onglet Probabilites - barres proba par reunion + bps cumules, defaut Probabilites avant Rate Path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
caty21
2026-06-29 12:18:51 +02:00
parent 34382c72dd
commit a37859ad40
2 changed files with 49 additions and 34 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ export default function Dashboard() {
const [globalMacroSlide, setGlobalMacroSlide] = useState<"mon"|"infl"|"cro"|"empl">("mon"); const [globalMacroSlide, setGlobalMacroSlide] = useState<"mon"|"infl"|"cro"|"empl">("mon");
const [globalCardTab, setGlobalCardTab] = useState<"overview"|"mispricing"|"focus">("overview"); const [globalCardTab, setGlobalCardTab] = useState<"overview"|"mispricing"|"focus">("overview");
const [globalSignauxSlide, setGlobalSignauxSlide] = useState<"ois"|"cot"|"sent">("ois"); const [globalSignauxSlide, setGlobalSignauxSlide] = useState<"ois"|"cot"|"sent">("ois");
const [globalOisChartTab, setGlobalOisChartTab] = useState<"curve"|"implied"|"scenarios">("curve"); const [globalOisChartTab, setGlobalOisChartTab] = useState<"curve"|"implied"|"scenarios">("scenarios");
const [macroSyncEnabled, setMacroSyncEnabled] = useState(false); const [macroSyncEnabled, setMacroSyncEnabled] = useState(false);
// ── Sentiment multi-paires Myfxbook → {CCY: {longPct, shortPct, pair}} ────── // ── Sentiment multi-paires Myfxbook → {CCY: {longPct, shortPct, pair}} ──────
+48 -33
View File
@@ -482,7 +482,7 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
syncChartTab?: "curve" | "implied" | "scenarios"; syncChartTab?: "curve" | "implied" | "scenarios";
onChartTabChange?: (id: "curve" | "implied" | "scenarios") => void; onChartTabChange?: (id: "curve" | "implied" | "scenarios") => void;
}) { }) {
const [localChartTab, setLocalChartTab] = useState<"curve" | "implied" | "scenarios">("curve"); const [localChartTab, setLocalChartTab] = useState<"curve" | "implied" | "scenarios">("scenarios");
const chartTab = syncChartTab ?? localChartTab; const chartTab = syncChartTab ?? localChartTab;
const setChartTab = (id: "curve" | "implied" | "scenarios") => { const setChartTab = (id: "curve" | "implied" | "scenarios") => {
setLocalChartTab(id); setLocalChartTab(id);
@@ -538,6 +538,8 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
rate: +m.impliedRate.toFixed(3), rate: +m.impliedRate.toFixed(3),
prob: m.probMovePct, prob: m.probMovePct,
isCut: m.probIsCut, isCut: m.probIsCut,
changeBps: m.changeBps,
cumulBps: Math.round((m.impliedRate - currentRate) * 100),
})); }));
// Y-axis domain for Rate Curve // Y-axis domain for Rate Curve
@@ -619,9 +621,9 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
{/* Tab buttons */} {/* Tab buttons */}
<div className="flex gap-1 mb-2"> <div className="flex gap-1 mb-2">
{([ {([
{ id: "scenarios" as const, label: "Probabilités" },
{ id: "curve" as const, label: "Rate Path" }, { id: "curve" as const, label: "Rate Path" },
{ id: "implied" as const, label: "Implied Pts" }, { id: "implied" as const, label: "Implied Pts" },
{ id: "scenarios" as const, label: "Scénarios" },
]).map(t => ( ]).map(t => (
<button <button
key={t.id} key={t.id}
@@ -737,41 +739,54 @@ function OISEnhancedBlock({ ratePath, syncChartTab, onChartTabChange }: {
</div> </div>
)} )}
{/* Chart 3 — Scénarios : taux implicite par réunion (barres horizontales) */} {/* Chart 3 — Probabilités : barre de probabilité par réunion + bps cumulés */}
{chartTab === "scenarios" && ( {chartTab === "scenarios" && (
<div> <div>
<div className="flex items-center mb-1.5"> <div className="flex items-center mb-1.5 gap-2">
<span className="text-[8px] text-slate-600">Taux implicite par réunion</span> <span className="text-[8px] text-slate-500 uppercase tracking-wider">Probabilités par réunion</span>
<div className="ml-auto flex items-center gap-4 text-[7px] text-slate-700 uppercase tracking-wide"> <div className="ml-auto flex items-center gap-2">
<span>Taux prévu</span> <span className="flex items-center gap-1 text-[7px] text-sky-400"><span className="inline-block w-1.5 h-1.5 rounded-full bg-sky-400" />Baisse</span>
<span title="Probabilité de mouvement de taux à cette réunion (OIS/futures)">Proba mvt</span> <span className="flex items-center gap-1 text-[7px] text-amber-400"><span className="inline-block w-1.5 h-1.5 rounded-full bg-amber-400" />Hold</span>
<span className="flex items-center gap-1 text-[7px] text-red-400"><span className="inline-block w-1.5 h-1.5 rounded-full bg-red-400" />Hausse</span>
</div>
</div> </div>
</div> <div className="space-y-1.5">
<div className="space-y-1"> {scenariosData.map(d => {
{(() => { const hasProb = d.prob > 0;
const maxR2 = Math.max(...scenariosData.map(d => d.rate), currentRate); const mlIsMove = d.prob >= 50;
const minR2 = Math.min(...scenariosData.map(d => d.rate), currentRate) - 0.05; const displayProb = hasProb ? (mlIsMove ? d.prob : 100 - d.prob) : 0;
const range = maxR2 - minR2 || 0.25; const barColor = !hasProb
return scenariosData.map(d => { ? "#334155"
const barW = Math.max(5, Math.min(100, ((d.rate - minR2) / range) * 100)); : mlIsMove ? (d.isCut ? "#38bdf8" : "#f87171") : "#f59e0b";
const isDown = d.rate < currentRate - 0.001; const dirArrow = !hasProb ? "?" : mlIsMove ? (d.isCut ? "▼" : "▲") : "—";
const isUp = d.rate > currentRate + 0.001; const arrowCls = !hasProb ? "text-slate-700" : mlIsMove ? (d.isCut ? "text-sky-400" : "text-red-400") : "text-amber-400";
const barCl = isDown ? "bg-sky-500/70" : isUp ? "bg-red-500/70" : "bg-amber-500/60"; const isPeak = ratePath.peakMeeting?.dateIso === d.dateIso;
const isPeak = ratePath.peakMeeting?.dateIso === d.dateIso; const cum = d.cumulBps;
return ( const cumLabel = cum === 0 ? "0bps" : `${cum > 0 ? "+" : ""}${cum}bps`;
<div key={d.label} className="flex items-center gap-1.5"> const cumCls = cum < 0 ? "text-sky-400" : cum > 0 ? "text-red-400" : "text-slate-600";
<span className={`text-[8px] w-9 shrink-0 ${isPeak ? "text-amber-300 font-bold" : "text-slate-600"}`}> return (
{d.label}{isPeak ? "●" : ""} <div key={d.label} className="flex items-center gap-1.5">
</span> <span className={`text-[8px] w-9 shrink-0 font-mono tabular-nums ${isPeak ? "font-bold text-amber-300" : "text-slate-500"}`}>
<div className="flex-1 bg-slate-700/30 rounded-full h-2.5 overflow-hidden"> {d.label}
<div className={`h-full ${barCl} rounded-full`} style={{ width: `${barW}%` }} /> </span>
</div> <div className="flex-1 bg-slate-700/30 rounded-full h-2 overflow-hidden">
<span className="text-[8px] font-mono text-slate-300 w-10 text-right shrink-0">{d.rate.toFixed(2)}%</span> <div
<span className="text-[7px] text-slate-600 w-6 text-right shrink-0">{d.prob.toFixed(0)}%</span> className="h-full rounded-full transition-all duration-300"
style={{ width: `${displayProb}%`, backgroundColor: barColor, opacity: 0.78 }}
/>
</div> </div>
); {hasProb ? (
}); <span className="text-[9px] font-bold tabular-nums w-7 text-right shrink-0" style={{ color: barColor }}>
})()} {Math.round(displayProb)}%
</span>
) : (
<span className="text-[8px] text-slate-700 w-7 text-right shrink-0">n/a</span>
)}
<span className={`text-[9px] w-4 text-center shrink-0 ${arrowCls}`}>{dirArrow}</span>
<span className={`text-[8px] font-mono tabular-nums w-12 text-right shrink-0 ${cumCls}`}>{cumLabel}</span>
</div>
);
})}
</div> </div>
</div> </div>
)} )}