"use client"; import { useEffect, useState, useCallback } from "react"; import { authFetch } from "@/lib/auth"; import { useAuth } from "@/lib/auth"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { TrendingUp, TrendingDown, Clock, Activity, AlertCircle, CheckCircle, PauseCircle, Plus, Settings, Trash2, PlusCircle, } from "lucide-react"; interface StrategyCard { strategy_id: string; display_name: string; symbol: string; status: "running" | "paused" | "error"; started_at: number; initial_balance: number; current_balance: number; net_usdt: number; net_r: number; trade_count: number; win_rate: number; avg_win_r: number; avg_loss_r: number; open_positions: number; pnl_usdt_24h: number; pnl_r_24h: number; std_r: number; last_trade_at: number | null; } function formatDuration(ms: number): string { const totalSec = Math.floor((Date.now() - ms) / 1000); const d = Math.floor(totalSec / 86400); const h = Math.floor((totalSec % 86400) / 3600); const m = Math.floor((totalSec % 3600) / 60); if (d > 0) return `${d}天 ${h}小时`; if (h > 0) return `${h}小时 ${m}分`; return `${m}分钟`; } function formatTime(ms: number | null): string { if (!ms) return "—"; const d = new Date(ms); return d.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }); } function StatusBadge({ status }: { status: string }) { if (status === "running") { return ( 运行中 ); } if (status === "paused") { return ( 已暂停 ); } return ( 异常 ); } // ── AddBalanceModal ──────────────────────────────────────────────────────────── function AddBalanceModal({ strategy, onClose, onSuccess, }: { strategy: StrategyCard; onClose: () => void; onSuccess: () => void; }) { const [amount, setAmount] = useState(1000); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); const handleSubmit = async () => { if (amount <= 0) { setError("金额必须大于0"); return; } setSubmitting(true); setError(""); try { const res = await authFetch(`/api/strategies/${strategy.strategy_id}/add-balance`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ amount }), }); if (!res.ok) throw new Error("追加失败"); onSuccess(); onClose(); } catch (e) { setError(e instanceof Error ? e.message : "未知错误"); } finally { setSubmitting(false); } }; return (

追加余额

策略:{strategy.display_name}

setAmount(parseFloat(e.target.value) || 0)} className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm" />

追加后初始资金:{(strategy.initial_balance + amount).toLocaleString()} USDT / 余额:{(strategy.current_balance + amount).toLocaleString()} USDT

{error &&

{error}

}
); } // ── StrategyCardComponent ────────────────────────────────────────────────────── function StrategyCardComponent({ s, onDeprecate, onAddBalance, }: { s: StrategyCard; onDeprecate: (s: StrategyCard) => void; onAddBalance: (s: StrategyCard) => void; }) { const isProfit = s.net_usdt >= 0; const is24hProfit = s.pnl_usdt_24h >= 0; const balancePct = ((s.current_balance / s.initial_balance) * 100).toFixed(1); const symbolShort = s.symbol?.replace("USDT", "") || ""; return (
{/* Header */}

{s.display_name}

