arbitrage-engine/frontend/app/live/page.tsx
root 1ef1f97b5d feat: /live 实盘交易前端页面
- 风控状态面板: 实时显示(正常/警告/熔断)、已实现R+未实现R+合计、连亏次数
- 紧急操作: 全平(双重确认)、禁止开仓、恢复交易
- 总览卡片: 盈亏R+USDT、胜率、持仓数、PF、手续费、资金费
- 当前持仓: WebSocket实时价格、滑点/裸奔/延迟指标、OrderID
- 权益曲线: Recharts AreaChart
- 历史交易: 含成交价/滑点/费用列、币种/盈亏筛选
- 详细统计: 滑点P50/P95/均值、按币种分组
- 导航栏: 新增实盘入口(Bolt图标)

风格与模拟盘一致: 白底+slate+emerald/red配色
2026-03-02 09:28:07 +00:00

345 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 });
}
const LIVE_STRATEGY = "v52_8signals";
// ─── 风控状态 ────────────────────────────────────────────────────
function RiskStatusPanel() {
const [risk, setRisk] = useState<any>(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 statusColor = risk.status === "normal" ? "border-emerald-400 bg-emerald-50" : risk.status === "warning" ? "border-amber-400 bg-amber-50" : risk.status === "circuit_break" ? "border-red-400 bg-red-50" : "border-slate-200 bg-slate-50";
const statusIcon = risk.status === "normal" ? "🟢" : risk.status === "warning" ? "🟡" : risk.status === "circuit_break" ? "🔴" : "⚪";
return (
<div className={`rounded-xl border-2 ${statusColor} px-4 py-3`}>
<div className="flex items-center justify-between flex-wrap gap-2">
<div className="flex items-center gap-3">
<span className="text-lg">{statusIcon}</span>
<div>
<span className="font-bold text-sm text-slate-800">: {risk.status === "normal" ? "正常" : risk.status === "warning" ? "警告" : risk.status === "circuit_break" ? "熔断中" : "未知"}</span>
{risk.circuit_break_reason && <p className="text-[10px] text-red-600 mt-0.5">{risk.circuit_break_reason}</p>}
</div>
</div>
<div className="flex gap-4 text-[11px] font-mono">
<div><span className="text-slate-400"></span><p className={`font-bold ${(risk.today_realized_r||0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>{(risk.today_realized_r||0) >= 0 ? "+" : ""}{risk.today_realized_r||0}R</p></div>
<div><span className="text-slate-400"></span><p className={`font-bold ${(risk.today_unrealized_r||0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>{(risk.today_unrealized_r||0) >= 0 ? "+" : ""}{risk.today_unrealized_r||0}R</p></div>
<div><span className="text-slate-400"></span><p className={`font-bold ${(risk.today_total_r||0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>{(risk.today_total_r||0) >= 0 ? "+" : ""}{risk.today_total_r||0}R</p></div>
<div><span className="text-slate-400"></span><p className="font-bold text-slate-800">{risk.consecutive_losses||0}</p></div>
</div>
</div>
{(risk.block_new_entries || risk.reduce_only) && (
<div className="mt-2 flex gap-2">
{risk.block_new_entries && <span className="text-[10px] px-2 py-0.5 rounded bg-red-100 text-red-700 font-medium">🚫 </span>}
{risk.reduce_only && <span className="text-[10px] px-2 py-0.5 rounded bg-red-100 text-red-700 font-medium">🔒 </span>}
</div>
)}
</div>
);
}
// ─── 紧急操作 ────────────────────────────────────────────────────
function EmergencyPanel() {
const [confirming, setConfirming] = useState<string | null>(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("操作失败"); }
};
return (
<div className="rounded-xl border border-slate-200 bg-white px-4 py-3">
<div className="flex items-center justify-between flex-wrap gap-2">
<h3 className="font-semibold text-slate-800 text-xs"> </h3>
<div className="flex gap-2">
{confirming === "emergency-close" ? (
<div className="flex items-center gap-1">
<span className="text-[10px] text-red-600 font-medium"></span>
<button onClick={() => doAction("emergency-close")} className="px-2 py-1 rounded text-[10px] font-bold bg-red-600 text-white"></button>
<button onClick={() => setConfirming(null)} className="px-2 py-1 rounded text-[10px] bg-slate-200 text-slate-600"></button>
</div>
) : (
<button onClick={() => setConfirming("emergency-close")} className="px-3 py-1.5 rounded-lg text-[11px] font-bold bg-red-500 text-white hover:bg-red-600">🔴 </button>
)}
{confirming === "block-new" ? (
<div className="flex items-center gap-1">
<span className="text-[10px] text-amber-600 font-medium"></span>
<button onClick={() => doAction("block-new")} className="px-2 py-1 rounded text-[10px] font-bold bg-amber-500 text-white"></button>
<button onClick={() => setConfirming(null)} className="px-2 py-1 rounded text-[10px] bg-slate-200 text-slate-600"></button>
</div>
) : (
<button onClick={() => setConfirming("block-new")} className="px-3 py-1.5 rounded-lg text-[11px] font-bold bg-amber-500 text-white hover:bg-amber-600">🟡 </button>
)}
<button onClick={() => doAction("resume")} className="px-3 py-1.5 rounded-lg text-[11px] font-bold bg-emerald-500 text-white hover:bg-emerald-600"> </button>
</div>
</div>
{msg && <p className="text-[10px] text-blue-600 mt-1">{msg}</p>}
</div>
);
}
// ─── 总览 ────────────────────────────────────────────────────────
function SummaryCards() {
const [data, setData] = useState<any>(null);
useEffect(() => {
const f = async () => { try { const r = await authFetch(`/api/live/summary?strategy=${LIVE_STRATEGY}`); if (r.ok) setData(await r.json()); } catch {} };
f(); const iv = setInterval(f, 10000); return () => clearInterval(iv);
}, []);
if (!data) return <div className="text-center text-slate-400 text-sm py-4">...</div>;
return (
<div className="grid grid-cols-3 lg:grid-cols-7 gap-1.5">
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">(R)</p>
<p className={`font-mono font-bold text-lg ${data.total_pnl_r >= 0 ? "text-emerald-600" : "text-red-500"}`}>{data.total_pnl_r >= 0 ? "+" : ""}{data.total_pnl_r}R</p>
<p className={`font-mono text-[10px] ${data.total_pnl_usdt >= 0 ? "text-emerald-500" : "text-red-400"}`}>{data.total_pnl_usdt >= 0 ? "+" : ""}${data.total_pnl_usdt}</p>
</div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2"><p className="text-[10px] text-slate-400"></p><p className="font-mono font-bold text-lg text-slate-800">{data.win_rate}%</p></div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2"><p className="text-[10px] text-slate-400"></p><p className="font-mono font-bold text-lg text-slate-800">{data.total_trades}</p></div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2"><p className="text-[10px] text-slate-400"></p><p className="font-mono font-bold text-lg text-blue-600">{data.active_positions}</p></div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2"><p className="text-[10px] text-slate-400">(PF)</p><p className="font-mono font-bold text-lg text-slate-800">{data.profit_factor}</p></div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2"><p className="text-[10px] text-slate-400"></p><p className="font-mono font-bold text-sm text-amber-600">${data.total_fee_usdt}</p></div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2"><p className="text-[10px] text-slate-400"></p><p className="font-mono font-bold text-sm text-violet-600">${data.total_funding_usdt}</p></div>
</div>
);
}
// ─── 当前持仓 ────────────────────────────────────────────────────
function ActivePositions() {
const [positions, setPositions] = useState<any[]>([]);
const [wsPrices, setWsPrices] = useState<Record<string, number>>({});
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 {} };
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 msg = JSON.parse(e.data); if (msg.data) { setWsPrices(prev => ({ ...prev, [msg.data.s]: parseFloat(msg.data.p) })); } } catch {} };
return () => ws.close();
}, []);
if (positions.length === 0) return <div className="rounded-xl border border-slate-200 bg-white px-3 py-4 text-center text-slate-400 text-sm"></div>;
return (
<div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100">
<h3 className="font-semibold text-slate-800 text-xs"> <span className="text-[10px] text-emerald-500 font-normal"> </span> <span className="text-[10px] text-slate-400 font-normal ml-2"></span></h3>
</div>
<div className="divide-y divide-slate-100">
{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 currentPrice = 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" ? (currentPrice - entry) / rd : (entry - currentPrice) / 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";
return (
<div key={p.id} className="px-3 py-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className={`text-xs font-bold ${p.direction === "LONG" ? "text-emerald-600" : "text-red-500"}`}>{p.direction === "LONG" ? "🟢" : "🔴"} {sym} {p.direction}</span>
<span className="text-[10px] text-slate-400">{p.score} · {p.tier === "heavy" ? "加仓" : "标准"}</span>
</div>
<div className="flex items-center gap-2">
<span className={`font-mono text-sm font-bold ${unrealR >= 0 ? "text-emerald-600" : "text-red-500"}`}>{unrealR >= 0 ? "+" : ""}{unrealR.toFixed(2)}R</span>
<span className={`font-mono text-[10px] ${unrealUsdt >= 0 ? "text-emerald-500" : "text-red-400"}`}>({unrealUsdt >= 0 ? "+" : ""}${unrealUsdt.toFixed(2)})</span>
<span className={`text-[10px] ${holdColor}`}>{holdMin}m</span>
</div>
</div>
<div className="flex gap-3 mt-1 text-[10px] font-mono text-slate-600 flex-wrap">
<span>入场: ${fmtPrice(entry)}</span>
<span>成交: ${fmtPrice(p.fill_price || entry)}</span>
<span className="text-blue-600">现价: ${currentPrice ? fmtPrice(currentPrice) : "-"}</span>
<span className="text-emerald-600">TP1: ${fmtPrice(p.tp1_price)}{p.tp1_hit ? " ✅" : ""}</span>
<span className="text-emerald-600">TP2: ${fmtPrice(p.tp2_price)}</span>
<span className="text-red-500">SL: ${fmtPrice(p.sl_price)}</span>
</div>
<div className="flex gap-2 mt-1 flex-wrap">
<span className={`text-[9px] px-1.5 py-0.5 rounded ${Math.abs(p.slippage_bps||0)>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</span>
<span className={`text-[9px] px-1.5 py-0.5 rounded ${(p.protection_gap_ms||0)>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"}`}> {p.protection_gap_ms||0}ms</span>
<span className="text-[9px] px-1.5 py-0.5 rounded bg-blue-50 text-blue-700"> {p.signal_to_order_ms||0}ms</span>
<span className="text-[9px] px-1.5 py-0.5 rounded bg-blue-50 text-blue-700"> {p.order_to_fill_ms||0}ms</span>
<span className="text-[9px] px-1.5 py-0.5 rounded bg-slate-100 text-slate-600">#{p.binance_order_id||"-"}</span>
</div>
</div>
);
})}
</div>
</div>
);
}
// ─── 权益曲线 ────────────────────────────────────────────────────
function EquityCurve() {
const [data, setData] = useState<any[]>([]);
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;
return (
<div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100"><h3 className="font-semibold text-slate-800 text-xs">线 (PnL)</h3></div>
<div className="p-2" style={{ height: 200 }}>
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data}>
<XAxis dataKey="ts" tickFormatter={(v) => bjt(v)} tick={{ fontSize: 10 }} />
<YAxis tick={{ fontSize: 10 }} tickFormatter={(v) => `${v}R`} />
<Tooltip labelFormatter={(v) => bjt(Number(v))} formatter={(v: any) => [`${v}R`, "累计PnL"]} />
<ReferenceLine y={0} stroke="#94a3b8" strokeDasharray="3 3" />
<Area type="monotone" dataKey="pnl" stroke="#10b981" fill="#d1fae5" strokeWidth={2} />
</AreaChart>
</ResponsiveContainer>
</div>
</div>
);
}
// ─── 历史交易 ────────────────────────────────────────────────────
type FilterSymbol = "all" | "BTC" | "ETH" | "XRP" | "SOL";
type FilterResult = "all" | "win" | "loss";
function TradeHistory() {
const [trades, setTrades] = useState<any[]>([]);
const [symbol, setSymbol] = useState<FilterSymbol>("all");
const [result, setResult] = useState<FilterResult>("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, 10000); return () => clearInterval(iv);
}, [symbol, result]);
return (
<div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100 flex items-center justify-between flex-wrap gap-1">
<h3 className="font-semibold text-slate-800 text-xs"></h3>
<div className="flex gap-1">
{(["all","BTC","ETH","XRP","SOL"] as FilterSymbol[]).map(s => (<button key={s} onClick={() => setSymbol(s)} className={`px-2 py-0.5 rounded text-[10px] ${symbol === s ? "bg-slate-800 text-white" : "text-slate-500 hover:bg-slate-100"}`}>{s === "all" ? "全部" : s}</button>))}
<span className="text-slate-300">|</span>
{(["all","win","loss"] as FilterResult[]).map(r => (<button key={r} onClick={() => setResult(r)} className={`px-2 py-0.5 rounded text-[10px] ${result === r ? "bg-slate-800 text-white" : "text-slate-500 hover:bg-slate-100"}`}>{r === "all" ? "全部" : r === "win" ? "盈利" : "亏损"}</button>))}
</div>
</div>
<div className="max-h-72 overflow-y-auto">
{trades.length === 0 ? <div className="text-center text-slate-400 text-sm py-6"></div> : (
<table className="w-full text-[11px]">
<thead className="bg-slate-50 sticky top-0"><tr className="text-slate-500">
<th className="px-2 py-1.5 text-left font-medium"></th>
<th className="px-2 py-1.5 text-left font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium">PnL(R)</th>
<th className="px-2 py-1.5 text-center font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium"></th>
<th className="px-2 py-1.5 text-right font-medium"></th>
</tr></thead>
<tbody className="divide-y divide-slate-50">
{trades.map((t: any) => {
const holdMin = t.exit_ts && t.entry_ts ? Math.round((t.exit_ts - t.entry_ts) / 60000) : 0;
return (
<tr key={t.id} className="hover:bg-slate-50">
<td className="px-2 py-1.5 font-mono">{t.symbol?.replace("USDT","")}</td>
<td className={`px-2 py-1.5 font-bold ${t.direction === "LONG" ? "text-emerald-600" : "text-red-500"}`}>{t.direction === "LONG" ? "🟢" : "🔴"} {t.direction}</td>
<td className="px-2 py-1.5 text-right font-mono">{fmtPrice(t.entry_price)}</td>
<td className="px-2 py-1.5 text-right font-mono">{t.fill_price ? fmtPrice(t.fill_price) : "-"}</td>
<td className="px-2 py-1.5 text-right font-mono">{t.exit_price ? fmtPrice(t.exit_price) : "-"}</td>
<td className={`px-2 py-1.5 text-right font-mono font-bold ${(t.pnl_r||0) > 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)}</td>
<td className="px-2 py-1.5 text-center"><span className={`px-1 py-0.5 rounded text-[9px] ${t.status==="tp"?"bg-emerald-100 text-emerald-700":t.status==="sl"?"bg-red-100 text-red-700":t.status==="sl_be"?"bg-amber-100 text-amber-700":"bg-slate-100 text-slate-600"}`}>{t.status==="tp"?"止盈":t.status==="sl"?"止损":t.status==="sl_be"?"保本":t.status}</span></td>
<td className={`px-2 py-1.5 text-right font-mono ${Math.abs(t.slippage_bps||0)>8?"text-red-500":Math.abs(t.slippage_bps||0)>2.5?"text-amber-500":"text-slate-600"}`}>{(t.slippage_bps||0).toFixed(1)}bps</td>
<td className="px-2 py-1.5 text-right font-mono text-amber-600">${(t.fee_usdt||0).toFixed(2)}</td>
<td className="px-2 py-1.5 text-right text-slate-400">{holdMin}m</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
</div>
);
}
// ─── 详细统计 ────────────────────────────────────────────────────
function StatsPanel() {
const [data, setData] = useState<any>(null);
useEffect(() => {
const f = async () => { try { const r = await authFetch(`/api/live/stats?strategy=${LIVE_STRATEGY}`); if (r.ok) setData(await r.json()); } catch {} };
f(); const iv = setInterval(f, 30000); return () => clearInterval(iv);
}, []);
if (!data || data.error) return null;
return (
<div className="rounded-xl border border-slate-200 bg-white overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100"><h3 className="font-semibold text-slate-800 text-xs"></h3></div>
<div className="p-3 grid grid-cols-2 lg:grid-cols-4 gap-2 text-xs">
<div><span className="text-slate-400"></span><p className="font-mono font-bold">{data.win_rate}%</p></div>
<div><span className="text-slate-400"></span><p className="font-mono font-bold">{data.win_loss_ratio}</p></div>
<div><span className="text-slate-400"></span><p className="font-mono font-bold text-emerald-600">+{data.avg_win}R</p></div>
<div><span className="text-slate-400"></span><p className="font-mono font-bold text-red-500">-{data.avg_loss}R</p></div>
<div><span className="text-slate-400"></span><p className="font-mono font-bold">{data.mdd}R</p></div>
<div><span className="text-slate-400"></span><p className={`font-mono font-bold ${data.total_pnl >= 0 ? "text-emerald-600" : "text-red-500"}`}>{data.total_pnl >= 0 ? "+" : ""}{data.total_pnl}R</p></div>
<div><span className="text-slate-400"></span><p className="font-mono font-bold">{data.total}</p></div>
<div><span className="text-slate-400">P50</span><p className="font-mono font-bold">{data.p50_slippage_bps}bps</p></div>
<div><span className="text-slate-400">P95</span><p className="font-mono font-bold">{data.p95_slippage_bps}bps</p></div>
<div><span className="text-slate-400"></span><p className="font-mono font-bold">{data.avg_slippage_bps}bps</p></div>
</div>
{data.by_symbol && (
<div className="px-3 pb-3">
<p className="text-[10px] text-slate-400 mb-1"></p>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
{Object.entries(data.by_symbol).map(([sym, v]: [string, any]) => (
<div key={sym} className="rounded-lg bg-slate-50 px-2 py-1.5 text-[11px]">
<span className="font-mono font-bold">{sym.replace("USDT","")}</span>
<span className="text-slate-400 ml-1">{v.total} {v.win_rate}%</span>
<span className={`ml-1 font-mono font-bold ${v.total_pnl >= 0 ? "text-emerald-600" : "text-red-500"}`}>{v.total_pnl >= 0 ? "+" : ""}{v.total_pnl}R</span>
</div>
))}
</div>
</div>
)}
</div>
);
}
// ─── 主页面 ──────────────────────────────────────────────────────
export default function LiveTradingPage() {
const { isLoggedIn, loading } = useAuth();
if (loading) return <div className="text-center text-slate-400 py-8">...</div>;
if (!isLoggedIn) return (
<div className="flex flex-col items-center justify-center h-64 gap-4">
<div className="text-5xl">🔒</div>
<p className="text-slate-600 font-medium"></p>
<Link href="/login" className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm"></Link>
</div>
);
return (
<div className="space-y-3">
<div>
<h1 className="text-lg font-bold text-slate-900"> </h1>
<p className="text-[10px] text-slate-500">V5.2 · USDT永续合约 · </p>
</div>
<RiskStatusPanel />
<EmergencyPanel />
<SummaryCards />
<ActivePositions />
<EquityCurve />
<TradeHistory />
<StatsPanel />
</div>
);
}