diff --git a/backend/paper_config.json b/backend/paper_config.json index 9d0d5a0..a9cb224 100644 --- a/backend/paper_config.json +++ b/backend/paper_config.json @@ -1,6 +1,6 @@ { "enabled": true, - "enabled_strategies": ["v53"], + "enabled_strategies": ["v53", "v53_fast"], "initial_balance": 10000, "risk_per_trade": 0.02, "max_positions": 4, diff --git a/backend/signal_engine.py b/backend/signal_engine.py index 4731df5..36dabe1 100644 --- a/backend/signal_engine.py +++ b/backend/signal_engine.py @@ -42,7 +42,7 @@ logger = logging.getLogger("signal-engine") SYMBOLS = ["BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"] LOOP_INTERVAL = 15 # 秒(从5改15,CPU降60%,信号质量无影响) STRATEGY_DIR = os.path.join(os.path.dirname(__file__), "strategies") -DEFAULT_STRATEGY_FILES = ["v51_baseline.json", "v52_8signals.json", "v53.json"] +DEFAULT_STRATEGY_FILES = ["v51_baseline.json", "v52_8signals.json", "v53.json", "v53_fast.json"] def load_strategy_configs() -> list[dict]: @@ -730,10 +730,32 @@ class SymbolState: strategy_name = strategy_cfg.get("name", "v53") strategy_threshold = int(strategy_cfg.get("threshold", 75)) flip_threshold = int(strategy_cfg.get("flip_threshold", 85)) + is_fast = strategy_name.endswith("_fast") snap = snapshot or self.build_evaluation_snapshot(now_ms) - cvd_fast = snap["cvd_fast"] - cvd_mid = snap["cvd_mid"] + + # v53_fast: 用自定义短窗口重算 cvd_fast / cvd_mid + if is_fast: + fast_ms = int(strategy_cfg.get("cvd_window_fast_ms", 5 * 60 * 1000)) + mid_ms = int(strategy_cfg.get("cvd_window_mid_ms", 30 * 60 * 1000)) + cutoff_fast = now_ms - fast_ms + cutoff_mid = now_ms - mid_ms + buy_f = sell_f = buy_m = sell_m = 0.0 + for t_ms, qty, _price, ibm in self.win_fast.trades: + if t_ms >= cutoff_fast: + if ibm == 0: buy_f += qty + else: sell_f += qty + # mid 从 win_mid 中读(win_mid 窗口是4h,包含30min内数据) + for t_ms, qty, _price, ibm in self.win_mid.trades: + if t_ms >= cutoff_mid: + if ibm == 0: buy_m += qty + else: sell_m += qty + cvd_fast = buy_f - sell_f + cvd_mid = buy_m - sell_m + else: + cvd_fast = snap["cvd_fast"] + cvd_mid = snap["cvd_mid"] + price = snap["price"] atr = snap["atr"] atr_value = snap.get("atr_value", atr) @@ -832,7 +854,21 @@ class SymbolState: (direction == "LONG" and cvd_fast_accel > 0) or (direction == "SHORT" and cvd_fast_accel < 0) ) else 0 - direction_score = min(cvd_resonance + p99_flow + accel_bonus, 55) + + # v53_fast:accel 独立触发路径(不要求 cvd 双线同向) + accel_independent_score = 0 + if is_fast and not no_direction: + accel_cfg = strategy_cfg.get("accel_independent", {}) + if accel_cfg.get("enabled", False): + # accel 足够大 + p99 大单同向 → 独立给分 + accel_strong = ( + (direction == "LONG" and cvd_fast_accel > 0 and has_aligned_p99) or + (direction == "SHORT" and cvd_fast_accel < 0 and has_aligned_p99) + ) + if accel_strong: + accel_independent_score = int(accel_cfg.get("min_direction_score", 35)) + + direction_score = max(min(cvd_resonance + p99_flow + accel_bonus, 55), accel_independent_score) # ── Crowding Layer(25分)───────────────────────────────── long_short_ratio = to_float(self.market_indicators.get("long_short_ratio")) @@ -858,7 +894,26 @@ class SymbolState: crowding_score = min(ls_score + top_trader_score, 25) # ── Environment Layer(15分)────────────────────────────── - environment_score = round(environment_score_raw / 15 * 15) + # OI变化率基础分(v53: 0~15) + oi_base_score = round(environment_score_raw / 15 * 10) # 缩到10分 + + # v53_fast:OBI 正向加分(5分,放入 environment 层) + obi_bonus = 0 + if is_fast and obi_raw is not None: + obi_cfg = strategy_cfg.get("obi_scoring", {}) + strong_thr = float(obi_cfg.get("strong_threshold", 0.30)) + weak_thr = float(obi_cfg.get("weak_threshold", 0.15)) + strong_sc = int(obi_cfg.get("strong_score", 5)) + weak_sc = int(obi_cfg.get("weak_score", 3)) + obi_aligned = (direction == "LONG" and obi_raw > 0) or (direction == "SHORT" and obi_raw < 0) + obi_abs = abs(obi_raw) + if obi_aligned: + if obi_abs >= strong_thr: + obi_bonus = strong_sc + elif obi_abs >= weak_thr: + obi_bonus = weak_sc + + environment_score = min(oi_base_score + obi_bonus, 15) if is_fast else round(environment_score_raw / 15 * 15) # ── Auxiliary Layer(5分)──────────────────────────────── coinbase_premium = to_float(self.market_indicators.get("coinbase_premium")) @@ -899,7 +954,7 @@ class SymbolState: "score": crowding_score, "max": 25, "lsr_contrarian": ls_score, "top_trader_position": top_trader_score, }, - "environment": {"score": environment_score, "max": 15}, + "environment": {"score": environment_score, "max": 15, "oi_base": oi_base_score if is_fast else environment_score, "obi_bonus": obi_bonus}, "auxiliary": {"score": aux_score, "max": 5, "coinbase_premium": coinbase_premium}, }, }) diff --git a/backend/strategies/v53_fast.json b/backend/strategies/v53_fast.json new file mode 100644 index 0000000..05d1d3e --- /dev/null +++ b/backend/strategies/v53_fast.json @@ -0,0 +1,60 @@ +{ + "name": "v53_fast", + "version": "5.3-fast", + "description": "V5.3 Fast 实验变体: CVD 5m/30m 短窗口 + OBI正向加分 + accel独立触发。对照组: v53(30m/4h)。", + "threshold": 75, + "flip_threshold": 85, + "cvd_window_fast_ms": 300000, + "cvd_window_mid_ms": 1800000, + "weights": { + "direction": 55, + "crowding": 25, + "environment": 15, + "auxiliary": 5 + }, + "obi_scoring": { + "strong_threshold": 0.30, + "weak_threshold": 0.15, + "strong_score": 5, + "weak_score": 3 + }, + "accel_independent": { + "enabled": true, + "accel_percentile_threshold": 0.95, + "min_direction_score": 35 + }, + "tp_sl": { + "sl_multiplier": 2.0, + "tp1_multiplier": 1.5, + "tp2_multiplier": 3.0, + "tp_maker": true + }, + "symbols": ["BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"], + "symbol_gates": { + "BTCUSDT": { + "min_vol_threshold": 0.002, + "whale_threshold_usd": 100000, + "whale_flow_threshold_pct": 0.5, + "obi_veto_threshold": 0.30, + "spot_perp_divergence_veto": 0.003 + }, + "ETHUSDT": { + "min_vol_threshold": 0.003, + "whale_threshold_usd": 50000, + "obi_veto_threshold": 0.35, + "spot_perp_divergence_veto": 0.005 + }, + "SOLUSDT": { + "min_vol_threshold": 0.006, + "whale_threshold_usd": 20000, + "obi_veto_threshold": 0.45, + "spot_perp_divergence_veto": 0.008 + }, + "XRPUSDT": { + "min_vol_threshold": 0.004, + "whale_threshold_usd": 30000, + "obi_veto_threshold": 0.40, + "spot_perp_divergence_veto": 0.006 + } + } +} diff --git a/frontend/app/paper-v53fast/page.tsx b/frontend/app/paper-v53fast/page.tsx new file mode 100644 index 0000000..18f993d --- /dev/null +++ b/frontend/app/paper-v53fast/page.tsx @@ -0,0 +1,391 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import { authFetch, useAuth } from "@/lib/auth"; +import { XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, Area, AreaChart } from "recharts"; + +function bjt(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")}`; +} + +function fmtPrice(p: number) { + return p < 100 ? p.toFixed(4) : p.toLocaleString("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 }); +} + +function parseFactors(raw: any) { + if (!raw) return null; + if (typeof raw === "string") { try { return JSON.parse(raw); } catch { return null; } } + return raw; +} + +const STRATEGY = "v53_fast"; +const ALL_COINS = ["BTCUSDT", "ETHUSDT", "XRPUSDT", "SOLUSDT"]; + +// ─── 最新信号 ──────────────────────────────────────────────────── + +function LatestSignals() { + const [signals, setSignals] = useState>({}); + useEffect(() => { + const f = async () => { + for (const sym of ALL_COINS) { + const coin = sym.replace("USDT", ""); + try { + const r = await authFetch(`/api/signals/signal-history?symbol=${coin}&limit=1&strategy=${STRATEGY}`); + if (r.ok) { const j = await r.json(); if (j.data?.length > 0) setSignals(prev => ({ ...prev, [sym]: j.data[0] })); } + } catch {} + } + }; + f(); const iv = setInterval(f, 15000); return () => clearInterval(iv); + }, []); + + return ( +
+
+

最新信号

+ v53 +
+
+ {ALL_COINS.map(sym => { + const s = signals[sym]; + const coin = sym.replace("USDT", ""); + const ago = s?.ts ? Math.round((Date.now() - s.ts) / 60000) : null; + const fc = s?.factors; + const gatePassed = fc?.gate_passed ?? true; + return ( +
+
+
+ {coin} + {s?.signal ? ( + <> + + {s.signal === "LONG" ? "🟢" : "🔴"} {s.signal} + + {s.score}分 + + ) : 暂无信号} +
+ {ago !== null && {ago < 60 ? `${ago}m前` : `${Math.round(ago/60)}h前`}} +
+ {fc && ( +
+ + {gatePassed ? "✅" : "❌"} {fc.gate_block || "Gate"} + + 方向{fc.direction?.score ?? 0}/55 + 拥挤{fc.crowding?.score ?? 0}/25 + 环境{fc.environment?.score ?? 0}/15 + 辅助{fc.auxiliary?.score ?? 0}/5 +
+ )} +
+ ); + })} +
+
+ ); +} + +// ─── 控制面板 ──────────────────────────────────────────────────── + +function ControlPanel() { + const [config, setConfig] = useState(null); + const [saving, setSaving] = useState(false); + useEffect(() => { + (async () => { try { const r = await authFetch("/api/paper/config"); if (r.ok) setConfig(await r.json()); } catch {} })(); + }, []); + const toggle = async () => { + setSaving(true); + try { + const r = await authFetch("/api/paper/config", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled: !config.enabled }) }); + if (r.ok) setConfig(await r.json().then((j: any) => j.config)); + } catch {} finally { setSaving(false); } + }; + if (!config) return null; + return ( +
+
+ + {config.enabled ? "🟢 运行中" : "⚪ 已停止"} +
+
+ 初始: ${config.initial_balance?.toLocaleString()} + 风险: {(config.risk_per_trade * 100).toFixed(0)}% + 最大: {config.max_positions}仓 +
+
+ ); +} + +// ─── 总览 ──────────────────────────────────────────────────────── + +function SummaryCards() { + const [data, setData] = useState(null); + useEffect(() => { + const f = async () => { try { const r = await authFetch(`/api/paper/summary?strategy=${STRATEGY}`); if (r.ok) setData(await r.json()); } catch {} }; + f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); + }, []); + if (!data) return
加载中...
; + return ( +
+ {[ + { label: "总盈亏(R)", value: `${data.total_pnl >= 0 ? "+" : ""}${data.total_pnl}R`, sub: `${data.total_pnl_usdt >= 0 ? "+" : ""}$${data.total_pnl_usdt}`, color: data.total_pnl >= 0 ? "text-emerald-600" : "text-red-500" }, + { label: "胜率", value: `${data.win_rate}%`, sub: `共${data.total_trades}笔`, color: "text-slate-800" }, + { label: "持仓中", value: data.active_positions, sub: "活跃仓位", color: "text-blue-600" }, + { label: "盈亏比", value: data.profit_factor, sub: "PF", color: "text-slate-800" }, + { label: "当前资金", value: `$${data.balance?.toLocaleString()}`, sub: "虚拟余额", color: data.balance >= 10000 ? "text-emerald-600" : "text-red-500" }, + { label: "状态", value: data.start_time ? "运行中 ✅" : "等待首笔", sub: "accumulating", color: "text-slate-600" }, + ].map(({ label, value, sub, color }) => ( +
+

{label}

+

{value}

+

{sub}

+
+ ))} +
+ ); +} + +// ─── 当前持仓 ──────────────────────────────────────────────────── + +function ActivePositions() { + const [positions, setPositions] = useState([]); + const [wsPrices, setWsPrices] = useState>({}); + const [paperRiskUsd, setPaperRiskUsd] = useState(200); + useEffect(() => { + (async () => { try { const r = await authFetch("/api/paper/config"); if (r.ok) { const cfg = await r.json(); setPaperRiskUsd((cfg.initial_balance||10000)*(cfg.risk_per_trade||0.02)); } } catch {} })(); + }, []); + useEffect(() => { + const f = async () => { try { const r = await authFetch(`/api/paper/positions?strategy=${STRATEGY}`); if (r.ok) setPositions((await r.json()).data||[]); } catch {} }; + f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); + }, []); + useEffect(() => { + const streams = ["btcusdt","ethusdt","xrpusdt","solusdt"].map(s=>`${s}@aggTrade`).join("/"); + const ws = new WebSocket(`wss://fstream.binance.com/stream?streams=${streams}`); + ws.onmessage = (e) => { try { const msg=JSON.parse(e.data); if(msg.data){const sym=msg.data.s;const price=parseFloat(msg.data.p);if(sym&&price>0)setWsPrices(prev=>({...prev,[sym]:price}));} } catch {} }; + return () => ws.close(); + }, []); + if (positions.length === 0) return
v53 暂无活跃持仓
; + return ( +
+

当前持仓 ● 实时

+
+ {positions.map((p: any) => { + const sym = p.symbol?.replace("USDT","") || ""; + const holdMin = Math.round((Date.now()-p.entry_ts)/60000); + const currentPrice = wsPrices[p.symbol]||p.current_price||0; + const entry = p.entry_price||0; + const riskDist = p.risk_distance||Math.abs(entry-(p.sl_price||entry))||1; + const tp1R = riskDist>0?(p.direction==="LONG"?((p.tp1_price||0)-entry)/riskDist:(entry-(p.tp1_price||0))/riskDist):0; + const fullR = riskDist>0?(p.direction==="LONG"?(currentPrice-entry)/riskDist:(entry-currentPrice)/riskDist):0; + const unrealR = p.tp1_hit?0.5*tp1R+0.5*fullR:fullR; + const unrealUsdt = unrealR*paperRiskUsd; + const fc = parseFactors(p.score_factors); + const track = fc?.track||(p.symbol==="BTCUSDT"?"BTC":"ALT"); + return ( +
+
+
+ {p.direction==="LONG"?"🟢":"🔴"} {sym} {p.direction} + {track} + 评分{p.score} +
+
+ =0?"text-emerald-600":"text-red-500"}`}>{unrealR>=0?"+":""}{unrealR.toFixed(2)}R + {holdMin}m +
+
+
+ 入: ${fmtPrice(p.entry_price)} + 现: ${currentPrice?fmtPrice(currentPrice):"-"} + TP1: ${fmtPrice(p.tp1_price)}{p.tp1_hit?" ✅":""} + TP2: ${fmtPrice(p.tp2_price)} + SL: ${fmtPrice(p.sl_price)} +
+
+ 入场时间: {p.entry_ts ? new Date(p.entry_ts).toLocaleString("zh-CN", {hour12:false, month:"2-digit", day:"2-digit", hour:"2-digit", minute:"2-digit", second:"2-digit"} as any) : "-"} +
+
+ ); + })} +
+
+ ); +} + +// ─── 权益曲线 ──────────────────────────────────────────────────── + +function EquityCurve() { + const [data, setData] = useState([]); + useEffect(() => { + const f = async () => { try { const r = await authFetch(`/api/paper/equity-curve?strategy=${STRATEGY}`); if (r.ok) setData((await r.json()).data||[]); } catch {} }; + f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); + }, []); + return ( +
+

权益曲线

+ {data.length < 2 ?
数据积累中...
: ( +
+ + + bjt(v)} tick={{ fontSize: 10 }} /> + `${v}R`} /> + bjt(Number(v))} formatter={(v: any) => [`${v}R`, "累计PnL"]} /> + + + + +
+ )} +
+ ); +} + +// ─── 历史交易 ──────────────────────────────────────────────────── + +type FilterSymbol = "all" | "BTC" | "ETH" | "XRP" | "SOL"; +type FilterResult = "all" | "win" | "loss"; + +function TradeHistory() { + const [trades, setTrades] = useState([]); + const [symbol, setSymbol] = useState("all"); + const [result, setResult] = useState("all"); + useEffect(() => { + const f = async () => { try { const r = await authFetch(`/api/paper/trades?symbol=${symbol}&result=${result}&strategy=${STRATEGY}&limit=50`); if (r.ok) setTrades((await r.json()).data||[]); } catch {} }; + f(); const iv = setInterval(f, 10000); return () => clearInterval(iv); + }, [symbol, result]); + return ( +
+
+

历史交易

+
+ {(["all","BTC","ETH","XRP","SOL"] as FilterSymbol[]).map(s => ( + + ))} + | + {(["all","win","loss"] as FilterResult[]).map(r => ( + + ))} +
+
+
+ {trades.length === 0 ?
暂无交易记录
: ( + + + + + + + + + + + + + + + {trades.map((t: any) => { + const holdMin = t.exit_ts&&t.entry_ts?Math.round((t.exit_ts-t.entry_ts)/60000):0; + const fc = parseFactors(t.score_factors); + const track = fc?.track||(t.symbol==="BTCUSDT"?"BTC":"ALT"); + return ( + + + + + + + + + + + ); + })} + +
币种方向入场出场PnL(R)状态分数持仓
{t.symbol?.replace("USDT","")}{track}{t.direction==="LONG"?"🟢":"🔴"} {t.direction}{fmtPrice(t.entry_price)}{t.exit_price?fmtPrice(t.exit_price):"-"}0?"text-emerald-600":t.pnl_r<0?"text-red-500":"text-slate-500"}`}>{t.pnl_r>0?"+":""}{t.pnl_r?.toFixed(2)}{t.status==="tp"?"止盈":t.status==="sl"?"止损":t.status==="sl_be"?"保本":t.status==="timeout"?"超时":t.status==="signal_flip"?"翻转":t.status}{t.score}{holdMin}m
+ )} +
+
+ ); +} + +// ─── 统计面板 ──────────────────────────────────────────────────── + +function StatsPanel() { + const [data, setData] = useState(null); + const [tab, setTab] = useState("ALL"); + useEffect(() => { + const f = async () => { try { const r = await authFetch(`/api/paper/stats?strategy=${STRATEGY}`); if (r.ok) setData(await r.json()); } catch {} }; + f(); const iv = setInterval(f, 30000); return () => clearInterval(iv); + }, []); + if (!data || data.error) return ( +
+

