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
|
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")
|
@app.get("/api/signals/trades")
|
||||||
async def get_signal_trades(
|
async def get_signal_trades(
|
||||||
status: str = "all",
|
status: str = "all",
|
||||||
|
|||||||
@ -241,15 +241,18 @@ class SymbolState:
|
|||||||
|
|
||||||
if self.warmup or price == 0 or atr == 0:
|
if self.warmup or price == 0 or atr == 0:
|
||||||
return result
|
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:
|
if cvd_fast > 0 and cvd_mid > 0:
|
||||||
direction = "LONG"
|
direction = "LONG"
|
||||||
elif cvd_fast < 0 and cvd_mid < 0:
|
elif cvd_fast < 0 and cvd_mid < 0:
|
||||||
direction = "SHORT"
|
direction = "SHORT"
|
||||||
else:
|
else:
|
||||||
return result
|
direction = "LONG" if cvd_fast > 0 else "SHORT"
|
||||||
|
no_direction = True
|
||||||
|
|
||||||
# V5.1 五层评分体系(总分100,方向层可因加速额外+5)
|
# V5.1 五层评分体系(总分100,方向层可因加速额外+5)
|
||||||
# 1) 方向层(45分 + 5加速分)
|
# 1) 方向层(45分 + 5加速分)
|
||||||
@ -345,13 +348,13 @@ class SymbolState:
|
|||||||
"auxiliary": {"score": aux_score, "coinbase_premium": coinbase_premium},
|
"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["signal"] = direction
|
||||||
result["tier"] = "heavy"
|
result["tier"] = "heavy"
|
||||||
elif total_score >= 75:
|
elif total_score >= 75 and not no_direction and not in_cooldown:
|
||||||
result["signal"] = direction
|
result["signal"] = direction
|
||||||
result["tier"] = "standard"
|
result["tier"] = "standard"
|
||||||
elif total_score >= 60:
|
elif total_score >= 60 and not no_direction and not in_cooldown:
|
||||||
result["signal"] = direction
|
result["signal"] = direction
|
||||||
result["tier"] = "light"
|
result["tier"] = "light"
|
||||||
else:
|
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 }) {
|
function IndicatorCards({ symbol }: { symbol: Symbol }) {
|
||||||
@ -410,6 +474,9 @@ export default function SignalsPage() {
|
|||||||
<MarketIndicatorsCards symbol={symbol} />
|
<MarketIndicatorsCards symbol={symbol} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 信号历史 */}
|
||||||
|
<SignalHistory symbol={symbol} />
|
||||||
|
|
||||||
{/* 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-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