ui: compact signal engine layout - smaller panels, tighter spacing, V5.1 title

This commit is contained in:
root 2026-02-28 10:42:51 +00:00
parent fb37dfb288
commit 9382d35496

View File

@ -84,14 +84,12 @@ function pct(v: number, digits = 1): string {
function LayerScore({ label, score, max, colorClass }: { label: string; score: number; max: number; colorClass: string }) { 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)); const ratio = Math.max(0, Math.min((score / max) * 100, 100));
return ( return (
<div className="space-y-1"> <div className="flex items-center gap-2">
<div className="flex items-center justify-between text-xs text-slate-600"> <span className="text-[10px] text-slate-500 w-6 shrink-0">{label}</span>
<span>{label}</span> <div className="flex-1 h-1.5 rounded-full bg-slate-100 overflow-hidden">
<span className="font-mono">{score}/{max}</span>
</div>
<div className="h-1.5 rounded-full bg-slate-100 overflow-hidden">
<div className={`h-full ${colorClass}`} style={{ width: `${ratio}%` }} /> <div className={`h-full ${colorClass}`} style={{ width: `${ratio}%` }} />
</div> </div>
<span className="text-[10px] font-mono text-slate-600 w-8 text-right">{score}/{max}</span>
</div> </div>
); );
} }
@ -137,26 +135,22 @@ function MarketIndicatorsCards({ symbol }: { symbol: Symbol }) {
const premium = Number(premVal?.premium_pct ?? 0); const premium = Number(premVal?.premium_pct ?? 0);
return ( return (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-1.5">
<div className="bg-white rounded-xl border border-slate-200 p-3"> <div className="bg-slate-50 rounded-lg px-2 py-1.5">
<p className="text-xs text-slate-400"> (L/S)</p> <p className="text-[10px] text-slate-400"></p>
<p className="text-sm text-slate-800 mt-1">Long: {longPct.toFixed(1)}%</p> <p className="text-xs font-mono text-slate-800">L:{longPct.toFixed(1)}% S:{shortPct.toFixed(1)}%</p>
<p className="text-sm text-slate-600">Short: {shortPct.toFixed(1)}%</p>
</div> </div>
<div className="bg-white rounded-xl border border-slate-200 p-3"> <div className="bg-slate-50 rounded-lg px-2 py-1.5">
<p className="text-xs text-slate-400"></p> <p className="text-[10px] text-slate-400"></p>
<p className="text-sm text-slate-800 mt-1">: {topLong.toFixed(1)}%</p> <p className="text-xs font-mono text-slate-800">{topLong.toFixed(1)}% {topLong >= 55 ? "📈" : topLong <= 45 ? "📉" : ""}</p>
<p className="text-sm text-slate-600">: {topLong >= 55 ? "多头占优" : topLong <= 45 ? "空头占优" : "中性"}</p>
</div> </div>
<div className="bg-white rounded-xl border border-slate-200 p-3"> <div className="bg-slate-50 rounded-lg px-2 py-1.5">
<p className="text-xs text-slate-400"> (OI)</p> <p className="text-[10px] text-slate-400">OI</p>
<p className="text-sm text-slate-800 mt-1">{oiDisplay}</p> <p className="text-xs font-mono text-slate-800">{oiDisplay}</p>
<p className="text-sm text-slate-600">{Number(oiVal?.sumOpenInterest ?? 0).toFixed(0)} BTC</p>
</div> </div>
<div className="bg-white rounded-xl border border-slate-200 p-3"> <div className="bg-slate-50 rounded-lg px-2 py-1.5">
<p className="text-xs text-slate-400">Coinbase Premium</p> <p className="text-[10px] text-slate-400">CB Premium</p>
<p className={`text-sm mt-1 font-mono ${premium >= 0 ? "text-emerald-600" : "text-red-500"}`}>{premium >= 0 ? "+" : ""}{premium.toFixed(4)}%</p> <p className={`text-xs font-mono ${premium >= 0 ? "text-emerald-600" : "text-red-500"}`}>{premium >= 0 ? "+" : ""}{premium.toFixed(4)}%</p>
<p className="text-sm text-slate-600">: {premium > 0.005 ? "偏多" : premium < -0.005 ? "偏空" : "中性"}</p>
</div> </div>
</div> </div>
); );
@ -196,22 +190,21 @@ function SignalHistory({ symbol }: { symbol: Symbol }) {
return ( return (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden"> <div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden">
<div className="px-4 py-3 border-b border-slate-100"> <div className="px-3 py-2 border-b border-slate-100">
<h3 className="font-semibold text-slate-800 text-sm"></h3> <h3 className="font-semibold text-slate-800 text-xs"></h3>
<p className="text-xs text-slate-400 mt-0.5">20</p>
</div> </div>
<div className="divide-y divide-slate-100"> <div className="divide-y divide-slate-100 max-h-48 overflow-y-auto">
{data.map((s, i) => ( {data.map((s, i) => (
<div key={i} className="px-4 py-2 flex items-center justify-between"> <div key={i} className="px-3 py-1.5 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
<span className={`text-sm font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}> <span className={`text-xs font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}>
{s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"} {s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"}
</span> </span>
<span className="text-xs text-slate-400">{bjtFull(s.ts)}</span> <span className="text-[10px] text-slate-400">{bjtFull(s.ts)}</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-1.5">
<span className="font-mono text-sm text-slate-700">{s.score}/100</span> <span className="font-mono text-xs text-slate-700">{s.score}</span>
<span className={`text-xs px-1.5 py-0.5 rounded ${ <span className={`text-[10px] px-1 py-0.5 rounded ${
s.score >= 85 ? "bg-red-100 text-red-700" : s.score >= 85 ? "bg-red-100 text-red-700" :
s.score >= 75 ? "bg-blue-100 text-blue-700" : s.score >= 75 ? "bg-blue-100 text-blue-700" :
"bg-slate-100 text-slate-600" "bg-slate-100 text-slate-600"
@ -258,76 +251,75 @@ function IndicatorCards({ symbol }: { symbol: Symbol }) {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
{/* CVD三轨 */} {/* CVD三轨 - 紧凑一行 */}
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-1.5">
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400">CVD_fast (30m)</p> <p className="text-[10px] text-slate-400">CVD_fast (30m)</p>
<p className={`font-mono font-bold text-lg ${data.cvd_fast >= 0 ? "text-emerald-600" : "text-red-500"}`}> <p className={`font-mono font-bold text-sm ${data.cvd_fast >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{fmt(data.cvd_fast)} {fmt(data.cvd_fast)}
</p> </p>
<p className="text-xs text-slate-400 mt-0.5"> <p className="text-[10px] text-slate-400">
: <span className={data.cvd_fast_slope >= 0 ? "text-emerald-600" : "text-red-500"}> : <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))} {data.cvd_fast_slope >= 0 ? "↑" : "↓"}{fmt(Math.abs(data.cvd_fast_slope))}
</span> </span>
</p> </p>
</div> </div>
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400">CVD_mid (4h)</p> <p className="text-[10px] text-slate-400">CVD_mid (4h)</p>
<p className={`font-mono font-bold text-lg ${data.cvd_mid >= 0 ? "text-emerald-600" : "text-red-500"}`}> <p className={`font-mono font-bold text-sm ${data.cvd_mid >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{fmt(data.cvd_mid)} {fmt(data.cvd_mid)}
</p> </p>
<p className="text-xs text-slate-400 mt-0.5">: {cvdMidDir}</p> <p className="text-[10px] text-slate-400">{cvdMidDir}</p>
</div> </div>
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400">CVD_day ()</p> <p className="text-[10px] text-slate-400">CVD_day</p>
<p className={`font-mono font-bold text-lg ${data.cvd_day >= 0 ? "text-emerald-600" : "text-red-500"}`}> <p className={`font-mono font-bold text-sm ${data.cvd_day >= 0 ? "text-emerald-600" : "text-red-500"}`}>
{fmt(data.cvd_day)} {fmt(data.cvd_day)}
</p> </p>
<p className="text-xs text-slate-400 mt-0.5">线</p> <p className="text-[10px] text-slate-400">线</p>
</div> </div>
</div> </div>
{/* ATR + VWAP + 大单 */} {/* ATR + VWAP + 大单 - 4列紧凑 */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-1.5">
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400">ATR (5m×14)</p> <p className="text-[10px] text-slate-400">ATR</p>
<p className="font-mono font-semibold text-slate-800">${fmt(data.atr_5m, 2)}</p> <p className="font-mono font-semibold text-sm text-slate-800">${fmt(data.atr_5m, 2)}</p>
<p className="text-xs text-slate-400 mt-0.5"> <p className="text-[10px]">
: <span className={data.atr_percentile > 60 ? "text-amber-600 font-semibold" : "text-slate-500"}> <span className={data.atr_percentile > 60 ? "text-amber-600 font-semibold" : "text-slate-400"}>
{data.atr_percentile.toFixed(0)}% {data.atr_percentile.toFixed(0)}%{data.atr_percentile > 60 ? "🔥" : ""}
</span> </span>
{data.atr_percentile > 60 ? " 🔥扩张" : data.atr_percentile < 40 ? " 压缩" : ""}
</p> </p>
</div> </div>
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400">VWAP (30m)</p> <p className="text-[10px] text-slate-400">VWAP</p>
<p className="font-mono font-semibold text-slate-800">${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}</p> <p className="font-mono font-semibold text-sm text-slate-800">${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}</p>
<p className="text-xs text-slate-400 mt-0.5"> <p className="text-[10px]">
VWAP<span className={data.price > data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap}</span> <span className={data.price > data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap}</span>
</p> </p>
</div> </div>
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400"> P95</p> <p className="text-[10px] text-slate-400">P95</p>
<p className="font-mono font-semibold text-slate-800">{data.p95_qty.toFixed(4)}</p> <p className="font-mono font-semibold text-sm text-slate-800">{data.p95_qty.toFixed(4)}</p>
<p className="text-xs text-slate-400 mt-0.5">24h动态分位</p> <p className="text-[10px] text-slate-400"></p>
</div> </div>
<div className="bg-white rounded-lg border border-slate-200 p-3"> <div className="bg-white rounded-lg border border-slate-200 px-2.5 py-2">
<p className="text-xs text-slate-400"> P99</p> <p className="text-[10px] text-slate-400">P99</p>
<p className="font-mono font-semibold text-amber-600">{data.p99_qty.toFixed(4)}</p> <p className="font-mono font-semibold text-sm text-amber-600">{data.p99_qty.toFixed(4)}</p>
<p className="text-xs text-slate-400 mt-0.5"></p> <p className="text-[10px] text-slate-400"></p>
</div> </div>
</div> </div>
{/* 信号状态V5.1 */} {/* 信号状态V5.1- 紧凑版 */}
<div className={`rounded-xl border p-3 ${ <div className={`rounded-xl border px-3 py-2.5 ${
data.signal === "LONG" ? "border-emerald-300 bg-emerald-50" : data.signal === "LONG" ? "border-emerald-300 bg-emerald-50" :
data.signal === "SHORT" ? "border-red-300 bg-red-50" : data.signal === "SHORT" ? "border-red-300 bg-red-50" :
"border-slate-200 bg-slate-50" "border-slate-200 bg-slate-50"
}`}> }`}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<p className="text-xs text-slate-500"></p> <p className="text-[10px] text-slate-500"></p>
<p className={`font-bold text-lg ${ <p className={`font-bold text-base ${
data.signal === "LONG" ? "text-emerald-700" : data.signal === "LONG" ? "text-emerald-700" :
data.signal === "SHORT" ? "text-red-600" : data.signal === "SHORT" ? "text-red-600" :
"text-slate-400" "text-slate-400"
@ -336,25 +328,17 @@ function IndicatorCards({ symbol }: { symbol: Symbol }) {
</p> </p>
</div> </div>
<div className="text-right"> <div className="text-right">
<p className="text-xs text-slate-500"></p> <p className="font-mono font-bold text-lg text-slate-800">{data.score}/100</p>
<p className="font-mono font-bold text-slate-800">{data.score}/100</p> <p className="text-[10px] text-slate-500">{data.tier === "heavy" ? "加仓" : data.tier === "standard" ? "标准" : data.tier === "light" ? "轻仓" : "不开仓"}</p>
<p className="text-xs text-slate-500 mt-0.5">: {data.tier === "heavy" ? "加仓" : data.tier === "standard" ? "标准" : data.tier === "light" ? "轻仓" : "不开仓"}</p>
</div> </div>
</div> </div>
<div className="mt-3 space-y-2"> <div className="mt-2 space-y-1">
<LayerScore label="方向" score={data.factors?.direction?.score ?? Math.min(Math.round(data.score * 0.45), 45)} max={45} colorClass="bg-blue-600" /> <LayerScore label="方向" score={data.factors?.direction?.score ?? Math.min(Math.round(data.score * 0.45), 45)} max={45} colorClass="bg-blue-600" />
<LayerScore label="拥挤" score={data.factors?.crowding?.score ?? Math.min(Math.round(data.score * 0.20), 20)} max={20} colorClass="bg-violet-600" /> <LayerScore label="拥挤" score={data.factors?.crowding?.score ?? Math.min(Math.round(data.score * 0.20), 20)} max={20} colorClass="bg-violet-600" />
<LayerScore label="环境" score={data.factors?.environment?.score ?? Math.min(Math.round(data.score * 0.15), 15)} max={15} colorClass="bg-emerald-600" /> <LayerScore label="环境" score={data.factors?.environment?.score ?? Math.min(Math.round(data.score * 0.15), 15)} max={15} colorClass="bg-emerald-600" />
<LayerScore label="确认" score={data.factors?.confirmation?.score ?? Math.min(Math.round(data.score * 0.15), 15)} max={15} colorClass="bg-amber-500" /> <LayerScore label="确认" score={data.factors?.confirmation?.score ?? Math.min(Math.round(data.score * 0.15), 15)} max={15} colorClass="bg-amber-500" />
<LayerScore label="辅助" score={data.factors?.auxiliary?.score ?? Math.min(Math.round(data.score * 0.05), 5)} max={5} colorClass="bg-slate-500" /> <LayerScore label="辅助" score={data.factors?.auxiliary?.score ?? Math.min(Math.round(data.score * 0.05), 5)} max={5} colorClass="bg-slate-500" />
</div> </div>
{data.signal && (
<div className="mt-2 grid grid-cols-3 gap-2 text-xs text-slate-600">
<span>{core1} CVD_fast方向</span>
<span>{core2} CVD_mid方向</span>
<span>{core3} VWAP位置</span>
</div>
)}
</div> </div>
</div> </div>
); );
@ -448,17 +432,17 @@ export default function SignalsPage() {
); );
return ( return (
<div className="space-y-5"> <div className="space-y-3">
{/* 标题 */} {/* 标题 */}
<div className="flex items-center justify-between flex-wrap gap-2"> <div className="flex items-center justify-between flex-wrap gap-2">
<div> <div>
<h1 className="text-xl font-bold text-slate-900">V5.1 </h1> <h1 className="text-lg font-bold text-slate-900"> V5.1</h1>
<p className="text-slate-500 text-xs mt-0.5">100 + + </p> <p className="text-slate-500 text-[10px]">100 · · </p>
</div> </div>
<div className="flex gap-1.5"> <div className="flex gap-1">
{(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => ( {(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => (
<button key={s} onClick={() => setSymbol(s)} <button key={s} onClick={() => setSymbol(s)}
className={`px-4 py-1.5 rounded-lg border text-sm font-medium transition-colors ${symbol === s ? "bg-blue-600 text-white border-blue-600" : "border-slate-200 text-slate-600 hover:border-blue-400"}`}> className={`px-3 py-1 rounded-lg border text-xs font-medium transition-colors ${symbol === s ? "bg-blue-600 text-white border-blue-600" : "border-slate-200 text-slate-600 hover:border-blue-400"}`}>
{s} {s}
</button> </button>
))} ))}
@ -469,8 +453,8 @@ export default function SignalsPage() {
<IndicatorCards symbol={symbol} /> <IndicatorCards symbol={symbol} />
{/* Market Indicators */} {/* Market Indicators */}
<div className="rounded-xl border border-slate-200 bg-white p-3"> <div className="rounded-xl border border-slate-200 bg-white px-3 py-2">
<h3 className="font-semibold text-slate-800 text-sm mb-2">Market Indicators</h3> <h3 className="font-semibold text-slate-800 text-xs mb-1.5">Market Indicators</h3>
<MarketIndicatorsCards symbol={symbol} /> <MarketIndicatorsCards symbol={symbol} />
</div> </div>
@ -479,10 +463,10 @@ export default function SignalsPage() {
{/* CVD三轨图 */} {/* CVD三轨图 */}
<div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden"> <div className="rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden">
<div className="px-4 py-3 border-b border-slate-100 flex items-center justify-between flex-wrap gap-2"> <div className="px-3 py-2 border-b border-slate-100 flex items-center justify-between flex-wrap gap-1">
<div> <div>
<h3 className="font-semibold text-slate-800 text-sm">CVD三轨 + </h3> <h3 className="font-semibold text-slate-800 text-xs">CVD三轨 + </h3>
<p className="text-xs text-slate-400 mt-0.5">=CVD_fast(30m) · 线=CVD_mid(4h) · 线=</p> <p className="text-[10px] text-slate-400">=fast(30m) · =mid(4h) · =</p>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1">
{WINDOWS.map(w => ( {WINDOWS.map(w => (
@ -493,15 +477,15 @@ export default function SignalsPage() {
))} ))}
</div> </div>
</div> </div>
<div className="p-4"> <div className="px-3 py-2">
<CVDChart symbol={symbol} minutes={minutes} /> <CVDChart symbol={symbol} minutes={minutes} />
</div> </div>
</div> </div>
{/* 说明 */} {/* 说明 - 紧凑 */}
<div className="rounded-xl border border-blue-100 bg-blue-50 px-4 py-3 text-xs text-slate-700 space-y-1"> <div className="rounded-lg border border-blue-100 bg-blue-50 px-3 py-2 text-[10px] text-slate-600">
<p><span className="text-blue-600 font-medium">V5.1</span>45 + 20 + 15 + 15 + 5+5</p> <span className="text-blue-600 font-medium"></span>45 + 20 + 15 + 15 + 5+5·
<p><span className="text-blue-600 font-medium"></span>&lt;6060-7475-848510</p> <span className="text-blue-600 font-medium"></span>&lt;60 · 60-74 · 75-84 · 85
</div> </div>
</div> </div>
); );