"use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import { authFetch, useAuth } from "@/lib/auth"; import { XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, Area, AreaChart } from "recharts"; function bjt(ms: number) { const d = new Date(ms + 8 * 3600 * 1000); return `${String(d.getUTCMonth()+1).padStart(2,"0")}-${String(d.getUTCDate()).padStart(2,"0")} ${String(d.getUTCHours()).padStart(2,"0")}:${String(d.getUTCMinutes()).padStart(2,"0")}`; } function fmtPrice(p: number) { return p < 100 ? p.toFixed(4) : p.toLocaleString("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 }); } function fmtMs(ms: number) { return ms > 999 ? `${(ms/1000).toFixed(1)}s` : `${ms}ms`; } const LIVE_STRATEGY = "v52_8signals"; // ═══════════════════════════════════════════════════════════════ // L0: 顶部固定风险条(sticky,永远可见) // ═══════════════════════════════════════════════════════════════ function L0_RiskBar() { const [risk, setRisk] = useState(null); const [recon, setRecon] = useState(null); const [account, setAccount] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/risk-status"); if (r.ok) setRisk(await r.json()); } catch {} try { const r = await authFetch("/api/live/reconciliation"); if (r.ok) setRecon(await r.json()); } catch {} try { const r = await authFetch("/api/live/account"); if (r.ok) setAccount(await r.json()); } catch {} }; f(); const iv = setInterval(f, 5000); return () => clearInterval(iv); }, []); const riskStatus = risk?.status || "unknown"; const riskColor = riskStatus === "normal" ? "bg-emerald-500" : riskStatus === "circuit_break" ? "bg-red-500" : "bg-amber-500"; const reconOk = recon?.status === "ok"; const totalR = (risk?.today_realized_r || 0) + Math.min(risk?.today_unrealized_r || 0, 0); const rBudgetPct = Math.abs(totalR / 5 * 100); // -5R日限 const rBudgetColor = rBudgetPct >= 100 ? "text-red-500" : rBudgetPct >= 80 ? "text-amber-500" : "text-emerald-500"; return (
{/* 交易状态 */}
{riskStatus === "normal" ? "运行中" : riskStatus === "circuit_break" ? "🔴 熔断" : riskStatus === "warning" ? "⚠️ 警告" : "未知"} {risk?.block_new_entries && 禁新仓} {risk?.reduce_only && 只减仓}
{/* R预算 */}
已实现 = 0 ? "text-emerald-400" : "text-red-400"}`}>{(risk?.today_realized_r||0) >= 0 ? "+" : ""}{risk?.today_realized_r||0}R
未实现 = 0 ? "text-emerald-400" : "text-red-400"}`}>{(risk?.today_unrealized_r||0) >= 0 ? "+" : ""}{risk?.today_unrealized_r||0}R
日限 {totalR >= 0 ? "+" : ""}{totalR.toFixed(1)}/-5R
{/* 对账+清算 */}
对账{reconOk ? "✓" : `✗(${recon?.diffs?.length||0})`}
连亏 {risk?.consecutive_losses||0}
{risk?.circuit_break_reason && {risk.circuit_break_reason}}
); } // ═══════════════════════════════════════════════════════════════ // L1: 一键止血区 // ═══════════════════════════════════════════════════════════════ function L1_EmergencyPanel() { const [confirming, setConfirming] = useState(null); const [msg, setMsg] = useState(""); const doAction = async (action: string) => { try { const r = await authFetch(`/api/live/${action}`, { method: "POST" }); const j = await r.json(); setMsg(j.message || j.error || "已执行"); setConfirming(null); setTimeout(() => setMsg(""), 5000); } catch { setMsg("操作失败"); } }; const ConfirmBtn = ({ action, label, color }: { action: string; label: string; color: string }) => ( confirming === action ? (
确认?
) : ( ) ); return (

⚡ 止血操作

{msg &&

{msg}

}
); } // ═══════════════════════════════════════════════════════════════ // L2: 账户概览(8卡片) // ═══════════════════════════════════════════════════════════════ function L2_AccountOverview() { const [data, setData] = useState(null); const [summary, setSummary] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/account"); if (r.ok) setData(await r.json()); } catch {} try { const r = await authFetch(`/api/live/summary?strategy=${LIVE_STRATEGY}`); if (r.ok) setSummary(await r.json()); } catch {} }; f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); }, []); const d = data || {}; const s = summary || {}; const cards = [ { label: "账户权益", value: `$${(d.equity||0).toFixed(2)}`, color: "" }, { label: "可用保证金", value: `$${(d.available_margin||0).toFixed(2)}`, color: "" }, { label: "已用保证金", value: `$${(d.used_margin||0).toFixed(2)}`, color: "" }, { label: "有效杠杆", value: `${d.effective_leverage||0}x`, color: (d.effective_leverage||0) > 10 ? "text-red-500" : "" }, { label: "今日净PnL", value: `${(d.today_realized_r||0)>=0?"+":""}${d.today_realized_r||0}R ($${d.today_realized_usdt||0})`, color: (d.today_realized_r||0)>=0 ? "text-emerald-600" : "text-red-500" }, { label: "总净PnL", value: `${(s.total_pnl_r||0)>=0?"+":""}${s.total_pnl_r||0}R ($${s.total_pnl_usdt||0})`, color: (s.total_pnl_r||0)>=0 ? "text-emerald-600" : "text-red-500" }, { label: "成本占比", value: `$${(s.total_fee_usdt||0)+(s.total_funding_usdt||0)}`, color: "text-amber-600" }, { label: "胜率/PF", value: `${s.win_rate||0}% / ${s.profit_factor||0}`, color: "" }, ]; return (
{cards.map((c, i) => (

{c.label}

{c.value}

))}
); } // ═══════════════════════════════════════════════════════════════ // L3: 当前持仓(WS实时) // ═══════════════════════════════════════════════════════════════ function L3_Positions() { const [positions, setPositions] = useState([]); const [wsPrices, setWsPrices] = useState>({}); const [recon, setRecon] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch(`/api/live/positions?strategy=${LIVE_STRATEGY}`); if (r.ok) { const j = await r.json(); setPositions(j.data||[]); } } catch {} try { const r = await authFetch("/api/live/reconciliation"); if (r.ok) setRecon(await r.json()); } catch {} }; f(); const iv = setInterval(f, 5000); return () => clearInterval(iv); }, []); useEffect(() => { const streams = ["btcusdt","ethusdt","xrpusdt","solusdt"].map(s=>`${s}@aggTrade`).join("/"); const ws = new WebSocket(`wss://fstream.binance.com/stream?streams=${streams}`); ws.onmessage = (e) => { try { const m = JSON.parse(e.data); if (m.data) setWsPrices(p => ({...p, [m.data.s]: parseFloat(m.data.p)})); } catch {} }; return () => ws.close(); }, []); // 从对账数据获取清算距离 const liqDist: Record = {}; if (recon?.exchange_positions) { for (const ep of recon.exchange_positions) { if (ep.liquidation_price > 0 && ep.mark_price > 0) { const dist = ep.direction === "LONG" ? (ep.mark_price - ep.liquidation_price) / ep.mark_price * 100 : (ep.liquidation_price - ep.mark_price) / ep.mark_price * 100; liqDist[ep.symbol] = dist; } } } if (positions.length === 0) return
暂无活跃持仓
; return (

L3 当前持仓 ● 实时

{positions.map((p: any) => { const sym = p.symbol?.replace("USDT","") || ""; const holdMin = p.hold_time_min || Math.round((Date.now()-p.entry_ts)/60000); const cp = wsPrices[p.symbol] || p.current_price || 0; const entry = p.entry_price || 0; const rd = p.risk_distance || 1; const fullR = rd > 0 ? (p.direction==="LONG"?(cp-entry)/rd:(entry-cp)/rd) : 0; const tp1R = rd > 0 ? (p.direction==="LONG"?((p.tp1_price||0)-entry)/rd:(entry-(p.tp1_price||0))/rd) : 0; const unrealR = p.tp1_hit ? 0.5*tp1R+0.5*fullR : fullR; const unrealUsdt = unrealR * 2; const holdColor = holdMin>=60?"text-red-500 font-bold":holdMin>=45?"text-amber-500":"text-slate-400"; const dist = liqDist[p.symbol]; const distColor = dist !== undefined ? (dist < 8 ? "bg-slate-900 text-white" : dist < 12 ? "bg-red-50 text-red-700" : dist < 20 ? "bg-amber-50 text-amber-700" : "bg-emerald-50 text-emerald-700") : "bg-slate-50 text-slate-400"; return (
{p.direction==="LONG"?"🟢":"🔴"} {sym} {p.direction} 评分{p.score} · {p.tier==="heavy"?"加仓":"标准"} {dist !== undefined && 清算{dist.toFixed(1)}%}
=0?"text-emerald-600":"text-red-500"}`}>{unrealR>=0?"+":""}{unrealR.toFixed(2)}R =0?"text-emerald-500":"text-red-400"}`}>({unrealUsdt>=0?"+":""}${unrealUsdt.toFixed(2)}) {holdMin}m
{/* 价格行 */}
入场: ${fmtPrice(entry)} 成交: ${fmtPrice(p.fill_price||entry)} 现价: ${cp ? fmtPrice(cp) : "-"} TP1: ${fmtPrice(p.tp1_price)}{p.tp1_hit?" ✅":""} TP2: ${fmtPrice(p.tp2_price)} SL: ${fmtPrice(p.sl_price)}
{/* 执行指标 */}
8?"bg-red-50 text-red-700":Math.abs(p.slippage_bps||0)>2.5?"bg-amber-50 text-amber-700":"bg-emerald-50 text-emerald-700"}`}>滑点 {(p.slippage_bps||0).toFixed(1)}bps 5000?"bg-red-50 text-red-700":(p.protection_gap_ms||0)>2000?"bg-amber-50 text-amber-700":"bg-emerald-50 text-emerald-700"}`}>裸奔 {fmtMs(p.protection_gap_ms||0)} S→O {fmtMs(p.signal_to_order_ms||0)} O→F {fmtMs(p.order_to_fill_ms||0)} #{p.binance_order_id||"-"}
); })}
); } // ═══════════════════════════════════════════════════════════════ // L4: 执行质量面板 // ═══════════════════════════════════════════════════════════════ function L4_ExecutionQuality() { const [data, setData] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/execution-quality"); if (r.ok) setData(await r.json()); } catch {} }; f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); }, []); if (!data || data.error) return null; const o = data.overall || {}; const MetricRow = ({ label, stat, unit, yellowThresh, redThresh }: any) => { const color = stat?.p95 > redThresh ? "text-red-500" : stat?.p95 > yellowThresh ? "text-amber-500" : "text-emerald-600"; return (
{label}
avg {stat?.avg}{unit} P50 {stat?.p50}{unit} P95 {stat?.p95}{unit}
); }; return (

L4 执行质量 {data.total_trades}笔

{data.by_symbol && Object.keys(data.by_symbol).length > 0 && (

按币种

{Object.entries(data.by_symbol).map(([sym, v]: [string, any]) => (
{sym.replace("USDT","")} {v.count}笔
滑点P95: {v.slippage_bps?.p95}bps 裸奔P95: {fmtMs(v.protection_gap_ms?.p95||0)}
))}
)}
); } // ═══════════════════════════════════════════════════════════════ // L5: 对账面板 // ═══════════════════════════════════════════════════════════════ function L5_Reconciliation() { const [data, setData] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/reconciliation"); if (r.ok) setData(await r.json()); } catch {} }; f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); }, []); if (!data) return null; const ok = data.status === "ok"; return (

