/* ═══════════════════════════════════════════════════════════════ Screens — Dashboard, Signaux, Volatile, Charts, Journal ═══════════════════════════════════════════════════════════════ */ const { useEffect: useEffectS, useState: useStateS, useRef: useRefS, useMemo: useMemoS } = React; /* ──────────────────────────────────────────────────────────────── DASHBOARD ──────────────────────────────────────────────────────────────── */ function DashboardScreen({ onAction }) { const { KPI, POSITIONS, ACTIVITY, EQUITY, SIGNALS } = window.MOCK; const top3 = [...SIGNALS].filter(s => s.state === "active").sort((a,b)=>b.score-a.score).slice(0, 3); return (

Dashboard

Lundi 13 mai 2026 · 14:42 · Connecté OKX
{/* KPI row */}
} foot={SUR {KPI.pnlTotal.period} · CAPITAL €10K} sparkline={EQUITY.map(p => p.v).slice(-30)} /> } foot={{KPI.winRate.wins}W · {KPI.winRate.losses}L · {KPI.winRate.trades} trades} /> {KPI.trades.d} aujourd'hui
} foot={
7J {KPI.trades.w} 30J {KPI.trades.m}
} /> {fmtPct(KPI.drawdown.current)}
} foot={
MAX {fmtPct(KPI.drawdown.max)} · SEUIL −15%
} /> {/* Crypto price chart full-width */} {/* Positions + Top signals */}

Positions ouvertes {POSITIONS.length}

Top signaux actifs

SCORE ≥ 8
{top3.map(s => )}
{/* Activity timeline */}

Activité récente

