feat: paper pages show per-strategy signals with layer scores

- V5.1 paper reads /api/signals/latest?strategy=v51_baseline
- V5.2 paper reads /api/signals/latest?strategy=v52_8signals
- Each coin shows layer score badges (方向/拥挤/环境/确认/辅助)
- V5.2 additionally shows FR and 清算 badges
This commit is contained in:
root 2026-03-02 02:59:39 +00:00
parent 7ebdb98643
commit 5b704a0a0e
2 changed files with 76 additions and 52 deletions

View File

@ -166,17 +166,13 @@ function LatestSignals() {
const [signals, setSignals] = useState<Record<string, any>>({}); const [signals, setSignals] = useState<Record<string, any>>({});
useEffect(() => { useEffect(() => {
const f = async () => { const f = async () => {
for (const sym of COINS) { try {
try { const r = await authFetch("/api/signals/latest?strategy=v52_8signals");
const r = await authFetch(`/api/signals/signal-history?symbol=${sym.replace("USDT", "")}&limit=1`); if (r.ok) {
if (r.ok) { const j = await r.json();
const j = await r.json(); setSignals(j);
if (j.data && j.data.length > 0) { }
setSignals((prev) => ({ ...prev, [sym]: j.data[0] })); } catch {}
}
}
} catch {}
}
}; };
f(); f();
const iv = setInterval(f, 15000); const iv = setInterval(f, 15000);
@ -190,25 +186,42 @@ function LatestSignals() {
</div> </div>
<div className="divide-y divide-slate-50"> <div className="divide-y divide-slate-50">
{COINS.map((sym) => { {COINS.map((sym) => {
const s = signals[sym];
const coin = sym.replace("USDT", ""); const coin = sym.replace("USDT", "");
const s = signals[coin];
const ago = s?.ts ? Math.round((Date.now() - s.ts) / 60000) : null; const ago = s?.ts ? Math.round((Date.now() - s.ts) / 60000) : null;
const f = s?.factors;
return ( return (
<div key={sym} className="px-3 py-1.5 flex items-center justify-between"> <div key={sym} className="px-3 py-2">
<div className="flex items-center gap-2"> <div className="flex items-center justify-between">
<span className="font-mono text-xs font-bold text-slate-700 w-8">{coin}</span> <div className="flex items-center gap-2">
{s?.signal ? ( <span className="font-mono text-xs font-bold text-slate-700 w-8">{coin}</span>
<> {s?.signal ? (
<span className={`text-xs font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}> <>
{s.signal === "LONG" ? "🟢" : "🔴"} {s.signal} <span className={`text-xs font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}>
</span> {s.signal === "LONG" ? "🟢" : "🔴"} {s.signal}
<span className="font-mono text-[10px] text-slate-500">{s.score}</span> </span>
</> <span className="font-mono text-xs font-bold text-slate-800">{s.score}</span>
) : ( </>
<span className="text-[10px] text-slate-400"> </span> ) : (
)} <>
<span className="text-[10px] text-slate-400"> </span>
<span className="font-mono text-[10px] text-slate-500">{s?.score ?? 0}</span>
</>
)}
</div>
{ago !== null && <span className="text-[10px] text-slate-400">{ago < 60 ? `${ago}m前` : `${Math.round(ago / 60)}h前`}</span>}
</div> </div>
{ago !== null && <span className="text-[10px] text-slate-400">{ago < 60 ? `${ago}m前` : `${Math.round(ago / 60)}h前`}</span>} {f && (
<div className="flex gap-1 mt-1 flex-wrap">
<span className="text-[9px] px-1 py-0.5 rounded bg-blue-50 text-blue-700">{f.direction?.score ?? 0}/{f.direction?.max ?? 40}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-violet-50 text-violet-700">{f.crowding?.score ?? 0}/{f.crowding?.max ?? 18}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-cyan-50 text-cyan-700">FR{f.funding_rate?.score ?? 0}/{f.funding_rate?.max ?? 5}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-emerald-50 text-emerald-700">{f.environment?.score ?? 0}/{f.environment?.max ?? 12}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-amber-50 text-amber-700">{f.confirmation?.score ?? 0}/{f.confirmation?.max ?? 15}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-orange-50 text-orange-700">{f.liquidation?.score ?? 0}/{f.liquidation?.max ?? 5}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-slate-100 text-slate-600">{f.auxiliary?.score ?? 0}/{f.auxiliary?.max ?? 5}</span>
</div>
)}
</div> </div>
); );
})} })}

View File

@ -118,17 +118,13 @@ function LatestSignals() {
const [signals, setSignals] = useState<Record<string, any>>({}); const [signals, setSignals] = useState<Record<string, any>>({});
useEffect(() => { useEffect(() => {
const f = async () => { const f = async () => {
for (const sym of COINS) { try {
try { const r = await authFetch("/api/signals/latest?strategy=v51_baseline");
const r = await authFetch(`/api/signals/signal-history?symbol=${sym.replace("USDT","")}&limit=1`); if (r.ok) {
if (r.ok) { const j = await r.json();
const j = await r.json(); setSignals(j);
if (j.data && j.data.length > 0) { }
setSignals(prev => ({ ...prev, [sym]: j.data[0] })); } catch {}
}
}
} catch {}
}
}; };
f(); const iv = setInterval(f, 15000); return () => clearInterval(iv); f(); const iv = setInterval(f, 15000); return () => clearInterval(iv);
}, []); }, []);
@ -140,25 +136,40 @@ function LatestSignals() {
</div> </div>
<div className="divide-y divide-slate-50"> <div className="divide-y divide-slate-50">
{COINS.map(sym => { {COINS.map(sym => {
const s = signals[sym];
const coin = sym.replace("USDT", ""); const coin = sym.replace("USDT", "");
const s = signals[coin];
const ago = s?.ts ? Math.round((Date.now() - s.ts) / 60000) : null; const ago = s?.ts ? Math.round((Date.now() - s.ts) / 60000) : null;
const f = s?.factors;
return ( return (
<div key={sym} className="px-3 py-1.5 flex items-center justify-between"> <div key={sym} className="px-3 py-2">
<div className="flex items-center gap-2"> <div className="flex items-center justify-between">
<span className="font-mono text-xs font-bold text-slate-700 w-8">{coin}</span> <div className="flex items-center gap-2">
{s?.signal ? ( <span className="font-mono text-xs font-bold text-slate-700 w-8">{coin}</span>
<> {s?.signal ? (
<span className={`text-xs font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}> <>
{s.signal === "LONG" ? "🟢" : "🔴"} {s.signal} <span className={`text-xs font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}>
</span> {s.signal === "LONG" ? "🟢" : "🔴"} {s.signal}
<span className="font-mono text-[10px] text-slate-500">{s.score}</span> </span>
</> <span className="font-mono text-xs font-bold text-slate-800">{s.score}</span>
) : ( </>
<span className="text-[10px] text-slate-400"> </span> ) : (
)} <>
<span className="text-[10px] text-slate-400"> </span>
<span className="font-mono text-[10px] text-slate-500">{s?.score ?? 0}</span>
</>
)}
</div>
{ago !== null && <span className="text-[10px] text-slate-400">{ago < 60 ? `${ago}m前` : `${Math.round(ago/60)}h前`}</span>}
</div> </div>
{ago !== null && <span className="text-[10px] text-slate-400">{ago < 60 ? `${ago}m前` : `${Math.round(ago/60)}h前`}</span>} {f && (
<div className="flex gap-1 mt-1 flex-wrap">
<span className="text-[9px] px-1 py-0.5 rounded bg-blue-50 text-blue-700">{f.direction?.score ?? 0}/{f.direction?.max ?? 45}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-violet-50 text-violet-700">{f.crowding?.score ?? 0}/{f.crowding?.max ?? 20}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-emerald-50 text-emerald-700">{f.environment?.score ?? 0}/{f.environment?.max ?? 15}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-amber-50 text-amber-700">{f.confirmation?.score ?? 0}/{f.confirmation?.max ?? 15}</span>
<span className="text-[9px] px-1 py-0.5 rounded bg-slate-100 text-slate-600">{f.auxiliary?.score ?? 0}/{f.auxiliary?.max ?? 5}</span>
</div>
)}
</div> </div>
); );
})} })}