L5 对账 {ok ? ✓ 一致 : ✗ 差异}

本地仓位 ({data.local_positions?.length || 0})

{(data.local_positions || []).map((p: any) => (
{p.symbol?.replace("USDT","")} {p.direction} @ {fmtPrice(p.entry_price)}
))} {!data.local_positions?.length &&
}

币安仓位 ({data.exchange_positions?.length || 0})

{(data.exchange_positions || []).map((p: any, i: number) => (
{p.symbol?.replace("USDT","")} {p.direction} qty={p.amount} liq={fmtPrice(p.liquidation_price)}
))} {!data.exchange_positions?.length &&
}
挂单: 本地预期 {data.local_orders||0} / 币安 {data.exchange_orders||0}
{data.diffs?.length > 0 && (
{data.diffs.map((d: any, i: number) => (
⚠ [{d.symbol}] {d.detail}
))}
)}
); } // ═══════════════════════════════════════════════════════════════ // L6: 风控状态 // ═══════════════════════════════════════════════════════════════ function L6_RiskStatus() { const [risk, setRisk] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/risk-status"); if (r.ok) setRisk(await r.json()); } catch {} }; f(); const iv = setInterval(f, 5000); return () => clearInterval(iv); }, []); if (!risk) return null; const thresholds = [ { rule: "单日亏损 > -5R", status: (risk.today_total_r||0) > -5 ? "✅" : "🔴" }, { rule: "连续亏损 < 5次", status: (risk.consecutive_losses||0) < 5 ? "✅" : "🔴" }, { rule: "API连接正常", status: risk.status !== "circuit_break" || !risk.circuit_break_reason?.includes("API") ? "✅" : "🔴" }, ]; return (