详细统计

+
等待交易记录积累...
+
+ ); + const coinTabs = ["ALL","BTC","ETH","XRP","SOL"]; + const st = tab==="ALL"?data:(data.by_symbol?.[tab]||null); + return ( +
+
+

详细统计

+
+ {coinTabs.map(t => ( + + ))} +
+
+ {st ? ( +
+
+
胜率

{st.win_rate}%

+
盈亏比

{st.win_loss_ratio}

+
平均盈利

+{st.avg_win}R

+
平均亏损

-{st.avg_loss}R

+
最大回撤

{st.mdd}R

+
夏普比率

{st.sharpe}

+
总盈亏

=0?"text-emerald-600":"text-red-500"}`}>{(st.total_pnl??0)>=0?"+":""}{st.total_pnl??"-"}R

+
总笔数

{st.total??data.total}

+
做多胜率

{st.long_win_rate}% ({st.long_count}笔)

+
做空胜率

{st.short_win_rate}% ({st.short_count}笔)

+
+
+ ) :
该币种暂无数据
} +
+ ); +} + +// ─── 主页面 ────────────────────────────────────────────────────── + +export default function PaperTradingV53Page() { + const { isLoggedIn, loading } = useAuth(); + if (loading) return
加载中...
; + if (!isLoggedIn) return ( +
+
🔒
+

请先登录查看模拟盘

+ 登录 +
+ ); + return ( +
+
+

📈 模拟盘 V5.3 Fast

+

实验变体 v53_fast · BTC/ETH/XRP/SOL · 四层评分 55/25/15/5 + per-symbol 四门控制

+
+ + + + + + + +
+ ); +} diff --git a/frontend/app/signals-v53fast/page.tsx b/frontend/app/signals-v53fast/page.tsx new file mode 100644 index 0000000..be069de --- /dev/null +++ b/frontend/app/signals-v53fast/page.tsx @@ -0,0 +1,565 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { authFetch } from "@/lib/auth"; +import { useAuth } from "@/lib/auth"; +import Link from "next/link"; +import { + ComposedChart, Area, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, + ReferenceLine, CartesianGrid, Legend +} from "recharts"; + +type Symbol = "BTC" | "ETH" | "XRP" | "SOL"; + +interface IndicatorRow { + ts: number; + cvd_fast: number; + cvd_mid: number; + cvd_day: number; + atr_5m: number; + vwap_30m: number; + price: number; + score: number; + signal: string | null; +} + +interface LatestIndicator { + ts: number; + cvd_fast: number; + cvd_mid: number; + cvd_day: number; + cvd_fast_slope: number; + atr_5m: number; + atr_percentile: number; + vwap_30m: number; + price: number; + p95_qty: number; + p99_qty: number; + score: number; + display_score?: number; // v53_btc: alt_score_ref(参考分) + gate_passed?: boolean; // v53_btc顶层字段 + signal: string | null; + tier?: "light" | "standard" | "heavy" | null; + factors?: { + track?: string; + direction?: { score?: number; max?: number; cvd_resonance?: number; p99_flow?: number; accel_bonus?: number }; + crowding?: { score?: number; max?: number; lsr_contrarian?: number; top_trader_position?: number }; + environment?: { score?: number; max?: number }; + auxiliary?: { score?: number; max?: number; coinbase_premium?: number }; + // BTC gate fields + gate_passed?: boolean; + block_reason?: string; // BTC用 + gate_block?: string; // ALT用 + obi_raw?: number; + spot_perp_div?: number; + whale_cvd_ratio?: number; + atr_pct_price?: number; + alt_score_ref?: number; + } | null; +} + +const WINDOWS = [ + { label: "1h", value: 60 }, + { label: "4h", value: 240 }, + { label: "12h", value: 720 }, + { label: "24h", value: 1440 }, +]; + +function bjtStr(ms: number) { + const d = new Date(ms + 8 * 3600 * 1000); + return `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}`; +} + +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 fmt(v: number, decimals = 1): string { + if (Math.abs(v) >= 1000000) return `${(v / 1000000).toFixed(1)}M`; + if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(1)}K`; + return v.toFixed(decimals); +} + +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)); + return ( +
+ {label} +
+
+
+ {score}/{max} +
+ ); +} + +// ─── ALT Gate 状态卡片 ────────────────────────────────────────── + +const ALT_GATE_THRESHOLDS: Record = { + ETH: { vol: "0.3%", obi: "0.35", spd: "0.5%", whale: "$50k" }, + XRP: { vol: "0.4%", obi: "0.40", spd: "0.6%", whale: "$30k" }, + SOL: { vol: "0.6%", obi: "0.45", spd: "0.8%", whale: "$20k" }, +}; + +function ALTGateCard({ symbol, factors }: { symbol: Symbol; factors: LatestIndicator["factors"] }) { + if (!factors || symbol === "BTC") return null; + const thresholds = ALT_GATE_THRESHOLDS[symbol] ?? ALT_GATE_THRESHOLDS["ETH"]; + const passed = factors.gate_passed ?? true; + const blockReason = factors.gate_block; + return ( +
+
+