DERNIÈRE HEURE
{ACTIVITY.map((a, i) => )}
); } function KpiCard({ label, main, foot, accent, sparkline }) { return (
{label} {accent && }
{main}
{sparkline && (
)}
{foot}
); } function WinRateGauge({ value }) { const r = 26; const c = 2 * Math.PI * r; const offset = c * (1 - value / 100); const animated = useCountUp(value, 900); return (
{animated.toFixed(1)}% RATIO W/L
); } function EquityCurve({ data }) { const ref = useRefS(null); const [hover, setHover] = useStateS(null); const w = 1200; const h = 220; const path = useMemoS(() => { if (!data || !data.length) return { line:"", area:"" }; const vs = data.map(d=>d.v); const min=Math.min(...vs); const max=Math.max(...vs); const range = max - min || 1; const pad = 12; const px = (i) => (i / (data.length - 1)) * (w - 2*pad) + pad; const py = (v) => h - pad - ((v - min) / range) * (h - 2*pad); let line = ""; data.forEach((d, i) => { line += (i===0?"M":"L") + px(i) + "," + py(d.v) + " "; }); const area = line + ` L${w-pad},${h} L${pad},${h} Z`; return { line, area, px, py, min, max }; }, [data]); const isPositive = data[data.length-1].v > data[0].v; const color = isPositive ? "var(--bull)" : "var(--bear)"; function onMove(e) { const r = ref.current.getBoundingClientRect(); const x = (e.clientX - r.left) / r.width * w; const i = Math.round((x - 12) / (w - 24) * (data.length - 1)); if (i >= 0 && i < data.length) setHover({ i, x: path.px(i), y: path.py(data[i].v), v: data[i].v }); } return (
setHover(null)}> {/* gridlines */} {[0.25, 0.5, 0.75].map(g => ( ))} {/* trade markers */} {[15, 38, 62, 88, 110, 135, 158].map(i => ( ))} {hover && ( )} {hover && (
JOUR {hover.i} €{hover.v.toLocaleString("fr-FR", { maximumFractionDigits:0 })}
)}
); } function PositionsTable({ positions }) { const isMobile = typeof window !== "undefined" && window.innerWidth < 880; if (positions.length === 0) { return
Aucune position ouverte
; } if (isMobile) { return (
{positions.map(p => )}
); } return ( {positions.map(p => )}
PAIRDIRENTRYCURRENTP&LSL/TP
); } function PositionCardMobile({ p }) { const animPnl = useCountUp(p.pnlEur); const animPct = useCountUp(p.pnlPct); const up = p.pnlEur >= 0; return (
{p.pair}
x{p.lev} · {p.isReal?"RÉEL":"PAPER"}
{up?"+":"−"}€{Math.abs(animPnl).toFixed(2)} {fmtPct(animPct)}
ENTRY
{fmtNum(p.entry)}
NOW
{fmtNum(p.current)}
SL/TP1
{fmtNum(p.sl)}
{fmtNum(p.tp1)}
); } function PositionRow({ p }) { const animPnl = useCountUp(p.pnlEur); const animPct = useCountUp(p.pnlPct); const up = p.pnlEur >= 0; return (
{p.pair} x{p.lev} · {p.opened}
{fmtNum(p.entry)} {fmtNum(p.current)}
{up?"+":"−"}€{Math.abs(animPnl).toFixed(2)} {fmtPct(animPct)}
{fmtNum(p.sl)} {fmtNum(p.tp1)}
); } function TopSignalRow({ signal }) { return (
{signal.pair}/USDT
{signal.note}
); } function ActivityRow({ item }) { const colorMap = { bull:"var(--bull)", bear:"var(--bear)", accent:"var(--accent-400)", muted:"var(--text-muted)" }; const iconMap = { "trend-up": Icon.TrendUp, "trend-down": Icon.TrendDown, "zap": Icon.Zap, "shield": Icon.Shield, "x": Icon.X }; const Ic = iconMap[item.icon] || Icon.Dot; return (
{item.t}
{item.title} {item.desc}
); } /* ─── Crypto chart card (replaces equity curve) ───────────────── */ function CryptoChartCard() { const PAIR_OPTIONS = [ { id: "BTC/USDT:USDT", sym: "BTC" }, { id: "ETH/USDT:USDT", sym: "ETH" }, { id: "SOL/USDT:USDT", sym: "SOL" }, { id: "XAU/USDT:USDT", sym: "XAU" }, { id: "DOGE/USDT:USDT", sym: "DOGE" }, ]; const TF_OPTIONS = [ { id: "1D", bar: "15m", limit: 96 }, { id: "1W", bar: "1H", limit: 168 }, { id: "1M", bar: "4H", limit: 180 }, { id: "ALL", bar: "1D", limit: 365 }, ]; const [pair, setPair] = useStateS(PAIR_OPTIONS[0]); const [tf, setTf] = useStateS(TF_OPTIONS[1]); const [data, setData] = useStateS([]); const [loading, setLoad] = useStateS(true); useEffectS(() => { const key = new URLSearchParams(location.search).get("key"); const refresh = (initial) => { if (initial) setLoad(true); fetch(`/api/candles?pair=${encodeURIComponent(pair.id)}&bar=${tf.bar}&limit=${tf.limit}`, { credentials: "include" }) .then(r => r.json()) .then(j => { const arr = (j.candles || []).map((c, i) => ({ t: c.t || i, v: parseFloat(c.close) })); setData(arr); if (initial) setLoad(false); }) .catch(() => { if (initial) setLoad(false); }); }; refresh(true); const t = setInterval(() => refresh(false), 30000); // refresh prix toutes les 30s return () => clearInterval(t); }, [pair.id, tf.id]); // Lit le prix LIVE depuis le WS si dispo (sinon last close) const store = window.useStore ? window.useStore() : {}; const livePx = store.PRICES && store.PRICES[pair.sym]; const last = livePx || (data.length ? data[data.length - 1].v : 0); const first = data.length ? data[0].v : 0; const chg = first > 0 ? ((last - first) / first) * 100 : 0; const up = chg >= 0; return (

{pair.sym}/USDT

{loading ? "—" : fmtNum(last)} {fmtPct(chg)}
PRIX MARCHÉ · {tf.bar.toUpperCase()} · OKX
{PAIR_OPTIONS.map(p => (
setPair(p)}> {p.sym}
))}
{TF_OPTIONS.map(t => (
setTf(t)}> {t.id}
))}
{loading ? (
Chargement…
) : data.length < 2 ? (
Pas de données
) : ( )}
); } function PriceCurve({ data, up }) { const ref = useRefS(null); const [hover, setHover] = useStateS(null); const w = 1200, h = 220; const path = useMemoS(() => { if (!data || data.length < 2) return { line:"", area:"" }; const vs = data.map(d=>d.v); const min = Math.min(...vs), max = Math.max(...vs); const range = max - min || 1; const pad = 12; const px = (i) => (i / (data.length - 1)) * (w - 2*pad) + pad; const py = (v) => h - pad - ((v - min) / range) * (h - 2*pad); let line = ""; data.forEach((d, i) => { line += (i===0?"M":"L") + px(i) + "," + py(d.v) + " "; }); const area = line + ` L${w-pad},${h} L${pad},${h} Z`; return { line, area, px, py, min, max }; }, [data]); function onMove(e) { const r = ref.current.getBoundingClientRect(); const x = (e.clientX - r.left) / r.width * w; const i = Math.round((x - 12) / (w - 24) * (data.length - 1)); if (i >= 0 && i < data.length) setHover({ i, x: path.px(i), y: path.py(data[i].v), v: data[i].v }); } const stroke = up ? "var(--bull)" : "var(--bear)"; const fill = up ? "rgba(16,185,129,0.18)" : "rgba(239,68,68,0.18)"; return (
setHover(null)}> {[0.25, 0.5, 0.75].map(g => ( ))} {hover && ( )} {hover && (
POINT {hover.i + 1}/{data.length} {fmtNum(hover.v)}
)}
); } Object.assign(window, { DashboardScreen });