L6 风控状态

{thresholds.map((t, i) => (
{t.status}{t.rule}
))}
{risk.circuit_break_reason && (
熔断原因:{risk.circuit_break_reason} {risk.auto_resume_time && 预计恢复: {new Date(risk.auto_resume_time * 1000).toLocaleTimeString("zh-CN")}}
)}
); } // ═══════════════════════════════════════════════════════════════ // L8: 实盘 vs 模拟盘对照 // ═══════════════════════════════════════════════════════════════ function L8_PaperComparison() { const [data, setData] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/paper-comparison?limit=20"); if (r.ok) setData(await r.json()); } catch {} }; f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); }, []); if (!data || !data.data?.length) return null; return (

L8 实盘 vs 模拟盘 平均R差: {data.avg_pnl_diff_r}R

{data.data.map((r: any, i: number) => ( ))}
币种方向 实盘入场模拟入场价差bps 实盘PnL模拟PnLR差
{r.symbol?.replace("USDT","")} {r.direction} {r.live_entry ? fmtPrice(r.live_entry) : "-"} {r.paper_entry ? fmtPrice(r.paper_entry) : "-"} {r.entry_diff_bps || "-"} =0?"text-emerald-600":"text-red-500"}`}>{r.live_pnl?.toFixed(2) || "-"} =0?"text-emerald-600":"text-red-500"}`}>{r.paper_pnl?.toFixed(2) || "-"} =0?"text-emerald-600":"text-red-500"}`}>{r.pnl_diff_r?.toFixed(2) || "-"}
); } // ═══════════════════════════════════════════════════════════════ // L9: 权益曲线+回撤 // ═══════════════════════════════════════════════════════════════ function L9_EquityCurve() { const [data, setData] = useState([]); useEffect(() => { const f = async () => { try { const r = await authFetch(`/api/live/equity-curve?strategy=${LIVE_STRATEGY}`); if (r.ok) { const j = await r.json(); setData(j.data||[]); } } catch {} }; f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); }, []); if (data.length < 2) return null; // 计算回撤 let peak = 0; const withDD = data.map(d => { if (d.pnl > peak) peak = d.pnl; return { ...d, dd: -(peak - d.pnl) }; }); return (

L9 权益曲线 + 回撤

bjt(v)} tick={{ fontSize: 10 }} /> `${v}R`} /> bjt(Number(v))} />
); } // ═══════════════════════════════════════════════════════════════ // L10: 历史交易表 // ═══════════════════════════════════════════════════════════════ type FS = "all"|"BTC"|"ETH"|"XRP"|"SOL"; type FR = "all"|"win"|"loss"; function L10_TradeHistory() { const [trades, setTrades] = useState([]); const [symbol, setSymbol] = useState("all"); const [result, setResult] = useState("all"); useEffect(() => { const f = async () => { try { const r = await authFetch(`/api/live/trades?symbol=${symbol}&result=${result}&strategy=${LIVE_STRATEGY}&limit=50`); if (r.ok) { const j = await r.json(); setTrades(j.data||[]); } } catch {} }; f(); const iv = setInterval(f, 15000); return () => clearInterval(iv); }, [symbol, result]); return (

L10 历史交易

{(["all","BTC","ETH","XRP","SOL"] as FS[]).map(s => ())} | {(["all","win","loss"] as FR[]).map(r => ())}
{trades.length === 0 ?
暂无交易记录
: ( {trades.map((t: any) => { const hm = t.exit_ts && t.entry_ts ? Math.round((t.exit_ts-t.entry_ts)/60000) : 0; return (); })}
币种方向 入场成交出场 PnL状态 滑点费用时长
{t.symbol?.replace("USDT","")} {t.direction==="LONG"?"▲":"▼"} {fmtPrice(t.entry_price)} {t.fill_price?fmtPrice(t.fill_price):"-"} {t.exit_price?fmtPrice(t.exit_price):"-"} 0?"text-emerald-600":(t.pnl_r||0)<0?"text-red-500":"text-slate-500"}`}>{(t.pnl_r||0)>0?"+":""}{(t.pnl_r||0).toFixed(2)}R {t.status==="tp"?"止盈":t.status==="sl"?"止损":t.status==="sl_be"?"保本":t.status} 8?"text-red-500":"text-slate-600"}`}>{(t.slippage_bps||0).toFixed(1)} ${(t.fee_usdt||0).toFixed(2)} {hm}m
)}
); } // ═══════════════════════════════════════════════════════════════ // L11: 系统健康 // ═══════════════════════════════════════════════════════════════ function L11_SystemHealth() { const [data, setData] = useState(null); useEffect(() => { const f = async () => { try { const r = await authFetch("/api/live/health"); if (r.ok) setData(await r.json()); } catch {} }; f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); }, []); if (!data) return null; const procs = data.processes || {}; const fresh = data.data_freshness || {}; return (

L11 系统健康

{Object.keys(procs).length > 0 && (

进程状态

{Object.entries(procs).map(([name, p]: [string, any]) => (
{name} {p.status} {p.memory_mb}MB ↻{p.restarts}
))}
)} {fresh.market_data && (
行情数据: {fresh.market_data.age_sec}秒前 {fresh.market_data.status==="green"?"✓":"⚠"}
)}
); } // ═══════════════════════════════════════════════════════════════ // 主页面 // ═══════════════════════════════════════════════════════════════ export default function LiveTradingPage() { const { isLoggedIn, loading } = useAuth(); if (loading) return
加载中...
; if (!isLoggedIn) return (
🔒

请先登录查看实盘

登录
); return (

⚡ 实盘交易

V5.2策略 · 币安USDT永续合约 · 测试网

); }