"use client"; import { useEffect, useState, useCallback } from "react"; import { authFetch } from "@/lib/auth"; import { useAuth } from "@/lib/auth"; import Link from "next/link"; import { ComposedChart, Area, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, CartesianGrid, Legend } from "recharts"; type Symbol = "BTC" | "ETH" | "XRP" | "SOL"; interface IndicatorRow { ts: number; cvd_fast: number; cvd_mid: number; cvd_day: number; atr_5m: number; vwap_30m: number; price: number; score: number; signal: string | null; } interface LatestIndicator { ts: number; cvd_fast: number; cvd_mid: number; cvd_day: number; cvd_fast_slope: number; atr_5m: number; atr_percentile: number; vwap_30m: number; price: number; p95_qty: number; p99_qty: number; score: number; signal: string | null; tier?: "light" | "standard" | "heavy" | null; factors?: { direction?: { score?: number }; crowding?: { score?: number }; environment?: { score?: number }; confirmation?: { score?: number }; auxiliary?: { score?: number }; } | null; } interface MarketIndicatorValue { value: Record; ts: number; } interface MarketIndicatorSet { long_short_ratio?: MarketIndicatorValue; top_trader_position?: MarketIndicatorValue; open_interest_hist?: MarketIndicatorValue; coinbase_premium?: MarketIndicatorValue; } const WINDOWS = [ { label: "1h", value: 60 }, { label: "4h", value: 240 }, { label: "12h", value: 720 }, { label: "24h", value: 1440 }, ]; function bjtStr(ms: number) { const d = new Date(ms + 8 * 3600 * 1000); return `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`; } function fmt(v: number, decimals = 1): string { if (Math.abs(v) >= 1000000) return `${(v / 1000000).toFixed(1)}M`; if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(1)}K`; return v.toFixed(decimals); } function pct(v: number, digits = 1): string { return `${(v * 100).toFixed(digits)}%`; } function LayerScore({ label, score, max, colorClass }: { label: string; score: number; max: number; colorClass: string }) { const ratio = Math.max(0, Math.min((score / max) * 100, 100)); return (
{label} {score}/{max}
); } function MarketIndicatorsCards({ symbol }: { symbol: Symbol }) { const [data, setData] = useState(null); useEffect(() => { const fetch = async () => { try { const res = await authFetch("/api/signals/market-indicators"); if (!res.ok) return; const json = await res.json(); setData(json[symbol] || null); } catch {} }; fetch(); const iv = setInterval(fetch, 5000); return () => clearInterval(iv); }, [symbol]); if (!data) return
等待市场指标数据...
; // value可能是JSON字符串或对象,统一解析 const parseVal = (v: unknown): Record => { if (!v) return {}; if (typeof v === "string") { try { return JSON.parse(v); } catch { return {}; } } if (typeof v === "object") return v as Record; return {}; }; const lsVal = parseVal(data.long_short_ratio?.value); const topVal = parseVal(data.top_trader_position?.value); const oiVal = parseVal(data.open_interest_hist?.value); const premVal = parseVal(data.coinbase_premium?.value); const longPct = Number(lsVal?.longAccount ?? 0.5) * 100; const shortPct = Number(lsVal?.shortAccount ?? 0.5) * 100; const topLong = Number(topVal?.longAccount ?? 0.5) * 100; const topShort = Number(topVal?.shortAccount ?? 0.5) * 100; const oiValue = Number(oiVal?.sumOpenInterestValue ?? 0); const oiDisplay = oiValue >= 1e9 ? `$${(oiValue / 1e9).toFixed(2)}B` : oiValue >= 1e6 ? `$${(oiValue / 1e6).toFixed(0)}M` : `$${oiValue.toFixed(0)}`; const premium = Number(premVal?.premium_pct ?? 0); return (

多空比 (L/S)

Long: {longPct.toFixed(1)}%

Short: {shortPct.toFixed(1)}%

大户持仓

大户做多: {topLong.toFixed(1)}%

方向: {topLong >= 55 ? "多头占优" : topLong <= 45 ? "空头占优" : "中性"}

持仓量 (OI)

{oiDisplay}

{Number(oiVal?.sumOpenInterest ?? 0).toFixed(0)} BTC

Coinbase Premium

= 0 ? "text-emerald-600" : "text-red-500"}`}>{premium >= 0 ? "+" : ""}{premium.toFixed(4)}%

机构: {premium > 0.005 ? "偏多" : premium < -0.005 ? "偏空" : "中性"}

); } // ─── 信号历史 ──────────────────────────────────────────────────── interface SignalRecord { ts: number; score: number; signal: string; } function bjtFull(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")}:${String(d.getUTCSeconds()).padStart(2, "0")}`; } function SignalHistory({ symbol }: { symbol: Symbol }) { const [data, setData] = useState([]); useEffect(() => { const fetchData = async () => { try { const res = await authFetch(`/api/signals/signal-history?symbol=${symbol}&limit=20`); if (!res.ok) return; const json = await res.json(); setData(json.data || []); } catch {} }; fetchData(); const iv = setInterval(fetchData, 15000); return () => clearInterval(iv); }, [symbol]); if (data.length === 0) return null; return (

最近信号

最近20条有效信号记录(北京时间)

