arbitrage-engine/frontend/app/signals-v53/page.tsx

555 lines
26 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 (
<div className="flex items-center gap-2">
<span className="text-[10px] text-slate-500 w-6 shrink-0">{label}</span>
<div className="flex-1 h-1.5 rounded-full bg-slate-100 overflow-hidden">
<div className={`h-full ${colorClass}`} style={{ width: `${ratio}%` }} />
</div>
<span className="text-[10px] font-mono text-slate-600 w-8 text-right">{score}/{max}</span>
</div>
);
}
// ─── ALT Gate 状态卡片 ──────────────────────────────────────────
const ALT_GATE_THRESHOLDS: Record<string, { vol: string; obi: string; spd: string; whale: string }> = {
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 (
<div className={`rounded-xl border px-3 py-2 mt-2 ${passed ? "border-purple-200 bg-purple-50" : "border-red-200 bg-red-50"}`}>
<div className="flex items-center justify-between mb-1.5">
<p className="text-[10px] font-semibold text-purple-800">🔒 {symbol} Gate-Control</p>
<span className={`text-[10px] font-bold px-2 py-0.5 rounded ${passed ? "bg-emerald-100 text-emerald-700" : "bg-red-100 text-red-600"}`}>
{passed ? "✅ Gate通过" : "❌ 否决"}
</span>
</div>
<div className="grid grid-cols-4 gap-1.5">
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400"></p>
<p className="text-xs font-mono text-slate-800">{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%</p>
<p className="text-[9px] text-slate-400"> {thresholds.vol}</p>
</div>
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400">OBI</p>
<p className={`text-xs font-mono ${(factors.obi_raw ?? 0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.obi_raw ?? 0) * 100).toFixed(2)}%
</p>
<p className="text-[9px] text-slate-400">±{thresholds.obi}</p>
</div>
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400"></p>
<p className={`text-xs font-mono ${(factors.spot_perp_div ?? 0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps
</p>
<p className="text-[9px] text-slate-400">±{thresholds.spd}</p>
</div>
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400"></p>
<p className="text-xs font-mono text-slate-800">{thresholds.whale}</p>
<p className="text-[9px] text-slate-400"></p>
</div>
</div>
{blockReason && (
<p className="text-[10px] text-red-600 mt-1.5 bg-red-50 rounded px-2 py-1">
: <span className="font-mono">{blockReason}</span>
</p>
)}
</div>
);
}
// ─── BTC Gate 状态卡片 ───────────────────────────────────────────
function BTCGateCard({ factors }: { factors: LatestIndicator["factors"] }) {
if (!factors) return null;
return (
<div className="rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 mt-2">
<div className="flex items-center justify-between mb-1.5">
<p className="text-[10px] font-semibold text-amber-800"> BTC Gate-Control</p>
<span className={`text-[10px] font-bold px-2 py-0.5 rounded ${factors.gate_passed ? "bg-emerald-100 text-emerald-700" : "bg-red-100 text-red-600"}`}>
{factors.gate_passed ? "✅ Gate通过" : "❌ 否决"}
</span>
</div>
<div className="grid grid-cols-4 gap-1.5">
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400"></p>
<p className="text-xs font-mono text-slate-800">{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%</p>
<p className="text-[9px] text-slate-400"> 0.2%</p>
</div>
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400">OBI</p>
<p className={`text-xs font-mono ${(factors.obi_raw ?? 0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.obi_raw ?? 0) * 100).toFixed(2)}%
</p>
<p className="text-[9px] text-slate-400"></p>
</div>
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400"></p>
<p className={`text-xs font-mono ${(factors.spot_perp_div ?? 0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps
</p>
<p className="text-[9px] text-slate-400">spot-perp</p>
</div>
<div className="bg-white rounded px-2 py-1">
<p className="text-[10px] text-slate-400">CVD</p>
<p className={`text-xs font-mono ${(factors.whale_cvd_ratio ?? 0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{((factors.whale_cvd_ratio ?? 0) * 100).toFixed(2)}%
</p>
<p className="text-[9px] text-slate-400">&gt;$100k</p>
</div>
</div>
{factors.block_reason && (
<p className="text-[10px] text-red-600 mt-1.5 bg-red-50 rounded px-2 py-1">
: <span className="font-mono">{factors.block_reason}</span>
</p>
)}
{factors.alt_score_ref !== undefined && (
<p className="text-[10px] text-slate-400 mt-1">ALT逻辑: {factors.alt_score_ref} </p>
)}
</div>
);
}
// ─── 实时指标卡片 ────────────────────────────────────────────────
function IndicatorCards({ symbol }: { symbol: Symbol }) {
const [data, setData] = useState<LatestIndicator | null>(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 <div className="text-center text-slate-400 text-sm py-4">...</div>;
const isBTC = symbol === "BTC";
const priceVsVwap = data.price > data.vwap_30m ? "上方" : "下方";
return (
<div className="space-y-3">
{/* CVD三轨 */}
<div className="grid grid-cols-3 gap-1.5">
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">CVD_fast (30m)</p>
<p className={`font-mono font-bold text-sm ${data.cvd_fast >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{fmt(data.cvd_fast)}
</p>
<p className="text-[10px] text-slate-400">
: <span className={data.cvd_fast_slope >= 0 ? "text-emerald-600" : "text-red-500"}>
{data.cvd_fast_slope >= 0 ? "↑" : "↓"}{fmt(Math.abs(data.cvd_fast_slope))}
</span>
</p>
</div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">CVD_mid (4h)</p>
<p className={`font-mono font-bold text-sm ${data.cvd_mid >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{fmt(data.cvd_mid)}
</p>
<p className="text-[10px] text-slate-400">{data.cvd_mid > 0 ? "多" : "空"}</p>
</div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">CVD共振</p>
<p className={`font-mono font-bold text-sm ${data.cvd_fast >= 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 ? "✅ 空头共振" : "⚠️ 分歧"}
</p>
<p className="text-[10px] text-slate-400">V5.3</p>
</div>
</div>
{/* ATR + VWAP */}
<div className="grid grid-cols-4 gap-1.5">
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">ATR</p>
<p className="font-mono font-semibold text-sm text-slate-800">${fmt(data.atr_5m, 2)}</p>
<p className="text-[10px]">
<span className={data.atr_percentile > 60 ? "text-amber-600 font-semibold" : "text-slate-400"}>
{data.atr_percentile.toFixed(0)}%{data.atr_percentile > 60 ? "🔥" : ""}
</span>
</p>
</div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">VWAP</p>
<p className="font-mono font-semibold text-sm text-slate-800">${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}</p>
<p className="text-[10px]">
<span className={data.price > data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap}</span>
</p>
</div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">P95</p>
<p className="font-mono font-semibold text-sm text-slate-800">{data.p95_qty.toFixed(4)}</p>
<p className="text-[10px] text-slate-400"></p>
</div>
<div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-[10px] text-slate-400">P99</p>
<p className="font-mono font-semibold text-sm text-amber-600">{data.p99_qty.toFixed(4)}</p>
<p className="text-[10px] text-slate-400"></p>
</div>
</div>
{/* 信号状态 */}
<div className={`rounded-xl border px-3 py-2.5 ${
data.signal === "LONG" ? "border-emerald-300 bg-emerald-50" :
data.signal === "SHORT" ? "border-red-300 bg-red-50" :
"border-slate-200 bg-slate-50"
}`}>
<div className="flex items-center justify-between">
<div>
<p className="text-[10px] text-slate-500">
{isBTC ? "BTC Gate-Control" : "ALT 四层评分"}
{" · "}{isBTC ? "v53_btc" : "v53_alt"}
</p>
<p className={`font-bold text-base ${
data.signal === "LONG" ? "text-emerald-700" :
data.signal === "SHORT" ? "text-red-600" :
"text-slate-400"
}`}>
{data.signal === "LONG" ? "🟢 做多" : data.signal === "SHORT" ? "🔴 做空" : "⚪ 无信号"}
</p>
</div>
<div className="text-right">
<p className="font-mono font-bold text-lg text-slate-800">{data.score}/100</p>
<p className="text-[10px] text-slate-500">{data.tier === "heavy" ? "加仓" : data.tier === "standard" ? "标准" : "不开仓"}</p>
</div>
</div>
{/* ALT四层 */}
{!isBTC && (
<div className="mt-2 space-y-1">
<LayerScore label="方向" score={data.factors?.direction?.score ?? 0} max={55} colorClass="bg-blue-600" />
<LayerScore label="拥挤" score={data.factors?.crowding?.score ?? 0} max={25} colorClass="bg-violet-600" />
<LayerScore label="环境" score={data.factors?.environment?.score ?? 0} max={15} colorClass="bg-emerald-600" />
<LayerScore label="辅助" score={data.factors?.auxiliary?.score ?? 0} max={5} colorClass="bg-slate-500" />
</div>
)}
</div>
{/* ALT Gate 卡片 */}
{!isBTC && data.factors && <ALTGateCard symbol={symbol} factors={data.factors} />}
{/* BTC Gate 卡片 */}
{isBTC && data.factors && <BTCGateCard factors={data.factors} />}
</div>
);
}
// ─── 信号历史 ────────────────────────────────────────────────────
interface SignalRecord {
ts: number;
score: number;
signal: string;
}
function SignalHistory({ symbol }: { symbol: Symbol }) {
const [data, setData] = useState<SignalRecord[]>([]);
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 (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100">
<h3 className="font-semibold text-slate-800 text-xs"> ({strategy})</h3>
</div>
<div className="divide-y divide-slate-100 max-h-48 overflow-y-auto">
{data.map((s, i) => (
<div key={i} className="px-3 py-1.5 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className={`text-xs font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}>
{s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"}
</span>
<span className="text-[10px] text-slate-400">{bjtFull(s.ts)}</span>
</div>
<div className="flex items-center gap-1.5">
<span className="font-mono text-xs text-slate-700">{s.score}</span>
<span className={`text-[10px] px-1 py-0.5 rounded ${
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 ? "标准" : "不开仓"}
</span>
</div>
</div>
))}
</div>
</div>
);
}
// ─── CVD图表 ────────────────────────────────────────────────────
function CVDChart({ symbol, minutes }: { symbol: Symbol; minutes: number }) {
const [data, setData] = useState<IndicatorRow[]>([]);
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 <div className="flex items-center justify-center h-48 text-slate-400 text-sm">...</div>;
if (data.length === 0) return <div className="flex items-center justify-center h-48 text-slate-400 text-sm"> V5.3 signal-engine </div>;
return (
<ResponsiveContainer width="100%" height={220}>
<ComposedChart data={chartData} margin={{ top: 4, right: 60, bottom: 0, left: 8 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" />
<XAxis dataKey="time" tick={{ fill: "#94a3b8", fontSize: 10 }} tickLine={false} interval="preserveStartEnd" />
<YAxis yAxisId="cvd" tick={{ fill: "#94a3b8", fontSize: 10 }} tickLine={false} axisLine={false} width={55} />
<YAxis yAxisId="price" orientation="right" tick={{ fill: "#f59e0b", fontSize: 10 }} tickLine={false} axisLine={false} width={65}
domain={[Math.floor(pMin - pPad), Math.ceil(pMax + pPad)]}
tickFormatter={(v: number) => v >= 1000 ? `$${(v / 1000).toFixed(1)}k` : `$${v.toFixed(0)}`}
/>
<Tooltip
// eslint-disable-next-line @typescript-eslint/no-explicit-any
formatter={(v: any, name: any) => {
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 }}
/>
<Legend wrapperStyle={{ fontSize: 11 }} />
<ReferenceLine yAxisId="cvd" y={0} stroke="#94a3b8" strokeDasharray="4 2" />
<Area yAxisId="cvd" type="monotone" dataKey="fast" name="fast" stroke="#2563eb" fill="#eff6ff" strokeWidth={1.5} dot={false} connectNulls />
<Line yAxisId="cvd" type="monotone" dataKey="mid" name="mid" stroke="#7c3aed" strokeWidth={1.5} dot={false} connectNulls strokeDasharray="6 3" />
<Line yAxisId="price" type="monotone" dataKey="price" name="price" stroke="#f59e0b" strokeWidth={1.5} dot={false} connectNulls strokeDasharray="4 2" />
</ComposedChart>
</ResponsiveContainer>
);
}
// ─── 主页面 ──────────────────────────────────────────────────────
export default function SignalsV53Page() {
const { isLoggedIn, loading } = useAuth();
const [symbol, setSymbol] = useState<Symbol>("ETH");
const [minutes, setMinutes] = useState(240);
if (loading) return <div className="flex items-center justify-center h-64 text-slate-400">...</div>;
if (!isLoggedIn) return (
<div className="flex flex-col items-center justify-center h-64 gap-4">
<div className="text-5xl">🔒</div>
<p className="text-slate-600 font-medium"></p>
<div className="flex gap-2">
<Link href="/login" className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm"></Link>
<Link href="/register" className="border border-slate-300 text-slate-600 px-4 py-2 rounded-lg text-sm"></Link>
</div>
</div>
);
return (
<div className="space-y-3">
<div className="flex items-center justify-between flex-wrap gap-2">
<div>
<h1 className="text-lg font-bold text-slate-900"> V5.3</h1>
<p className="text-slate-500 text-[10px]">
55/25/15/5 · ALT双轨 + BTC gate-control ·
{symbol === "BTC" ? " 🔵 BTC轨gate-control" : " 🟣 ALT轨ETH/XRP/SOL"}
</p>
</div>
<div className="flex gap-1">
{(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => (
<button key={s} onClick={() => setSymbol(s)}
className={`px-3 py-1 rounded-lg border text-xs font-medium transition-colors ${symbol === s ? (s === "BTC" ? "bg-amber-500 text-white border-amber-500" : "bg-blue-600 text-white border-blue-600") : "border-slate-200 text-slate-600 hover:border-blue-400"}`}>
{s}{s === "BTC" ? " 🔵" : ""}
</button>
))}
</div>
</div>
<IndicatorCards symbol={symbol} />
<SignalHistory symbol={symbol} />
<div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100 flex items-center justify-between flex-wrap gap-1">
<div>
<h3 className="font-semibold text-slate-800 text-xs">CVD三轨 + </h3>
<p className="text-[10px] text-slate-400">=fast(30m) · =mid(4h) · =</p>
</div>
<div className="flex gap-1">
{WINDOWS.map(w => (
<button key={w.value} onClick={() => setMinutes(w.value)}
className={`px-2 py-1 rounded border text-xs transition-colors ${minutes === w.value ? "bg-slate-800 text-white border-slate-800" : "border-slate-200 text-slate-500 hover:border-slate-400"}`}>
{w.label}
</button>
))}
</div>
</div>
<div className="px-3 py-2">
<CVDChart symbol={symbol} minutes={minutes} />
</div>
</div>
<div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden">
<div className="px-3 py-2 border-b border-slate-100">
<h3 className="font-semibold text-slate-800 text-xs">📖 V5.3 </h3>
</div>
<div className="px-3 py-2 space-y-2 text-[11px] text-slate-600">
<div className="p-2 bg-purple-50 rounded-lg border border-purple-100">
<span className="font-bold text-purple-800">🟣 ALT轨ETH/XRP/SOL 线</span>
<div className="mt-1 space-y-1">
<p><span className="font-semibold">1 55</span> CVD共振30分fast+mid同向+ P99大单对齐20分 + 5CVD双重计分问题</p>
<p><span className="font-semibold">2 25</span> LSR反向拥挤15分=+ 10</p>
<p><span className="font-semibold">3 15</span> OI变化率vs撤离</p>
<p><span className="font-semibold">4 5</span> Coinbase Premium</p>
</div>
</div>
<div className="p-2 bg-amber-50 rounded-lg border border-amber-100">
<span className="font-bold text-amber-800">🔵 BTC轨 Gate-Control逻辑</span>
<div className="mt-1 space-y-1">
<p><span className="font-semibold"></span>ATR/Price 0.2%</p>
<p><span className="font-semibold">OBI否决</span>簿100ms</p>
<p><span className="font-semibold"></span>spot与perp价差超阈值时否决1s</p>
<p><span className="font-semibold">CVD</span>&gt;$100k成交额净CVD15</p>
</div>
</div>
<div className="pt-1 border-t border-slate-100">
<span className="text-blue-600 font-medium"></span>&lt;75 · 75-84 · 85 · 10
</div>
</div>
</div>
</div>
);
}