feat: OISEnhancedBlock — rate curve, implied pts, scénarios charts per CB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
caty21
2026-06-27 14:12:05 +02:00
co-authored by Claude Sonnet 4.6
parent a3d847ce59
commit 9da463a424
12 changed files with 1777 additions and 754 deletions
+1020 -534
View File
File diff suppressed because it is too large Load Diff
+24 -23
View File
@@ -95,18 +95,16 @@ export default function DriversBar({ drivers }: Props) {
const {
vix, vixDelta,
sp500, sp500ChangePct,
btc, btcChange24h,
btc, btcDeltaPct, btcChange24h,
hySpread, igSpread,
us10y, us2y, curveSlope,
gold, goldDelta,
silver, silverDelta,
brent, brentDelta,
wti, wtiDelta,
gold, goldDeltaPct,
silver, silverDeltaPct,
brent, brentDelta, brentDeltaPct,
wti, wtiDelta, wtiDeltaPct,
} = drivers;
const dxy = (drivers as DriverData & { dxy?: number | null }).dxy ?? null;
const dxyDelta = (drivers as DriverData & { dxyDelta?: number | null }).dxyDelta ?? null;
const riskOff = (vix ?? 0) > 25 || (hySpread ?? 0) > 500;
const riskOff = (vix ?? 0) > 25 || (hySpread ?? 0) > 500;
return (
<div className="mb-4 bg-slate-950/60 border border-slate-800 rounded-xl p-3 space-y-2">
@@ -133,9 +131,10 @@ export default function DriversBar({ drivers }: Props) {
accent={(vix ?? 0) > 25 ? "red" : undefined}
tooltip="Clôture actuelle clôture précédente (Yahoo Finance)." />
<Tile label="S&P 500" value={sp500} dec={0} delta={sp500ChangePct} deltaPct deltaDec={2}
tooltip="% vs clôture précédente (Yahoo Finance)." />
<Tile label="Bitcoin" value={btc} dec={0} unit=" $" delta={btcChange24h} deltaPct deltaDec={2}
tooltip="Variation 24h (Binance / CoinGecko)." />
tooltip="% vs clôture J-1 (Business Insider, cache 1 min). Fallback : Yahoo Finance." />
<Tile label="BTC/USD" value={btc} dec={0} unit=" $"
delta={btcDeltaPct ?? btcChange24h} deltaPct deltaDec={2}
tooltip="% vs clôture J-1 (investing.com, cache 1 min). Fallback : Binance/CoinGecko 24h." />
{/* ── Crédit ────────────────────────────────────────────── */}
<div className="w-px bg-slate-800 self-stretch mx-0.5 hidden sm:block" />
@@ -146,11 +145,9 @@ export default function DriversBar({ drivers }: Props) {
<Tile label="IG Spread" value={igSpread} dec={0} unit=" bps"
tooltip="Investment Grade spread vs Treasuries US." />
{/* ── FX / Taux ─────────────────────────────────────────── */}
{/* ── Taux ──────────────────────────────────────────────── */}
<div className="w-px bg-slate-800 self-stretch mx-0.5 hidden sm:block" />
<GroupLabel label="FX / Taux" />
<Tile label="DXY" value={dxy} dec={2} delta={dxyDelta} deltaDec={2}
tooltip="ICE Dollar Index Futures (DX=F) — Yahoo Finance, cache 5 min." />
<GroupLabel label="Taux" />
<Tile
label="Crb 2-10" value={curveSlope} dec={0} unit=" bps"
accent={(curveSlope ?? 0) < -50 ? "amber" : undefined}
@@ -160,14 +157,18 @@ export default function DriversBar({ drivers }: Props) {
{/* ── Commodités ────────────────────────────────────────── */}
<div className="w-px bg-slate-800 self-stretch mx-0.5 hidden sm:block" />
<GroupLabel label="Commodités" />
<Tile label="Or $/oz" value={gold} dec={0} delta={goldDelta} deltaDec={1}
tooltip="Delta intraday closeopen (Stooq)." />
<Tile label="Argent $/oz" value={silver} dec={2} delta={silverDelta}
tooltip="Delta intraday closeopen (Stooq)." />
<Tile label="Brent $/b" value={brent} dec={1} delta={brentDelta} deltaDec={1}
tooltip="Delta intraday closeopen (Stooq)." />
<Tile label="WTI $/b" value={wti} dec={1} delta={wtiDelta} deltaDec={1}
tooltip="Delta intraday closeopen (Stooq)." />
<Tile label="Or $/oz" value={gold} dec={0}
delta={goldDeltaPct} deltaPct deltaDec={2}
tooltip="% vs clôture J-1 (Business Insider, cache 1 min). Fallback : Yahoo GC=F." />
<Tile label="Argent $/oz" value={silver} dec={2}
delta={silverDeltaPct} deltaPct deltaDec={2}
tooltip="% vs clôture J-1 (Business Insider, cache 1 min). Fallback : Yahoo SI=F." />
<Tile label="Brent $/b" value={brent} dec={1}
delta={brentDeltaPct ?? brentDelta} deltaPct={brentDeltaPct !== null} deltaDec={2}
tooltip={`% évolution vs clôture J-1 (abcbourse.com — Six Financial Information, temps réel).${brentDelta != null ? `\nDelta: ${brentDelta > 0 ? "+" : ""}${brentDelta.toFixed(2)} $` : ""}`} />
<Tile label="WTI $/b" value={wti} dec={1}
delta={wtiDeltaPct ?? wtiDelta} deltaPct={wtiDeltaPct !== null} deltaDec={2}
tooltip={`% évolution vs clôture J-1 (Business Insider, cache 1 min). Fallback : Yahoo Finance CL=F.${wtiDelta != null ? `\nDelta: ${wtiDelta > 0 ? "+" : ""}${wtiDelta.toFixed(2)} $` : ""}`} />
</div>
</div>
+8 -4
View File
@@ -93,8 +93,12 @@ export default function NewsTab({ items, loading, onRefresh }: Props) {
if (priorityOnly && !isPriorityItem(item)) return false;
if (filterCcy !== "ALL" && !item.impacts.some(i => i.ccy === filterCcy)) return false;
if (filterCat !== "ALL" && !item.categories.includes(filterCat)) return false;
if (filterDir !== "all" && filterCcy !== "ALL") {
if (!item.impacts.some(i => i.ccy === filterCcy && i.direction === filterDir)) return false;
if (filterDir !== "all") {
if (filterCcy !== "ALL") {
if (!item.impacts.some(i => i.ccy === filterCcy && i.direction === filterDir)) return false;
} else {
if (!item.impacts.some(i => i.direction === filterDir)) return false;
}
}
return true;
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -251,8 +255,8 @@ export default function NewsTab({ items, loading, onRefresh }: Props) {
{filterCcy !== "ALL" && ` · ${CCY_FLAGS[filterCcy]} ${filterCcy}`}
{filterCat !== "ALL" && ` · ${filterCat}`}
</span>
{(filterCcy !== "ALL" || filterCat !== "ALL" || filterDir !== "all") && (
<button onClick={() => { setFilterCcy("ALL"); setFilterCat("ALL"); setFilterDir("all"); }}
{(filterCcy !== "ALL" || filterCat !== "ALL" || filterDir !== "all" || priorityOnly) && (
<button onClick={() => { setFilterCcy("ALL"); setFilterCat("ALL"); setFilterDir("all"); setPriorityOnly(false); }}
className="text-[10px] text-slate-600 hover:text-slate-400">
Effacer filtres ×
</button>
+66 -35
View File
@@ -15,6 +15,7 @@ interface YieldsData {
interface Props {
yieldsData: YieldsData | null;
fxDayPct?: Record<string, number | null> | null;
}
// ── Helpers ───────────────────────────────────────────────────────────────────
@@ -54,7 +55,7 @@ function carryLabel(bps: number): { label: string; color: string } {
// ── Component ─────────────────────────────────────────────────────────────────
export default function YieldsTab({ yieldsData }: Props) {
export default function YieldsTab({ yieldsData, fxDayPct }: Props) {
if (!yieldsData) {
return (
<div className="flex items-center justify-center h-48 text-gray-400 text-sm">
@@ -99,21 +100,35 @@ export default function YieldsTab({ yieldsData }: Props) {
const topCarry = carryPairs.slice(0, 6);
const tightPairs = [...carryPairs].sort((a, b) => a.bps - b.bps).slice(0, 4);
// Divergences : devises dont la direction journalière est opposée à la majorité
// Divergences yield/FX : yield et devise vont dans des directions opposées
// Yield ↑ + devise ↓ → obligations vendues MAIS devise sous pression = signal de stress
// Yield ↓ + devise ↑ → obligations achetées MAIS devise forte = safe-haven ou anomalie
// Seuils : yield ≥ 2bp (0.02%) ET FX ≥ 0.1% pour filtrer le bruit
const divergences = useMemo(() => {
const deltas = sorted.map(([ccy]) => ({ ccy, d: dayDeltas[ccy] ?? 0 }));
const rising = deltas.filter(x => x.d > 0.001).length;
const falling = deltas.filter(x => x.d < -0.001).length;
const majority = rising >= falling ? "up" : "down";
const YIELD_THRESHOLD = 0.02; // 2 basis points minimum
const FX_THRESHOLD = 0.1; // 0.1% FX move minimum
const result: { ccy: string; yieldDelta: number; fxPct: number; type: "stress" | "safehaven" }[] = [];
return deltas
.filter(x => {
if (majority === "up" && x.d < -0.001) return true;
if (majority === "down" && x.d > 0.001) return true;
return false;
})
.sort((a, b) => Math.abs(b.d) - Math.abs(a.d));
}, [sorted, dayDeltas]);
for (const [ccy] of sorted) {
const yd = dayDeltas[ccy] ?? null;
const fx = fxDayPct?.[ccy] ?? null;
if (yd === null || fx === null) continue;
if (Math.abs(yd) < YIELD_THRESHOLD || Math.abs(fx) < FX_THRESHOLD) continue;
const yieldUp = yd > 0;
const fxUp = fx > 0;
if (yieldUp && !fxUp) {
// Yield monte, devise baisse → signal de stress / pression vendeuse sur dettes
result.push({ ccy, yieldDelta: yd, fxPct: fx, type: "stress" });
} else if (!yieldUp && fxUp) {
// Yield baisse, devise monte → safe-haven ou anomalie (ex: JPY, CHF)
result.push({ ccy, yieldDelta: yd, fxPct: fx, type: "safehaven" });
}
}
return result.sort((a, b) => Math.abs(b.yieldDelta) + Math.abs(b.fxPct) - (Math.abs(a.yieldDelta) + Math.abs(a.fxPct)));
}, [sorted, dayDeltas, fxDayPct]);
// Movers du jour (triés par amplitude de variation)
const movers = useMemo(() => {
@@ -276,42 +291,58 @@ export default function YieldsTab({ yieldsData }: Props) {
{/* ── Divergences + Movers du jour ────────────────────────────────────── */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Divergences */}
{/* Divergences yield/FX */}
<div className="bg-white border border-gray-100 rounded-xl p-4">
<p className="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-3">
Divergences du jour
<p className="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1">
Divergences Yield / FX
</p>
{divergences.length === 0 ? (
<p className="text-[9px] text-gray-400 mb-3">
Yield et devise vont dans des sens opposés signal d'anomalie ou de stress
</p>
{!fxDayPct ? (
<p className="text-xs text-gray-400 italic">Données FX non disponibles.</p>
) : divergences.length === 0 ? (
<p className="text-xs text-gray-400 italic">
Aucune divergence notable tous les yields bougent dans la même direction.
Aucune divergence — yield et devise évoluent de façon cohérente ce jour.
</p>
) : (
<>
<div className="space-y-2">
{divergences.map(({ ccy, d }) => (
<div key={ccy} className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<div className="space-y-2.5">
{divergences.map(({ ccy, yieldDelta, fxPct, type }) => (
<div key={ccy} className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1.5 shrink-0">
<span className="text-xs">{CCY_FLAGS[ccy]}</span>
<span className="text-xs font-semibold text-gray-700">{ccy}</span>
<span className="text-[10px] text-gray-400">{CCY_COUNTRY[ccy]}</span>
<span className="text-xs font-semibold text-gray-700 w-7">{ccy}</span>
</div>
<div className={`flex items-center gap-1 text-xs font-mono font-semibold ${deltaColor(d)}`}>
{deltaIcon(d)}
{d >= 0 ? "+" : ""}{d.toFixed(3)}%
<span className={`ml-1 text-[9px] border rounded-full px-1.5 py-0.5 ${
d > 0
? "bg-green-50 text-green-700 border-green-200"
: "bg-red-50 text-red-700 border-red-200"
<div className="flex items-center gap-2 flex-1 justify-end flex-wrap">
{/* Yield delta */}
<span className={`text-[10px] font-mono font-semibold flex items-center gap-0.5 ${deltaColor(yieldDelta)}`}>
{deltaIcon(yieldDelta)}
<span className="text-[9px] text-gray-400 mr-0.5">yield</span>
{yieldDelta >= 0 ? "+" : ""}{yieldDelta.toFixed(3)}%
</span>
<span className="text-gray-300 text-[10px]">vs</span>
{/* FX delta */}
<span className={`text-[10px] font-mono font-semibold flex items-center gap-0.5 ${deltaColor(fxPct)}`}>
{deltaIcon(fxPct)}
<span className="text-[9px] text-gray-400 mr-0.5">FX</span>
{fxPct >= 0 ? "+" : ""}{fxPct.toFixed(2)}%
</span>
{/* Badge type */}
<span className={`text-[9px] border rounded-full px-1.5 py-0.5 shrink-0 ${
type === "stress"
? "bg-red-50 text-red-700 border-red-200"
: "bg-blue-50 text-blue-700 border-blue-200"
}`}>
{d > 0 ? "↑ contre tendance" : "↓ contre tendance"}
{type === "stress" ? "⚠ stress dette" : "🛡 safe-haven"}
</span>
</div>
</div>
))}
</div>
<p className="text-[9px] text-gray-400 mt-3 border-t border-gray-50 pt-2">
Ces devises bougent à contre-courant des autres ce jour.
Signal potentiel de réévaluation ou d'actualité macro spécifique.
Stress dette : yield ↑ + devise ↓ (obligations vendues, devise sous pression).<br />
Safe-haven : yield ↓ + devise ↑ (capitaux attirés malgré taux bas).
</p>
</>
)}