"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); 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); } }; if (loading) { return (
加载中...
); } return (
{/* Header */}

策略广场

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

{lastUpdated && (
{lastUpdated.toLocaleTimeString("zh-CN", { timeZone: "Asia/Shanghai", hour12: false })}
)}
{/* Strategy Cards */}
{strategies.map((s) => ( ))}
{strategies.length === 0 && (

暂无运行中的策略

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