"use client"; // YieldsTab — Rendements souverains 10Y : classement, opportunités carry, divergences import { useMemo } from "react"; import { TrendingUp, TrendingDown, Minus, ArrowRight } from "lucide-react"; // ── Types ───────────────────────────────────────────────────────────────────── interface YieldsData { yields: Record; spreads: Record; dayDeltas?: Record; } interface Props { yieldsData: YieldsData | null; } // ── Helpers ─────────────────────────────────────────────────────────────────── const CCY_FLAGS: Record = { USD: "🇺🇸", EUR: "🇪🇺", GBP: "🇬🇧", JPY: "🇯🇵", AUD: "🇦🇺", CAD: "🇨🇦", CHF: "🇨🇭", NZD: "🇳🇿", }; const CCY_COUNTRY: Record = { USD: "États-Unis", EUR: "Zone Euro", GBP: "Royaume-Uni", JPY: "Japon", AUD: "Australie", CAD: "Canada", CHF: "Suisse", NZD: "Nvl-Zélande", }; function fmt(n: number | null, dec = 3): string { return n !== null ? n.toFixed(dec) + "%" : "—"; } function deltaColor(d: number | null) { if (d === null || d === 0) return "text-gray-400"; return d > 0 ? "text-green-600" : "text-red-600"; } function deltaIcon(d: number | null) { if (d === null || Math.abs(d) < 0.001) return ; return d > 0 ? : ; } // Niveau d'écart en bps → label d'opportunité function carryLabel(bps: number): { label: string; color: string } { if (bps >= 400) return { label: "Très fort", color: "text-emerald-700 bg-emerald-50 border-emerald-200" }; if (bps >= 200) return { label: "Fort", color: "text-green-700 bg-green-50 border-green-200" }; if (bps >= 100) return { label: "Modéré", color: "text-yellow-700 bg-yellow-50 border-yellow-200" }; return { label: "Faible", color: "text-gray-600 bg-gray-50 border-gray-200" }; } // ── Component ───────────────────────────────────────────────────────────────── export default function YieldsTab({ yieldsData }: Props) { if (!yieldsData) { return (
Chargement des rendements…
); } const { yields, dayDeltas = {} } = yieldsData; // Filtre les devises avec données, tri décroissant const sorted = useMemo(() => { return (Object.entries(yields) as [string, number | null][]) .filter(([, v]) => v !== null) .sort(([, a], [, b]) => (b as number) - (a as number)) as [string, number][]; }, [yields]); if (sorted.length === 0) { return (
Données indisponibles
); } const maxYield = sorted[0][1]; const minYield = sorted[sorted.length - 1][1]; // Top carry pairs (toutes combinaisons triées par écart décroissant) const carryPairs = useMemo(() => { const pairs: { long: string; short: string; bps: number }[] = []; for (let i = 0; i < sorted.length; i++) { for (let j = i + 1; j < sorted.length; j++) { const [cLong, yLong] = sorted[i]; const [cShort, yShort] = sorted[j]; pairs.push({ long: cLong, short: cShort, bps: Math.round((yLong - yShort) * 100) }); } } return pairs.sort((a, b) => b.bps - a.bps); }, [sorted]); 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é 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"; 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]); // Movers du jour (triés par amplitude de variation) const movers = useMemo(() => { return sorted .map(([ccy]) => ({ ccy, d: dayDeltas[ccy] ?? 0 })) .filter(x => Math.abs(x.d) >= 0.001) .sort((a, b) => Math.abs(b.d) - Math.abs(a.d)); }, [sorted, dayDeltas]); return (
{/* ── Header ──────────────────────────────────────────────────────────── */}

Rendements Souverains 10Y

Source : Trading Economics (données du jour) · EUR = Bund allemand

{sorted[0][0]} {fmt(maxYield)} ↑ max | {sorted.at(-1)![0]} {fmt(minYield)} ↓ min
{/* ── Classement barres horizontales ──────────────────────────────────── */}

Classement Yield 10Y

{sorted.map(([ccy, yld], i) => { const pct = maxYield > 0 ? (yld / maxYield) * 100 : 0; const delta = dayDeltas[ccy] ?? null; const isMax = i === 0; const isMin = i === sorted.length - 1; const barColor = isMax ? "bg-emerald-500" : isMin ? "bg-blue-400" : yld > 3.5 ? "bg-green-400" : yld > 2 ? "bg-yellow-400" : "bg-blue-300"; return (
{/* Devise */}
{CCY_FLAGS[ccy]} {ccy} {isMax && ( MAX )} {isMin && ( MIN )}
{/* Barre */}
{/* Yield */}
{fmt(yld)}
{/* Delta jour */}
{deltaIcon(delta)} {delta !== null ? (delta >= 0 ? "+" : "") + delta.toFixed(3) : "—"}
); })}
{/* ── Deux colonnes : Top Carry + Spreads Étroits ─────────────────────── */}
{/* Top carry */}

🚀 Top Carry — Écarts les plus larges

{topCarry.map(({ long, short, bps }) => { const { label, color } = carryLabel(bps); return (
{CCY_FLAGS[long]} {long} {CCY_FLAGS[short]} {short}
+{bps} bps {label}
); })}

Long = devise avec yield le plus élevé · Court = yield le plus faible. Carry = emprunter la devise bon marché pour acheter la devise chère.

{/* Spreads étroits */}

🔄 Convergence — Spreads les plus étroits

{tightPairs.map(({ long, short, bps }) => (
{CCY_FLAGS[long]} {long} / {CCY_FLAGS[short]} {short}
{bps} bps Range
))}

Spreads étroits = peu d'avantage de taux entre les deux devises. La paire est davantage guidée par d'autres facteurs (sentiment, données macro).

{/* ── Divergences + Movers du jour ────────────────────────────────────── */}
{/* Divergences */}

⚡ Divergences du jour

{divergences.length === 0 ? (

Aucune divergence notable — tous les yields bougent dans la même direction.

) : ( <>
{divergences.map(({ ccy, d }) => (
{CCY_FLAGS[ccy]} {ccy} {CCY_COUNTRY[ccy]}
{deltaIcon(d)} {d >= 0 ? "+" : ""}{d.toFixed(3)}% 0 ? "bg-green-50 text-green-700 border-green-200" : "bg-red-50 text-red-700 border-red-200" }`}> {d > 0 ? "↑ contre tendance" : "↓ contre tendance"}
))}

Ces devises bougent à contre-courant des autres ce jour. Signal potentiel de réévaluation ou d'actualité macro spécifique.

)}
{/* Movers du jour */}

📊 Movers du jour (variation yield)

{movers.length === 0 ? (

Pas de mouvement significatif ce jour.

) : (
{movers.map(({ ccy, d }, i) => { const maxDelta = Math.max(...movers.map(m => Math.abs(m.d))); const barW = maxDelta > 0 ? (Math.abs(d) / maxDelta) * 100 : 0; return (
{CCY_FLAGS[ccy]} {ccy} {i === 0 && ( 1er )}
0 ? "bg-green-400" : "bg-red-400"}`} style={{ width: `${barW}%` }} />
{d >= 0 ? "+" : ""}{d.toFixed(3)}%
); })}
)}

Variation du rendement 10Y par rapport à la clôture de la veille. Un yield qui monte = signal hawkish / pression vendeuse sur l'obligataire.

); }