feat: signal history list + always compute scoring even without signal
This commit is contained in:
parent
ca25938adc
commit
424cb993f8
@ -461,6 +461,23 @@ async def get_market_indicators(user: dict = Depends(get_current_user)):
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/api/signals/history")
|
||||
async def get_signal_history(
|
||||
symbol: str = "BTC",
|
||||
limit: int = 50,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""返回最近的信号历史(只返回有信号的记录)"""
|
||||
sym_full = symbol.upper() + "USDT"
|
||||
rows = await async_fetch(
|
||||
"SELECT ts, score, signal FROM signal_indicators "
|
||||
"WHERE symbol = $1 AND signal IS NOT NULL "
|
||||
"ORDER BY ts DESC LIMIT $2",
|
||||
sym_full, limit
|
||||
)
|
||||
return {"symbol": symbol, "count": len(rows), "data": rows}
|
||||
|
||||
|
||||
@app.get("/api/signals/trades")
|
||||
async def get_signal_trades(
|
||||
status: str = "all",
|
||||
|
||||
@ -241,15 +241,18 @@ class SymbolState:
|
||||
|
||||
if self.warmup or price == 0 or atr == 0:
|
||||
return result
|
||||
if now_ms - self.last_signal_ts < COOLDOWN_MS:
|
||||
return result
|
||||
|
||||
# 判断倾向方向(用于评分展示,即使冷却或方向不一致也计算)
|
||||
no_direction = False
|
||||
in_cooldown = (now_ms - self.last_signal_ts < COOLDOWN_MS)
|
||||
|
||||
if cvd_fast > 0 and cvd_mid > 0:
|
||||
direction = "LONG"
|
||||
elif cvd_fast < 0 and cvd_mid < 0:
|
||||
direction = "SHORT"
|
||||
else:
|
||||
return result
|
||||
direction = "LONG" if cvd_fast > 0 else "SHORT"
|
||||
no_direction = True
|
||||
|
||||
# V5.1 五层评分体系(总分100,方向层可因加速额外+5)
|
||||
# 1) 方向层(45分 + 5加速分)
|
||||
@ -345,13 +348,13 @@ class SymbolState:
|
||||
"auxiliary": {"score": aux_score, "coinbase_premium": coinbase_premium},
|
||||
}
|
||||
|
||||
if total_score >= 85:
|
||||
if total_score >= 85 and not no_direction and not in_cooldown:
|
||||
result["signal"] = direction
|
||||
result["tier"] = "heavy"
|
||||
elif total_score >= 75:
|
||||
elif total_score >= 75 and not no_direction and not in_cooldown:
|
||||
result["signal"] = direction
|
||||
result["tier"] = "standard"
|
||||
elif total_score >= 60:
|
||||
elif total_score >= 60 and not no_direction and not in_cooldown:
|
||||
result["signal"] = direction
|
||||
result["tier"] = "light"
|
||||
else:
|
||||
|
||||
@ -162,6 +162,70 @@ function MarketIndicatorsCards({ symbol }: { symbol: Symbol }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 信号历史 ────────────────────────────────────────────────────
|
||||
|
||||
interface SignalRecord {
|
||||
ts: number;
|
||||
score: number;
|
||||
signal: string;
|
||||
}
|
||||
|
||||
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 SignalHistory({ symbol }: { symbol: Symbol }) {
|
||||
const [data, setData] = useState<SignalRecord[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await authFetch(`/api/signals/history?symbol=${symbol}&limit=20`);
|
||||
if (!res.ok) return;
|
||||
const json = await res.json();
|
||||
setData(json.data || []);
|
||||
} catch {}
|
||||
};
|
||||
fetchData();
|
||||
const iv = setInterval(fetchData, 15000);
|
||||
return () => clearInterval(iv);
|
||||
}, [symbol]);
|
||||
|
||||
if (data.length === 0) return null;
|
||||
|
||||
return (
|
||||
<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">
|
||||
<h3 className="font-semibold text-slate-800 text-sm">最近信号</h3>
|
||||
<p className="text-xs text-slate-400 mt-0.5">最近20条有效信号记录(北京时间)</p>
|
||||
</div>
|
||||
<div className="divide-y divide-slate-100">
|
||||
{data.map((s, i) => (
|
||||
<div key={i} className="px-4 py-2 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={`text-sm font-bold ${s.signal === "LONG" ? "text-emerald-600" : "text-red-500"}`}>
|
||||
{s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"}
|
||||
</span>
|
||||
<span className="text-xs text-slate-400">{bjtFull(s.ts)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono text-sm text-slate-700">{s.score}/100</span>
|
||||
<span className={`text-xs px-1.5 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>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 实时指标卡片 ────────────────────────────────────────────────
|
||||
|
||||
function IndicatorCards({ symbol }: { symbol: Symbol }) {
|
||||
@ -410,6 +474,9 @@ export default function SignalsPage() {
|
||||
<MarketIndicatorsCards symbol={symbol} />
|
||||
</div>
|
||||
|
||||
{/* 信号历史 */}
|
||||
<SignalHistory symbol={symbol} />
|
||||
|
||||
{/* CVD三轨图 */}
|
||||
<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">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user