🔒 {symbol} Gate-Control

+ + {passed ? "✅ Gate通过" : "❌ 否决"} + +
+
+
+

波动率

+

{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%

+

需 ≥{thresholds.vol}

+
+
+

OBI

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.obi_raw ?? 0) * 100).toFixed(2)}% +

+

否决±{thresholds.obi}

+
+
+

期现背离

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps +

+

否决±{thresholds.spd}

+
+
+

鲸鱼阈值

+

{thresholds.whale}

+

大单门槛

+
+
+ {blockReason && ( +

+ 否决原因: {blockReason} +

+ )} +
+ ); +} + +// ─── BTC Gate 状态卡片 ─────────────────────────────────────────── + +function BTCGateCard({ factors }: { factors: LatestIndicator["factors"] }) { + if (!factors) return null; + return ( +
+
+

⚡ BTC Gate-Control

+ + {factors.gate_passed ? "✅ Gate通过" : "❌ 否决"} + +
+
+
+

波动率

+

{((factors.atr_pct_price ?? 0) * 100).toFixed(3)}%

+

需 ≥0.2%

+
+
+

OBI

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.obi_raw ?? 0) * 100).toFixed(2)}% +

+

盘口失衡

+
+
+

期现背离

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.spot_perp_div ?? 0) * 10000).toFixed(2)}bps +

