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:
parent
7ebdb98643
commit
5b704a0a0e
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user