"use client"; import { useState } from "react"; import { CURRENCY_META } from "@/lib/constants"; import type { Currency } from "@/lib/types"; interface MyfxSymbol { name: string; longPercentage: number; shortPercentage:number; longVolume: number; shortVolume: number; longPositions: number; shortPositions: number; totalPositions: number; avgLongPrice?: number; avgShortPrice?: number; } interface Props { symbols: MyfxSymbol[] | null; } const PAIRS: { base: Currency; quote: Currency; std: string }[] = [ { base: "EUR", quote: "USD", std: "EURUSD" }, { base: "GBP", quote: "USD", std: "GBPUSD" }, { base: "USD", quote: "JPY", std: "USDJPY" }, { base: "USD", quote: "CHF", std: "USDCHF" }, { base: "USD", quote: "CAD", std: "USDCAD" }, { base: "AUD", quote: "USD", std: "AUDUSD" }, { base: "NZD", quote: "USD", std: "NZDUSD" }, { base: "EUR", quote: "GBP", std: "EURGBP" }, { base: "EUR", quote: "JPY", std: "EURJPY" }, { base: "EUR", quote: "CHF", std: "EURCHF" }, { base: "EUR", quote: "CAD", std: "EURCAD" }, { base: "EUR", quote: "AUD", std: "EURAUD" }, { base: "EUR", quote: "NZD", std: "EURNZD" }, { base: "GBP", quote: "JPY", std: "GBPJPY" }, { base: "GBP", quote: "CHF", std: "GBPCHF" }, { base: "GBP", quote: "CAD", std: "GBPCAD" }, { base: "GBP", quote: "AUD", std: "GBPAUD" }, { base: "GBP", quote: "NZD", std: "GBPNZD" }, { base: "AUD", quote: "JPY", std: "AUDJPY" }, { base: "AUD", quote: "CAD", std: "AUDCAD" }, { base: "AUD", quote: "CHF", std: "AUDCHF" }, { base: "AUD", quote: "NZD", std: "AUDNZD" }, { base: "CAD", quote: "JPY", std: "CADJPY" }, { base: "CHF", quote: "JPY", std: "CHFJPY" }, { base: "NZD", quote: "JPY", std: "NZDJPY" }, { base: "NZD", quote: "CAD", std: "NZDCAD" }, { base: "NZD", quote: "CHF", std: "NZDCHF" }, { base: "CAD", quote: "CHF", std: "CADCHF" }, ]; const GROUPS = [ { label: "Majeures USD", pairs: ["EURUSD","GBPUSD","USDJPY","USDCHF","USDCAD","AUDUSD","NZDUSD"] }, { label: "Crosses EUR", pairs: ["EURGBP","EURJPY","EURCHF","EURCAD","EURAUD","EURNZD"] }, { label: "Crosses GBP", pairs: ["GBPJPY","GBPCHF","GBPCAD","GBPAUD","GBPNZD"] }, { label: "Crosses AUD/NZD", pairs: ["AUDJPY","AUDCAD","AUDCHF","AUDNZD","NZDJPY","NZDCAD","NZDCHF"] }, { label: "Crosses CAD/CHF", pairs: ["CADJPY","CHFJPY","CADCHF"] }, ]; function fmtVol(v: number): string { if (v >= 1000) return `${(v / 1000).toFixed(1)}k`; return v.toFixed(0); } function fmtPos(n: number): string { if (n >= 1000) return `${(n / 1000).toFixed(1)}k`; return String(n); } // ── Barre Long/Short % ──────────────────────────────────────────────────────── function PctBar({ longPct }: { longPct: number }) { const extreme = longPct >= 70 || longPct <= 30; return (
{longPct}%
{100 - longPct}% {extreme && }
); } // ── Barre Volume ────────────────────────────────────────────────────────────── function VolBar({ longVol, shortVol }: { longVol: number; shortVol: number }) { const total = longVol + shortVol || 1; const longPct = Math.round((longVol / total) * 100); return (
{fmtVol(longVol)}
{fmtVol(shortVol)}
); } // ── Row ─────────────────────────────────────────────────────────────────────── function PairRow({ pairName, sym, base, quote, showVol }: { pairName: string; sym: MyfxSymbol | undefined; base: Currency; quote: Currency; showVol: boolean; }) { const baseMeta = CURRENCY_META[base]; const quoteMeta = CURRENCY_META[quote]; const extreme = sym && (sym.longPercentage >= 70 || sym.longPercentage <= 30); return ( {/* Paire */}
{baseMeta?.flag} {quoteMeta?.flag} {pairName}
{/* % Long/Short */} {sym ? : N/D} {/* Volume lots */} {showVol && ( {sym ? : } )} {/* Positions (traders) */} {sym ? (
{fmtPos(sym.longPositions)} / {fmtPos(sym.shortPositions)}
) : } {/* Prix moy. */} {sym?.avgLongPrice ? (
{sym.avgLongPrice.toFixed(4)}
{sym.avgShortPrice?.toFixed(4) ?? "—"}
) : } ); } // ── Main ────────────────────────────────────────────────────────────────────── export default function SentimentPairsTab({ symbols }: Props) { const [showVol, setShowVol] = useState(true); const [showContrariens, setShowContrariens] = useState(false); const symMap: Record = {}; for (const s of symbols ?? []) symMap[s.name] = s; const pairMap: Record = {}; for (const p of PAIRS) pairMap[p.std] = { base: p.base, quote: p.quote }; // Paires avec signal contrarien pour le résumé const contrarians = PAIRS .map(p => ({ ...p, sym: symMap[p.std] })) .filter(p => p.sym && (p.sym.longPercentage >= 70 || p.sym.longPercentage <= 30)) .sort((a, b) => { const scoreA = Math.abs((a.sym!.longPercentage) - 50); const scoreB = Math.abs((b.sym!.longPercentage) - 50); return scoreB - scoreA; }); return (
{/* Header */}

Sentiment Retail — Myfxbook Community Outlook

{symbols ? `${Object.keys(symMap).length} paires` : "chargement…"} {" "}· Long % = traders retail haussiers sur la devise de base {" "}· ⚡ signal contrarien (>70% ou <30%)

{/* Résumé signaux contrariens — compact / déroulant */} {contrarians.length > 0 && (
{showContrariens && (
{contrarians.slice(0, 12).map(p => { const dir = p.sym!.longPercentage >= 70 ? "short" : "long"; return (
{CURRENCY_META[p.base]?.flag}{CURRENCY_META[p.quote]?.flag} {p.std} {p.sym!.longPercentage}%L → {dir === "short" ? "↓ SELL" : "↑ BUY"}
); })}
)}
)} {/* Tables par groupe */}
{GROUPS.map((group, gi) => (
{/* Group header */}
0 ? "border-t border-t-slate-700" : ""} bg-slate-900/60`}> {group.label}
{showVol && } {group.pairs.map(pairName => { const def = pairMap[pairName]; return ( ); })}
Paire % Long / Short (retail)Volume lots (L / S)Traders (L / S) Prix moy. entrée
))} {/* Footer */}
Long Short Contrarien (>70% ou <30%) Source : Myfxbook Community Outlook · ~50k traders retail trackés
); }