+

spot-perp

+
+
+

巨鲸CVD

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {((factors.whale_cvd_ratio ?? 0) * 100).toFixed(2)}% +

+

>$100k

+
+
+ {factors.block_reason && ( +

+ 否决原因: {factors.block_reason} +

+ )} +
+ ); +} + +// ─── 实时指标卡片 ──────────────────────────────────────────────── + +function IndicatorCards({ symbol }: { symbol: Symbol }) { + const [data, setData] = useState(null); + const strategy = "v53"; + + useEffect(() => { + const fetch = async () => { + try { + const res = await authFetch(`/api/signals/latest?strategy=${strategy}`); + if (!res.ok) return; + const json = await res.json(); + setData(json[symbol] || null); + } catch {} + }; + fetch(); + const iv = setInterval(fetch, 5000); + return () => clearInterval(iv); + }, [symbol, strategy]); + + if (!data) return
等待指标数据...
; + + const isBTC = symbol === "BTC"; + const priceVsVwap = data.price > data.vwap_30m ? "上方" : "下方"; + + return ( +
+ {/* CVD三轨 */} +
+
+

CVD_fast (30m)

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {fmt(data.cvd_fast)} +

+

+ 斜率: = 0 ? "text-emerald-600" : "text-red-500"}> + {data.cvd_fast_slope >= 0 ? "↑" : "↓"}{fmt(Math.abs(data.cvd_fast_slope))} + +

