"use client"; import { useParams, useSearchParams, useRouter } from "next/navigation"; import { useEffect, useState, useCallback } from "react"; import { authFetch } from "@/lib/auth"; import Link from "next/link"; import dynamic from "next/dynamic"; import { ArrowLeft, CheckCircle, PauseCircle, AlertCircle, Clock, Settings, } from "lucide-react"; // ─── Dynamic imports for each strategy's pages ─────────────────── const SignalsV53 = dynamic(() => import("@/app/signals-v53/page"), { ssr: false }); const SignalsV53Fast = dynamic(() => import("@/app/signals-v53fast/page"), { ssr: false }); const SignalsV53Middle = dynamic(() => import("@/app/signals-v53middle/page"), { ssr: false }); const PaperV53 = dynamic(() => import("@/app/paper-v53/page"), { ssr: false }); const PaperV53Fast = dynamic(() => import("@/app/paper-v53fast/page"), { ssr: false }); const PaperV53Middle = dynamic(() => import("@/app/paper-v53middle/page"), { ssr: false }); const SignalsGeneric = dynamic(() => import("./SignalsGeneric"), { ssr: false }); const PaperGeneric = dynamic(() => import("./PaperGeneric"), { ssr: false }); // ─── UUID → legacy strategy name map ───────────────────────────── const UUID_TO_LEGACY: Record = { "00000000-0000-0000-0000-000000000053": "v53", "00000000-0000-0000-0000-000000000054": "v53_middle", "00000000-0000-0000-0000-000000000055": "v53_fast", }; // ─── Types ──────────────────────────────────────────────────────── interface StrategySummary { strategy_id?: string; 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; cvd_windows?: string; cvd_fast_window?: string; cvd_slow_window?: string; description?: string; symbol?: string; } interface StrategyDetail { weight_direction: number; weight_env: number; weight_aux: number; weight_momentum: number; entry_score: number; // 门1 波动率 gate_vol_enabled: boolean; vol_atr_pct_min: number; // 门2 CVD共振 gate_cvd_enabled: boolean; // 门3 鲸鱼否决 gate_whale_enabled: boolean; whale_usd_threshold: number; whale_flow_pct: number; // 门4 OBI否决 gate_obi_enabled: boolean; obi_threshold: number; // 门5 期现背离 gate_spot_perp_enabled: boolean; spot_perp_threshold: number; sl_atr_multiplier: number; tp1_ratio: number; tp2_ratio: number; timeout_minutes: number; flip_threshold: number; symbol: string; direction: string; cvd_fast_window: string; cvd_slow_window: string; } // ─── Helpers ────────────────────────────────────────────────────── 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); const m = Math.floor((s % 3600) / 60); if (d > 0) return `${d}天${h}h`; 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 异常; } // ─── Config Tab ─────────────────────────────────────────────────── function ConfigTab({ detail, strategyId }: { detail: StrategyDetail; strategyId: string }) { const router = useRouter(); const row = (label: string, value: string | number | boolean) => (
{label} {String(value)}
); const gateRow = (label: string, enabled: boolean, threshold: string) => (
{label}
{enabled ? threshold : "已关闭"}
); return (
{/* 基础配置 */}

基础配置

{row("交易对", detail.symbol)} {row("交易方向", detail.direction === "both" ? "多空双向" : detail.direction === "long_only" ? "只做多" : "只做空")} {row("CVD 快线", detail.cvd_fast_window)} {row("CVD 慢线", detail.cvd_slow_window)}
{/* 四层权重 */}

四层权重

{row("方向权重", `${detail.weight_direction}%`)} {row("环境权重", `${detail.weight_env}%`)} {row("辅助权重", `${detail.weight_aux}%`)} {row("动量权重", `${detail.weight_momentum}%`)} {row("入场阈值", `${detail.entry_score} 分`)}
{/* 五道 Gate */}

过滤门控 (Gate)

{gateRow("门1 波动率", detail.gate_vol_enabled, `ATR% ≥ ${((detail.vol_atr_pct_min ?? 0) * 100).toFixed(2)}%`)} {gateRow("门2 CVD共振", detail.gate_cvd_enabled ?? true, "快慢CVD同向")} {gateRow("门3 鲸鱼否决", detail.gate_whale_enabled, `USD ≥ $${((detail.whale_usd_threshold ?? 50000) / 1000).toFixed(0)}k`)} {gateRow("门4 OBI否决", detail.gate_obi_enabled, `阈值 ${detail.obi_threshold}`)} {gateRow("门5 期现背离", detail.gate_spot_perp_enabled, `溢价 ≤ ${((detail.spot_perp_threshold ?? 0.005) * 100).toFixed(2)}%`)}
{/* 风控参数 */}

风控参数

{row("SL 宽度", `${detail.sl_atr_multiplier} × ATR`)} {row("TP1 目标", `${detail.tp1_ratio} × RD`)} {row("TP2 目标", `${detail.tp2_ratio} × RD`)} {row("超时", `${detail.timeout_minutes} 分钟`)} {row("反转阈值", `${detail.flip_threshold} 分`)}
); } // ─── Content router ─────────────────────────────────────────────── function SignalsContent({ strategyId, symbol, detail }: { strategyId: string; symbol?: string; detail?: StrategyDetail | null }) { const legacy = UUID_TO_LEGACY[strategyId] || strategyId; if (legacy === "v53") return ; if (legacy === "v53_fast") return ; if (legacy === "v53_middle") return ; const weights = detail ? { direction: detail.weight_direction, env: detail.weight_env, aux: detail.weight_aux, momentum: detail.weight_momentum, } : { direction: 38, env: 32, aux: 28, momentum: 2 }; const gates = detail ? { obi_threshold: detail.obi_threshold, whale_usd_threshold: detail.whale_usd_threshold, whale_flow_pct: detail.whale_flow_pct, vol_atr_pct_min: detail.vol_atr_pct_min, spot_perp_threshold: detail.spot_perp_threshold, } : { obi_threshold: 0.3, whale_usd_threshold: 100000, whale_flow_pct: 0.5, vol_atr_pct_min: 0.002, spot_perp_threshold: 0.003 }; return ( ); } function PaperContent({ strategyId, symbol }: { strategyId: string; symbol?: string }) { const legacy = UUID_TO_LEGACY[strategyId] || strategyId; if (legacy === "v53") return ; if (legacy === "v53_fast") return ; if (legacy === "v53_middle") return ; return ; } // ─── 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 [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); const fetchData = useCallback(async () => { try { // Try new /api/strategies/{id} first for full detail const r = await authFetch(`/api/strategies/${strategyId}`); if (r.ok) { const d = await r.json(); const s = d.strategy; setSummary({ strategy_id: s.strategy_id, display_name: s.display_name, status: s.status, started_at: s.started_at || s.created_at || Date.now(), initial_balance: s.initial_balance, current_balance: s.current_balance, net_usdt: s.net_usdt || 0, net_r: s.net_r || 0, trade_count: s.trade_count || 0, win_rate: s.win_rate || 0, avg_win_r: s.avg_win_r || 0, avg_loss_r: s.avg_loss_r || 0, open_positions: s.open_positions || 0, pnl_usdt_24h: s.pnl_usdt_24h || 0, pnl_r_24h: s.pnl_r_24h || 0, cvd_fast_window: s.cvd_fast_window, cvd_slow_window: s.cvd_slow_window, description: s.description, symbol: s.symbol, }); setDetail({ weight_direction: s.weight_direction, weight_env: s.weight_env, weight_aux: s.weight_aux, weight_momentum: s.weight_momentum, entry_score: s.entry_score, gate_vol_enabled: s.gate_vol_enabled, vol_atr_pct_min: s.vol_atr_pct_min, gate_cvd_enabled: s.gate_cvd_enabled, gate_whale_enabled: s.gate_whale_enabled, whale_usd_threshold: s.whale_usd_threshold, whale_flow_pct: s.whale_flow_pct, gate_obi_enabled: s.gate_obi_enabled, obi_threshold: s.obi_threshold, gate_spot_perp_enabled: s.gate_spot_perp_enabled, spot_perp_threshold: s.spot_perp_threshold, sl_atr_multiplier: s.sl_atr_multiplier, tp1_ratio: s.tp1_ratio, tp2_ratio: s.tp2_ratio, timeout_minutes: s.timeout_minutes, flip_threshold: s.flip_threshold, symbol: s.symbol, direction: s.direction, cvd_fast_window: s.cvd_fast_window, cvd_slow_window: s.cvd_slow_window, }); return; } } catch {} // Fallback to legacy /api/strategy-plaza/{id}/summary 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(() => { fetchData().finally(() => setLoading(false)); const iv = setInterval(fetchData, 30000); return () => clearInterval(iv); }, [fetchData]); if (loading) { return (
加载中...
); } const isProfit = (summary?.net_usdt ?? 0) >= 0; const cvdLabel = summary?.cvd_fast_window ? `${summary.cvd_fast_window}/${summary.cvd_slow_window}` : summary?.cvd_windows || ""; return (
{/* Back + Strategy Header */}
策略广场 / {summary?.display_name ?? strategyId}
{/* Summary Bar */} {summary && (
运行 {fmtDur(summary.started_at)} {cvdLabel && ( CVD {cvdLabel} )} 胜率 = 50 ? "text-emerald-600 font-bold" : "text-amber-600 font-bold"}>{summary.win_rate}% 净R {summary.net_r >= 0 ? "+" : ""}{summary.net_r}R 余额 {summary.current_balance.toLocaleString()} U 24h = 0 ? "text-emerald-600" : "text-red-500"}`}>{summary.pnl_usdt_24h >= 0 ? "+" : ""}{summary.pnl_usdt_24h} U
)} {/* Tabs */}
{[ { key: "signals", label: "📊 信号引擎" }, { key: "paper", label: "📈 模拟盘" }, { key: "config", label: "⚙️ 参数配置" }, ].map(({ key, label }) => ( ))}
{/* Content */}
{tab === "signals" && } {tab === "paper" && } {tab === "config" && detail && } {tab === "config" && !detail && (
暂无配置信息
)}
); }