"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?: {
track?: string;
direction?: { score?: number; max?: number; cvd_resonance?: number; p99_flow?: number; accel_bonus?: number };
crowding?: { score?: number; max?: number; lsr_contrarian?: number; top_trader_position?: number };
environment?: { score?: number; max?: number };
auxiliary?: { score?: number; max?: number; coinbase_premium?: number };
// BTC gate fields
gate_passed?: boolean;
block_reason?: string; // BTC用
gate_block?: string; // ALT用
obi_raw?: number;
spot_perp_div?: number;
whale_cvd_ratio?: number;
atr_pct_price?: number;
alt_score_ref?: number;
} | null;
}
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 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 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 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 (
);
}
// ─── ALT Gate 状态卡片 ──────────────────────────────────────────
const ALT_GATE_THRESHOLDS: Record = {
ETH: { vol: "0.3%", obi: "0.35", spd: "0.5%", whale: "$50k" },
XRP: { vol: "0.4%", obi: "0.40", spd: "0.6%", whale: "$30k" },
SOL: { vol: "0.6%", obi: "0.45", spd: "0.8%", whale: "$20k" },
};
function ALTGateCard({ symbol, factors }: { symbol: Symbol; factors: LatestIndicator["factors"] }) {
if (!factors || symbol === "BTC") return null;
const thresholds = ALT_GATE_THRESHOLDS[symbol] ?? ALT_GATE_THRESHOLDS["ETH"];
const passed = factors.gate_passed ?? true;
const blockReason = factors.gate_block;
return (
🔒 {symbol} Gate-Control
{passed ? "✅ Gate通过" : "❌ 否决"}
波动率
{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%
需 ≥{thresholds.vol}
OBI
= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.obi_raw ?? 0) * 100).toFixed(2)}%
否决±{thresholds.obi}
期现背离
= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps
否决±{thresholds.spd}
鲸鱼阈值
{thresholds.whale}
大单门槛
{blockReason && (
否决原因: {blockReason}
)}
);
}
// ─── BTC Gate 状态卡片 ───────────────────────────────────────────
function BTCGateCard({ factors }: { factors: LatestIndicator["factors"] }) {
if (!factors) return null;
return (
⚡ BTC Gate-Control
{factors.gate_passed ? "✅ Gate通过" : "❌ 否决"}
波动率
{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%
需 ≥0.2%
OBI
= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.obi_raw ?? 0) * 100).toFixed(2)}%
盘口失衡
期现背离
= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps
spot-perp
巨鲸CVD
= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.whale_cvd_ratio ?? 0) * 100).toFixed(2)}%
>$100k
{factors.block_reason && (
否决原因: {factors.block_reason}
)}
);
}
// ─── 实时指标卡片 ────────────────────────────────────────────────
function IndicatorCards({ symbol }: { symbol: Symbol }) {
const [data, setData] = useState(null);
const strategy = symbol === "BTC" ? "v53_btc" : "v53_alt";
useEffect(() => {
const fetch = async () => {
try {
const res = await authFetch(`/api/signals/latest?strategy=${strategy}`);
if (!res.ok) return;
const json = await res.json();
setData(json[symbol] || null);
} catch {}
};
fetch();
const iv = setInterval(fetch, 5000);
return () => clearInterval(iv);
}, [symbol, strategy]);
if (!data) return 等待指标数据...
;
const isBTC = symbol === "BTC";
const priceVsVwap = 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)}
{data.cvd_mid > 0 ? "多" : "空"}头占优
CVD共振
= 0 && data.cvd_mid >= 0 ? "text-emerald-600" : data.cvd_fast < 0 && data.cvd_mid < 0 ? "text-red-500" : "text-slate-400"}`}>
{data.cvd_fast >= 0 && data.cvd_mid >= 0 ? "✅ 多头共振" : data.cvd_fast < 0 && data.cvd_mid < 0 ? "✅ 空头共振" : "⚠️ 分歧"}
V5.3核心信号
{/* ATR + VWAP */}
ATR
${fmt(data.atr_5m, 2)}
60 ? "text-amber-600 font-semibold" : "text-slate-400"}>
{data.atr_percentile.toFixed(0)}%{data.atr_percentile > 60 ? "🔥" : ""}
VWAP
${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}
价格在 data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap}
P95
{data.p95_qty.toFixed(4)}
大单阈值
P99
{data.p99_qty.toFixed(4)}
超大单
{/* 信号状态 */}
{isBTC ? "BTC Gate-Control" : "ALT 四层评分"}
{" · "}{isBTC ? "v53_btc" : "v53_alt"}
{data.signal === "LONG" ? "🟢 做多" : data.signal === "SHORT" ? "🔴 做空" : "⚪ 无信号"}
{isBTC ? (
<>
{data.factors?.alt_score_ref ?? data.score}/100
参考分
{(data.factors?.gate_passed) ? (data.tier === "standard" ? "标准" : "不开仓") : "Gate否决"}
>
) : (
<>
{data.score}/100
{data.tier === "heavy" ? "加仓" : data.tier === "standard" ? "标准" : "不开仓"}
>
)}
{/* 四层分数 — ALT和BTC都显示 */}
{/* ALT Gate 卡片 */}
{!isBTC && data.factors &&
}
{/* BTC Gate 卡片 */}
{isBTC && data.factors &&
}
);
}
// ─── 信号历史 ────────────────────────────────────────────────────
interface SignalRecord {
ts: number;
score: number;
signal: string;
}
function SignalHistory({ symbol }: { symbol: Symbol }) {
const [data, setData] = useState([]);
const strategy = symbol === "BTC" ? "v53_btc" : "v53_alt";
useEffect(() => {
const fetchData = async () => {
try {
const res = await authFetch(`/api/signals/signal-history?symbol=${symbol}&limit=20&strategy=${strategy}`);
if (!res.ok) return;
const json = await res.json();
setData(json.data || []);
} catch {}
};
fetchData();
const iv = setInterval(fetchData, 15000);
return () => clearInterval(iv);
}, [symbol, strategy]);
if (data.length === 0) return null;
return (
最近信号 ({strategy})
{data.map((s, i) => (
{s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"}
{bjtFull(s.ts)}
{s.score}
= 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 ? "标准" : "不开仓"}
))}
);
}
// ─── CVD图表 ────────────────────────────────────────────────────
function CVDChart({ symbol, minutes }: { symbol: Symbol; minutes: number }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const strategy = symbol === "BTC" ? "v53_btc" : "v53_alt";
const fetchData = useCallback(async (silent = false) => {
try {
const res = await authFetch(`/api/signals/indicators?symbol=${symbol}&minutes=${minutes}&strategy=${strategy}`);
if (!res.ok) return;
const json = await res.json();
setData(json.data || []);
if (!silent) setLoading(false);
} catch {}
}, [symbol, minutes, strategy]);
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 暂无 V5.3 指标数据,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 SignalsV53Page() {
const { isLoggedIn, loading } = useAuth();
const [symbol, setSymbol] = useState("ETH");
const [minutes, setMinutes] = useState(240);
if (loading) return 加载中...
;
if (!isLoggedIn) return (
);
return (
⚡ 信号引擎 V5.3
四层评分 55/25/15/5 · ALT双轨 + BTC gate-control ·
{symbol === "BTC" ? " 🔵 BTC轨(gate-control)" : " 🟣 ALT轨(ETH/XRP/SOL)"}
{(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => (
))}
CVD三轨 + 币价
蓝=fast(30m) · 紫=mid(4h) · 橙=价格
{WINDOWS.map(w => (
))}
📖 V5.3 双轨信号说明
🟣 ALT轨(ETH/XRP/SOL)— 四层线性评分
1️⃣ 方向层(55分) — CVD共振30分(fast+mid同向)+ P99大单对齐20分 + 加速奖励5分。删除独立确认层,解决CVD双重计分问题。
2️⃣ 拥挤层(25分) — LSR反向拥挤15分(散户过度拥挤=信号)+ 大户持仓方向10分。
3️⃣ 环境层(15分) — OI变化率,新资金进场vs撤离,判断趋势持续性。
4️⃣ 辅助层(5分) — Coinbase Premium,美系机构动向。
🔵 BTC轨 — Gate-Control逻辑
波动率门控:ATR/Price ≥ 0.2%,低波动行情拒绝开仓
OBI否决:订单簿失衡超阈值且与信号方向冲突时否决(实时100ms)
期现背离否决:spot与perp价差超阈值时否决(实时1s)
巨鲸CVD:>$100k成交额净CVD,15分钟滚动窗口实时计算
档位:<75不开仓 · 75-84标准 · ≥85加仓 · 冷却10分钟
);
}