+
+
+

CVD_mid (4h)

+

= 0 ? "text-emerald-600" : "text-red-500"}`}> + {fmt(data.cvd_mid)} +

+

{data.cvd_mid > 0 ? "多" : "空"}头占优

+
+
+

CVD共振

+

= 0 && data.cvd_mid >= 0 ? "text-emerald-600" : data.cvd_fast < 0 && data.cvd_mid < 0 ? "text-red-500" : "text-slate-400"}`}> + {data.cvd_fast >= 0 && data.cvd_mid >= 0 ? "✅ 多头共振" : data.cvd_fast < 0 && data.cvd_mid < 0 ? "✅ 空头共振" : "⚠️ 分歧"} +

+

V5.3核心信号

+
+
+ + {/* ATR + VWAP */} +
+
+

ATR

+

${fmt(data.atr_5m, 2)}

+

+ 60 ? "text-amber-600 font-semibold" : "text-slate-400"}> + {data.atr_percentile.toFixed(0)}%{data.atr_percentile > 60 ? "🔥" : ""} + +

+
+
+

VWAP

+

${data.vwap_30m.toLocaleString("en-US", { maximumFractionDigits: 1 })}

+

+ 价格在 data.vwap_30m ? "text-emerald-600" : "text-red-500"}>{priceVsVwap} +

