"use client"; import { useEffect, useState, useCallback } from "react"; import { useParams, useSearchParams, useRouter } from "next/navigation"; import { authFetch } from "@/lib/auth"; import Link from "next/link"; import { ArrowLeft, CheckCircle, PauseCircle, AlertCircle, Clock, TrendingUp, TrendingDown, } from "lucide-react"; // ─── Types ──────────────────────────────────────────────────────── interface StrategySummary { id: string; display_name: string; status: string; 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; cvd_windows?: string; description?: string; } interface Signal { ts: number; symbol: string; score: number; signal: string | null; price: number; factors: any; } interface Trade { id: number; symbol: string; direction: string; score: number; entry_price: number; exit_price: number | null; tp1_price: number; tp2_price: number; sl_price: number; tp1_hit: boolean; pnl_r: number; risk_distance: number; entry_ts: number; exit_ts: number | null; status: string; } // ─── Helpers ────────────────────────────────────────────────────── 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 fmtDur(ms: number) { const s = Math.floor((Date.now() - ms) / 1000); const d = Math.floor(s / 86400); const h = Math.floor((s % 86400) / 3600); if (d > 0) return `${d}天${h}h`; const m = Math.floor((s % 3600) / 60); if (h > 0) return `${h}h${m}m`; return `${m}m`; } function StatusBadge({ status }: { status: string }) { if (status === "running") return 运行中; if (status === "paused") return 已暂停; return 异常; } // ─── Sub-views ──────────────────────────────────────────────────── function SignalsView({ strategyId }: { strategyId: string }) { const [signals, setSignals] = useState([]); const [loading, setLoading] = useState(true); const fetch_ = useCallback(async () => { try { const r = await authFetch(`/api/strategy-plaza/${strategyId}/signals?limit=40`); const d = await r.json(); setSignals(d.signals || []); } catch {} setLoading(false); }, [strategyId]); useEffect(() => { fetch_(); const iv = setInterval(fetch_, 15000); return () => clearInterval(iv); }, [fetch_]); if (loading) return
加载信号中...
; return (
{signals.map((s, i) => { const fc = s.factors && (typeof s.factors === "string" ? JSON.parse(s.factors) : s.factors); return ( ); })}
时间(北京) 币种 价格 分数 信号 CVD 30m CVD 4h
{bjt(s.ts)} {s.symbol.replace("USDT", "")} {s.price?.toLocaleString()} = 75 ? "text-emerald-400" : s.score >= 50 ? "text-yellow-400" : "text-gray-500"}`}> {s.score} {s.signal ? ( {s.signal} ) : } {fc?.cvd_30m?.toFixed(0) ?? "—"} {fc?.cvd_4h?.toFixed(0) ?? "—"}
{signals.length === 0 &&
暂无信号数据
}
); } function TradesView({ strategyId }: { strategyId: string }) { const [trades, setTrades] = useState([]); const [loading, setLoading] = useState(true); const fetch_ = useCallback(async () => { try { const r = await authFetch(`/api/strategy-plaza/${strategyId}/trades?limit=50`); const d = await r.json(); setTrades(d.trades || []); } catch {} setLoading(false); }, [strategyId]); useEffect(() => { fetch_(); }, [fetch_]); if (loading) return
加载交易记录中...
; return (
{trades.map((t) => { const isWin = t.pnl_r > 0; const isActive = !t.exit_ts; return ( ); })}
入场时间 出场时间 币种 方向 入场价 出场价 盈亏R 盈亏U 状态
{bjt(t.entry_ts)} {t.exit_ts ? bjt(t.exit_ts) : 持仓中} {t.symbol.replace("USDT", "")} {t.direction === "LONG" ? "多" : "空"} {t.entry_price?.toLocaleString()} {t.exit_price?.toLocaleString() ?? "—"} {isActive ? "活跃" : `${isWin ? "+" : ""}${t.pnl_r?.toFixed(3)}R`} {isActive ? "—" : `${isWin ? "+" : ""}${Math.round(t.pnl_r * 200)}U`} {isActive ? "活跃" : isWin ? "盈利" : "亏损"}
{trades.length === 0 &&
暂无交易记录
}
); } // ─── Main Page ──────────────────────────────────────────────────── export default function StrategyDetailPage() { const params = useParams(); const searchParams = useSearchParams(); const router = useRouter(); const strategyId = params?.id as string; const tab = searchParams?.get("tab") || "signals"; const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const fetchSummary = useCallback(async () => { try { const r = await authFetch(`/api/strategy-plaza/${strategyId}/summary`); if (r.ok) { const d = await r.json(); setSummary(d); } } catch {} setLoading(false); }, [strategyId]); useEffect(() => { fetchSummary(); const iv = setInterval(fetchSummary, 30000); return () => clearInterval(iv); }, [fetchSummary]); if (loading) return
加载中...
; if (!summary) return
策略不存在
; const isProfit = summary.net_usdt >= 0; return (
{/* Back */} 返回策略广场 {/* Header */}

{summary.display_name}

{summary.description &&

{summary.description}

}
运行 {fmtDur(summary.started_at)} {summary.cvd_windows && ( CVD {summary.cvd_windows} )}
{isProfit ? "+" : ""}{summary.net_usdt.toLocaleString()} U
{summary.current_balance.toLocaleString()} / {summary.initial_balance.toLocaleString()} USDT
{/* Stats */}
{[ { label: "胜率", value: `${summary.win_rate}%`, color: summary.win_rate >= 50 ? "text-emerald-400" : summary.win_rate >= 45 ? "text-yellow-400" : "text-red-400" }, { label: "净R", value: `${summary.net_r >= 0 ? "+" : ""}${summary.net_r}R`, color: summary.net_r >= 0 ? "text-emerald-400" : "text-red-400" }, { label: "24h盈亏", value: `${summary.pnl_usdt_24h >= 0 ? "+" : ""}${summary.pnl_usdt_24h}U`, color: summary.pnl_usdt_24h >= 0 ? "text-emerald-400" : "text-red-400" }, { label: "总交易数", value: summary.trade_count, color: "text-white" }, ].map(({ label, value, color }) => (
{label}
{value}
))}
{/* Tabs */}
{["signals", "paper"].map((t) => ( ))}
{/* Tab Content */}
{tab === "signals" ? ( ) : ( )}
); }