{symbolShort && ( {symbolShort} )}
{formatDuration(s.started_at)}
{/* Main PnL */}
当前余额
{s.current_balance.toLocaleString()} USDT
累计盈亏
{isProfit ? "+" : ""}{s.net_usdt.toLocaleString()} U
{/* Balance Bar */}
{balancePct}% {s.initial_balance.toLocaleString()} USDT 初始
{/* Stats Row */}
胜率
= 50 ? "text-emerald-600" : s.win_rate >= 45 ? "text-amber-600" : "text-red-500"}`}> {s.win_rate}%
净R
= 0 ? "text-emerald-600" : "text-red-500"}`}> {s.net_r >= 0 ? "+" : ""}{s.net_r}R
交易数
{s.trade_count}
{/* Avg win/loss */}
平均赢 +{s.avg_win_r}R
平均亏 {s.avg_loss_r}R
{/* Footer */}
{is24hProfit ? ( ) : ( )} 24h {is24hProfit ? "+" : ""}{s.pnl_usdt_24h.toLocaleString()} U
{s.open_positions > 0 ? ( {s.open_positions}仓持仓中 ) : ( 上次: {formatTime(s.last_trade_at)} )}
{/* Action Buttons */}
调整参数
); } // ── Main Page ───────────────────────────────────────────────────────────────── export default function StrategyPlazaPage() { useAuth(); const router = useRouter(); const [strategies, setStrategies] = useState([]); const [loading, setLoading] = useState(true); const [lastUpdated, setLastUpdated] = useState(null); const [addBalanceTarget, setAddBalanceTarget] = useState(null); type SymbolFilter = "ALL" | "BTCUSDT" | "ETHUSDT" | "XRPUSDT" | "SOLUSDT"; type StatusFilter = "all" | "running" | "paused" | "error"; type PnlFilter = "all" | "positive" | "negative"; type PositionFilter = "all" | "with_open" | "no_open"; type SortKey = "recent" | "net_usdt_desc" | "net_usdt_asc" | "pnl24h_desc" | "pnl24h_asc"; const [symbolFilter, setSymbolFilter] = useState("ALL"); const [statusFilter, setStatusFilter] = useState("all"); const [pnlFilter, setPnlFilter] = useState("all"); const [positionFilter, setPositionFilter] = useState("all"); const [sortKey, setSortKey] = useState("net_usdt_desc"); const fetchData = useCallback(async () => { try { const res = await authFetch("/api/strategies"); const data = await res.json(); setStrategies(data.strategies || []); setLastUpdated(new Date()); } catch (e) { console.error(e); } finally { setLoading(false); } }, []); useEffect(() => { fetchData(); const interval = setInterval(fetchData, 30000); return () => clearInterval(interval); }, [fetchData]); const handleDeprecate = async (s: StrategyCard) => { if (!confirm(`确认废弃策略「${s.display_name}」?\n\n废弃后策略停止运行,数据永久保留,可在废弃列表中重新启用。`)) return; try { await authFetch(`/api/strategies/${s.strategy_id}/deprecate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ confirm: true }), }); await fetchData(); } catch (e) { console.error(e); } }; const filteredStrategies = strategies .filter((s) => { if (symbolFilter !== "ALL" && s.symbol !== symbolFilter) return false; if (statusFilter !== "all" && s.status !== statusFilter) return false; if (pnlFilter === "positive" && s.net_usdt <= 0) return false; if (pnlFilter === "negative" && s.net_usdt >= 0) return false; if (positionFilter === "with_open" && s.open_positions <= 0) return false; if (positionFilter === "no_open" && s.open_positions > 0) return false; return true; }) .sort((a, b) => { switch (sortKey) { case "net_usdt_desc": return b.net_usdt - a.net_usdt; case "net_usdt_asc": return a.net_usdt - b.net_usdt; case "pnl24h_desc": return b.pnl_usdt_24h - a.pnl_usdt_24h; case "pnl24h_asc": return a.pnl_usdt_24h - b.pnl_usdt_24h; case "recent": default: { const aTs = a.last_trade_at ?? a.started_at ?? 0; const bTs = b.last_trade_at ?? b.started_at ?? 0; return bTs - aTs; } } }); if (loading) { return (
加载中...
); } return (
{/* Header */}

策略广场

点击策略名查看信号引擎和模拟盘详情

{lastUpdated && (
{lastUpdated.toLocaleTimeString("zh-CN", { timeZone: "Asia/Shanghai", hour12: false })}
)}
{/* Filters & Sorting */}
{/* 左侧:币种 + 盈亏过滤 */}
币种: {(["ALL", "BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"] as SymbolFilter[]).map((sym) => { const label = sym === "ALL" ? "全部" : sym.replace("USDT", ""); const active = symbolFilter === sym; return ( ); })}
盈亏: {[ { key: "all" as PnlFilter, label: "全部" }, { key: "positive" as PnlFilter, label: "仅盈利" }, { key: "negative" as PnlFilter, label: "仅亏损" }, ].map((opt) => { const active = pnlFilter === opt.key; return ( ); })}
{/* 右侧:状态 + 持仓 + 排序 */}
状态: {[ { key: "all" as StatusFilter, label: "全部" }, { key: "running" as StatusFilter, label: "运行中" }, { key: "paused" as StatusFilter, label: "已暂停" }, { key: "error" as StatusFilter, label: "异常" }, ].map((opt) => { const active = statusFilter === opt.key; return ( ); })}
持仓: {[ { key: "all" as PositionFilter, label: "全部" }, { key: "with_open" as PositionFilter, label: "有持仓" }, { key: "no_open" as PositionFilter, label: "无持仓" }, ].map((opt) => { const active = positionFilter === opt.key; return ( ); })}
排序:
{/* Strategy Cards */}
{filteredStrategies.map((s) => ( ))}
{filteredStrategies.length === 0 && (

暂无运行中的策略

)} {/* Add Balance Modal */} {addBalanceTarget && ( setAddBalanceTarget(null)} onSuccess={fetchData} /> )}
); }