+
+
+

P95

+

{data.p95_qty.toFixed(4)}

+

大单阈值

+
+
+

P99

+

{data.p99_qty.toFixed(4)}

+

超大单

+
+
+ + {/* 信号状态 */} +
+
+
+

+ {isBTC ? "BTC Gate-Control" : "ALT 四层评分"} + {" · "}{"v53"} +

+

+ {data.signal === "LONG" ? "🟢 做多" : data.signal === "SHORT" ? "🔴 做空" : "⚪ 无信号"} +

+
+
+ {isBTC ? ( + <> +

+ {data.display_score ?? data.factors?.alt_score_ref ?? data.score}/100 + 参考分 +

+

+ {(data.gate_passed ?? data.factors?.gate_passed) ? (data.tier === "standard" ? "标准" : "不开仓") : "Gate否决"} +

+ + ) : ( + <> +

{data.score}/100

+

{data.tier === "heavy" ? "加仓" : data.tier === "standard" ? "标准" : "不开仓"}

+ + )} +
+
+ + {/* 四层分数 — ALT和BTC都显示 */} +
+ + + + +
+
+ + {/* ALT Gate 卡片 */} + {!isBTC && data.factors && } + + {/* BTC Gate 卡片 */} + {isBTC && data.factors && } +
+ ); +} + +// ─── 信号历史 ──────────────────────────────────────────────────── + +interface SignalRecord { + ts: number; + score: number; + signal: string; +} + +function SignalHistory({ symbol }: { symbol: Symbol }) { + const [data, setData] = useState([]); + const strategy = "v53"; + + useEffect(() => { + const fetchData = async () => { + try { + const res = await authFetch(`/api/signals/signal-history?symbol=${symbol}&limit=20&strategy=${strategy}`); + if (!res.ok) return; + const json = await res.json(); + setData(json.data || []); + } catch {} + }; + fetchData(); + const iv = setInterval(fetchData, 15000); + return () => clearInterval(iv); + }, [symbol, strategy]); + + if (data.length === 0) return null; + + return ( +
+
+