{data.map((s, i) => (
{s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"} {bjtFull(s.ts)}
{s.score}/100 = 85 ? "bg-red-100 text-red-700" : s.score >= 75 ? "bg-blue-100 text-blue-700" : "bg-slate-100 text-slate-600" }`}> {s.score >= 85 ? "加仓" : s.score >= 75 ? "标准" : "轻仓"}
))}
); } // ─── 实时指标卡片 ──────────────────────────────────────────────── function IndicatorCards({ symbol }: { symbol: Symbol }) { const [data, setData] = useState(null); useEffect(() => { const fetch = async () => { try { const res = await authFetch("/api/signals/latest"); if (!res.ok) return; const json = await res.json(); setData(json[symbol] || null); } catch {} }; fetch(); const iv = setInterval(fetch, 5000); return () => clearInterval(iv); }, [symbol]); if (!data) return
等待指标数据...
; const cvdFastDir = data.cvd_fast > 0 ? "多" : "空"; const cvdMidDir = data.cvd_mid > 0 ? "多" : "空"; const priceVsVwap = data.price > data.vwap_30m ? "上方" : "下方"; // 核心条件检查 const core1 = data.cvd_fast > 0 && data.cvd_fast_slope > 0 ? "✅" : data.cvd_fast < 0 && data.cvd_fast_slope < 0 ? "✅空" : "⬜"; const core2 = data.cvd_mid !== 0 ? "✅" : "⬜"; const core3 = data.price !== data.vwap_30m ? "✅" : "⬜"; return (
{/* CVD三轨 */}

CVD_fast (30m)

= 0 ? "text-emerald-600" : "text-red-500"}`}> {fmt(data.cvd_fast)}

斜率: = 0 ? "text-emerald-600" : "text-red-500"}> {data.cvd_fast_slope >= 0 ? "↑" : "↓"} {fmt(Math.abs(data.cvd_fast_slope))}

CVD_mid (4h)

= 0 ? "text-emerald-600" : "text-red-500"}`}> {fmt(data.cvd_mid)}

大方向: {cvdMidDir}头占优

CVD_day (日内)

= 0 ? "text-emerald-600" : "text-red-500"}`}> {fmt(data.cvd_day)}

盘中基线

{/* ATR + VWAP + 大单 */}

ATR (5m×14)

${fmt(data.atr_5m, 2)}

分位: 60 ? "text-amber-600 font-semibold" : "text-slate-500"}> {data.atr_percentile.toFixed(0)}% {data.atr_percentile > 60 ? " 🔥扩张" : data.atr_percentile < 40 ? " 压缩" : ""}

VWAP (30m)

${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}

价格在VWAP data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap}

大单阈值 P95

{data.p95_qty.toFixed(4)}

24h动态分位

大单阈值 P99

{data.p99_qty.toFixed(4)}

超大单门槛

{/* 信号状态(V5.1) */}

当前信号

{data.signal === "LONG" ? "🟢 做多" : data.signal === "SHORT" ? "🔴 做空" : "⚪ 无信号"}

总分

{data.score}/100

档位: {data.tier === "heavy" ? "加仓" : data.tier === "standard" ? "标准" : data.tier === "light" ? "轻仓" : "不开仓"}

{data.signal && (
{core1} CVD_fast方向 {core2} CVD_mid方向 {core3} VWAP位置
)}
); } // ─── CVD三轨图 ────────────────────────────────────────────────── function CVDChart({ symbol, minutes }: { symbol: Symbol; minutes: number }) { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const fetchData = useCallback(async (silent = false) => { try { const res = await authFetch(`/api/signals/indicators?symbol=${symbol}&minutes=${minutes}`); if (!res.ok) return; const json = await res.json(); setData(json.data || []); if (!silent) setLoading(false); } catch {} }, [symbol, minutes]); useEffect(() => { setLoading(true); fetchData(); const iv = setInterval(() => fetchData(true), 30000); return () => clearInterval(iv); }, [fetchData]); const chartData = data.map(d => ({ time: bjtStr(d.ts), fast: parseFloat(d.cvd_fast?.toFixed(2) || "0"), mid: parseFloat(d.cvd_mid?.toFixed(2) || "0"), price: d.price, })); // 价格轴自适应 const prices = chartData.map(d => d.price).filter(v => v > 0); const pMin = prices.length ? Math.min(...prices) : 0; const pMax = prices.length ? Math.max(...prices) : 0; const pPad = (pMax - pMin) * 0.3 || pMax * 0.001; if (loading) return
加载指标数据...
; if (data.length === 0) return
暂无指标数据,signal-engine需运行积累
; return ( v >= 1000 ? `$${(v / 1000).toFixed(1)}k` : `$${v.toFixed(0)}`} /> { if (name === "price") return [`$${Number(v).toLocaleString()}`, "币价"]; if (name === "fast") return [fmt(Number(v)), "CVD_fast(30m)"]; return [fmt(Number(v)), "CVD_mid(4h)"]; }} contentStyle={{ background: "#fff", border: "1px solid #e2e8f0", borderRadius: 8, fontSize: 11 }} /> ); } // ─── 主页面 ────────────────────────────────────────────────────── export default function SignalsPage() { const { isLoggedIn, loading } = useAuth(); const [symbol, setSymbol] = useState("BTC"); const [minutes, setMinutes] = useState(240); if (loading) return
加载中...
; if (!isLoggedIn) return (
🔒

请先登录查看信号数据

登录 注册
); return (
{/* 标题 */}

V5.1 信号引擎

五层100分评分 + 市场拥挤度 + 环境确认

{(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => ( ))}
{/* 实时指标卡片 */} {/* Market Indicators */}

Market Indicators

{/* 信号历史 */} {/* CVD三轨图 */}

CVD三轨 + 币价

蓝色面积=CVD_fast(30m) · 紫色虚线=CVD_mid(4h) · 橙色虚线=币价

{WINDOWS.map(w => ( ))}
{/* 说明 */}

V5.1评分逻辑:方向层45分 + 拥挤层20分 + 环境层15分 + 确认层15分 + 辅助层5分(方向加速可额外+5)。

开仓档位:<60不开仓,60-74轻仓,75-84标准仓位,≥85允许加仓;信号冷却10分钟。

); }