diff --git a/components/TvChart.tsx b/components/TvChart.tsx index 0503957..0832421 100644 --- a/components/TvChart.tsx +++ b/components/TvChart.tsx @@ -33,7 +33,7 @@ function mountEmbedWidget( const script = document.createElement("script"); script.type = "text/javascript"; - script.async = true; + script.async = false; // false = exécution ordonnée → document.currentScript correctement défini script.src = scriptSrc; script.text = JSON.stringify(config); wrapper.appendChild(script); diff --git a/components/YieldsTab.tsx b/components/YieldsTab.tsx index 5352880..67cfdd2 100644 --- a/components/YieldsTab.tsx +++ b/components/YieldsTab.tsx @@ -1,7 +1,5 @@ "use client"; -// YieldsTab — Rendements souverains 10Y : classement, opportunités carry, divergences - import { useMemo } from "react"; import { TrendingUp, TrendingDown, Minus, ArrowRight } from "lucide-react"; @@ -25,19 +23,13 @@ const CCY_FLAGS: Record = { 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"; + if (d === null || d === 0) return "text-slate-500"; + return d > 0 ? "text-emerald-400" : "text-red-400"; } function deltaIcon(d: number | null) { @@ -45,12 +37,11 @@ function deltaIcon(d: number | null) { 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" }; +function carryStrength(bps: number): { label: string; color: string } { + if (bps >= 400) return { label: "Très fort", color: "text-emerald-400 bg-emerald-500/15 border-emerald-500/30" }; + if (bps >= 200) return { label: "Fort", color: "text-green-400 bg-green-500/15 border-green-500/30" }; + if (bps >= 100) return { label: "Modéré", color: "text-yellow-400 bg-yellow-500/15 border-yellow-500/30" }; + return { label: "Faible", color: "text-slate-400 bg-slate-700/30 border-slate-600/30" }; } // ── Component ───────────────────────────────────────────────────────────────── @@ -58,7 +49,7 @@ function carryLabel(bps: number): { label: string; color: string } { export default function YieldsTab({ yieldsData, fxDayPct }: Props) { if (!yieldsData) { return ( -
+
Chargement des rendements…
); @@ -66,7 +57,6 @@ export default function YieldsTab({ yieldsData, fxDayPct }: Props) { 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) @@ -75,16 +65,14 @@ export default function YieldsTab({ yieldsData, fxDayPct }: Props) { 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++) { @@ -97,73 +85,37 @@ export default function YieldsTab({ yieldsData, fxDayPct }: Props) { 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); + const topCarry = carryPairs.slice(0, 4); - // 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 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" }[] = []; - - 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(() => { return sorted - .map(([ccy]) => ({ ccy, d: dayDeltas[ccy] ?? 0 })) + .map(([ccy, yld]) => ({ ccy, yld, 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 -

+

Rendements Souverains 10Y

+

Trading Economics · EUR = Bund allemand

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

+ {/* ── Classement Yield 10Y ────────────────────────────────────────────── */} +

+

Classement Yield 10Y

-
+
{sorted.map(([ccy, yld], i) => { const pct = maxYield > 0 ? (yld / maxYield) * 100 : 0; const delta = dayDeltas[ccy] ?? null; @@ -172,41 +124,28 @@ export default function YieldsTab({ yieldsData, fxDayPct }: Props) { const barColor = isMax ? "bg-emerald-500" : isMin - ? "bg-blue-400" - : yld > 3.5 - ? "bg-green-400" - : yld > 2 - ? "bg-yellow-400" - : "bg-blue-300"; + ? "bg-sky-400" + : yld > 3.5 ? "bg-green-500" + : yld > 2 ? "bg-yellow-500" + : "bg-sky-500"; return (
- {/* Devise */} -
+
{CCY_FLAGS[ccy]} - {ccy} - {isMax && ( - MAX - )} - {isMin && ( - MIN - )} + {ccy} + {isMax && MAX} + {isMin && MIN}
- {/* Barre */} -
-
+
+
- {/* Yield */} -
+
{fmt(yld)}
- {/* Delta jour */}
{deltaIcon(delta)} {delta !== null ? (delta >= 0 ? "+" : "") + delta.toFixed(3) : "—"} @@ -217,179 +156,86 @@ export default function YieldsTab({ yieldsData, fxDayPct }: Props) {
- {/* ── Deux colonnes : Top Carry + Spreads Étroits ─────────────────────── */} -
+ {/* ── Carry Trade + Movers ────────────────────────────────────────────── */} +
- {/* Top carry */} -
-

- 🚀 Top Carry — Écarts les plus larges + {/* Top Carry — compact */} +

+

+ 🚀 Top Carry Trade

- {topCarry.map(({ long, short, bps }) => { - const { label, color } = carryLabel(bps); + {topCarry.map(({ long, short, bps }, i) => { + const { label, color } = carryStrength(bps); return ( -
-
- {CCY_FLAGS[long]} - {long} - - {CCY_FLAGS[short]} - {short} -
-
- - +{bps} bps - - - {label} - -
+
+ {i === 0 && } + {i !== 0 && {i + 1}} + {CCY_FLAGS[long]} + {long} + + {CCY_FLAGS[short]} + {short} + +{bps}bp + + {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. +

+ Long haut yield · Short bas yield · Emprunter la devise bon marché

- {/* 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 yield/FX */} -
-

- ⚡ Divergences Yield / FX -

-

- Yield et devise vont dans des sens opposés — signal d'anomalie ou de stress -

- {!fxDayPct ? ( -

Données FX non disponibles.

- ) : divergences.length === 0 ? ( -

- Aucune divergence — yield et devise évoluent de façon cohérente ce jour. -

- ) : ( - <> -
- {divergences.map(({ ccy, yieldDelta, fxPct, type }) => ( -
-
- {CCY_FLAGS[ccy]} - {ccy} -
-
- {/* Yield delta */} - - {deltaIcon(yieldDelta)} - yield - {yieldDelta >= 0 ? "+" : ""}{yieldDelta.toFixed(3)}% - - vs - {/* FX delta */} - - {deltaIcon(fxPct)} - FX - {fxPct >= 0 ? "+" : ""}{fxPct.toFixed(2)}% - - {/* Badge type */} - - {type === "stress" ? "⚠ stress dette" : "🛡 safe-haven"} - -
-
- ))} -
-

- Stress dette : yield ↑ + devise ↓ (obligations vendues, devise sous pression).
- Safe-haven : yield ↓ + devise ↑ (capitaux attirés malgré taux bas). -

- - )} -
- - {/* Movers du jour */} -
-

- 📊 Movers du jour (variation yield) + {/* Movers du jour — renforcé */} +

+

+ 📊 Movers du Jour — Yield 10Y

{movers.length === 0 ? ( -

Pas de mouvement significatif ce jour.

+

Aucun mouvement significatif.

) : ( -
- {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; +
+ {movers.map(({ ccy, yld, d }, i) => { + const maxAbs = Math.max(...movers.map(m => Math.abs(m.d))); + const barW = maxAbs > 0 ? (Math.abs(d) / maxAbs) * 100 : 0; + const isTop = i === 0; return ( -
-
- {CCY_FLAGS[ccy]} - {ccy} - {i === 0 && ( - 1er +
+
+ {CCY_FLAGS[ccy]} + + {ccy} + + {isTop && ( + 1er )} + {fmt(yld)} + + {d >= 0 ? "+" : ""}{d.toFixed(3)}% +
-
-
0 ? "bg-green-400" : "bg-red-400"}`} - style={{ width: `${barW}%` }} - /> -
-
- {d >= 0 ? "+" : ""}{d.toFixed(3)}% +
+
+
0 ? "bg-emerald-500" : "bg-red-500"}`} + style={{ width: `${barW}%` }} + /> +
); })}
)} -

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

+ Variation vs clôture veille · Yield ↑ = signal hawkish / pression vendeuse

-
+
); }