最近信号 ({strategy})

+
+
+ {data.map((s, i) => ( +
+
+ + {s.signal === "LONG" ? "🟢 LONG" : "🔴 SHORT"} + + {bjtFull(s.ts)} +
+
+ {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 ? "标准" : "不开仓"} + +
+
+ ))} +
+
+ ); +} + +// ─── CVD图表 ──────────────────────────────────────────────────── + +function CVDChart({ symbol, minutes }: { symbol: Symbol; minutes: number }) { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const strategy = "v53"; + + const fetchData = useCallback(async (silent = false) => { + try { + const res = await authFetch(`/api/signals/indicators?symbol=${symbol}&minutes=${minutes}&strategy=${strategy}`); + if (!res.ok) return; + const json = await res.json(); + setData(json.data || []); + if (!silent) setLoading(false); + } catch {} + }, [symbol, minutes, strategy]); + + useEffect(() => { + setLoading(true); + fetchData(); + const iv = setInterval(() => fetchData(true), 30000); + return () => clearInterval(iv); + }, [fetchData]); + + const chartData = data.map(d => ({ + time: bjtStr(d.ts), + fast: parseFloat(d.cvd_fast?.toFixed(2) || "0"), + mid: parseFloat(d.cvd_mid?.toFixed(2) || "0"), + price: d.price, + })); + + const prices = chartData.map(d => d.price).filter(v => v > 0); + const pMin = prices.length ? Math.min(...prices) : 0; + const pMax = prices.length ? Math.max(...prices) : 0; + const pPad = (pMax - pMin) * 0.3 || pMax * 0.001; + + if (loading) return
加载指标数据...
; + if (data.length === 0) return
暂无 V5.3 指标数据,signal-engine 需运行积累
; + + return ( + + + + + + v >= 1000 ? `$${(v / 1000).toFixed(1)}k` : `$${v.toFixed(0)}`} + /> + { + if (name === "price") return [`$${Number(v).toLocaleString()}`, "币价"]; + if (name === "fast") return [fmt(Number(v)), "CVD_fast(30m)"]; + return [fmt(Number(v)), "CVD_mid(4h)"]; + }} + contentStyle={{ background: "#fff", border: "1px solid #e2e8f0", borderRadius: 8, fontSize: 11 }} + /> + + + + + + + + ); +} + +// ─── 主页面 ────────────────────────────────────────────────────── + +export default function SignalsV53Page() { + const { isLoggedIn, loading } = useAuth(); + const [symbol, setSymbol] = useState("ETH"); + const [minutes, setMinutes] = useState(240); + + if (loading) return
加载中...
; + if (!isLoggedIn) return ( +
+
🔒
+

请先登录查看信号数据

+
+ 登录 + 注册 +
+
+ ); + + return ( +
+
+
+

⚡ 信号引擎 V5.3

+

+ 四层评分 55/25/15/5 · ALT双轨 + BTC gate-control · + {symbol === "BTC" ? " 🔵 BTC轨(gate-control)" : " 🟣 ALT轨(ETH/XRP/SOL)"} +

+
+
+ {(["BTC", "ETH", "XRP", "SOL"] as Symbol[]).map(s => ( + + ))} +
+
+ + + + +
+
+
+

CVD三轨 + 币价

+

蓝=fast(30m) · 紫=mid(4h) · 橙=价格

+
+
+ {WINDOWS.map(w => ( + + ))} +
+
+
+ +
+
+ +
+
+

📖 V5.3 双轨信号说明

+
+
+
+ 🟣 ALT轨(ETH/XRP/SOL)— 四层线性评分 +
+

1️⃣ 方向层(55分) — CVD共振30分(fast+mid同向)+ P99大单对齐20分 + 加速奖励5分。删除独立确认层,解决CVD双重计分问题。

+

2️⃣ 拥挤层(25分) — LSR反向拥挤15分(散户过度拥挤=信号)+ 大户持仓方向10分。

+

3️⃣ 环境层(15分) — OI变化率,新资金进场vs撤离,判断趋势持续性。

+

4️⃣ 辅助层(5分) — Coinbase Premium,美系机构动向。

+
+
+
+ 🔵 BTC轨 — Gate-Control逻辑 +
+

波动率门控:ATR/Price ≥ 0.2%,低波动行情拒绝开仓

+

OBI否决:订单簿失衡超阈值且与信号方向冲突时否决(实时100ms)

+

期现背离否决:spot与perp价差超阈值时否决(实时1s)

+

巨鲸CVD:>$100k成交额净CVD,15分钟滚动窗口实时计算

+
+
+
+ 档位:<75不开仓 · 75-84标准 · ≥85加仓 · 冷却10分钟 +
+
+
+
+ ); +} diff --git a/frontend/components/Sidebar.tsx b/frontend/components/Sidebar.tsx index 38aa46d..c82d310 100644 --- a/frontend/components/Sidebar.tsx +++ b/frontend/components/Sidebar.tsx @@ -16,6 +16,8 @@ const navItems = [ { href: "/live", label: "⚡ 实盘交易", icon: Bolt, section: "── 实盘 ──" }, { href: "/signals-v53", label: "V5.3 信号引擎", icon: Zap, section: "── V5.3 ──" }, { href: "/paper-v53", label: "V5.3 模拟盘", icon: LineChart }, + { href: "/signals-v53fast", label: "V5.3 Fast 信号", icon: Zap, section: "── V5.3 Fast ──" }, + { href: "/paper-v53fast", label: "V5.3 Fast 模拟盘", icon: LineChart }, { href: "/server", label: "服务器", icon: Monitor }, { href: "/about", label: "说明", icon: Info }, ];