diff --git a/backend/signal_engine.py b/backend/signal_engine.py index 5082f08..0324011 100644 --- a/backend/signal_engine.py +++ b/backend/signal_engine.py @@ -317,8 +317,8 @@ class SymbolState: self.last_signal_dir: dict[str, str] = {} self.recent_large_trades: deque = deque() # ── Phase 2 实时内存字段(由后台WebSocket协程更新)────────── - self.rt_obi: float = 0.0 # 订单簿失衡[-1,1] - self.rt_spot_perp_div: float = 0.0 # 期现背离(spot-mark)/mark + self.rt_obi: float = 0.0 # 订单簿失衡[-1,1](实时WebSocket,所有symbol) + self.rt_spot_perp_div: float = 0.0 # 期现背离(spot-mark)/mark(实时WebSocket,所有symbol) # tiered_cvd_whale:按成交额分档,实时累计(最近15分钟窗口) self._whale_trades: deque = deque() # (time_ms, usd_val, is_sell) self.WHALE_WINDOW_MS: int = 15 * 60 * 1000 # 15分钟 @@ -753,6 +753,20 @@ class SymbolState: last_signal_ts = self.last_signal_ts.get(strategy_name, 0) in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS + # ── Per-symbol 四门参数(从 strategy_cfg.symbol_gates 读取)────────── + symbol_gates = (strategy_cfg.get("symbol_gates") or {}).get(self.symbol, {}) + min_vol = float(symbol_gates.get("min_vol_threshold", 0.003)) + whale_usd = float(symbol_gates.get("whale_threshold_usd", 50000)) + obi_veto = float(symbol_gates.get("obi_veto_threshold", 0.35)) + spd_veto = float(symbol_gates.get("spot_perp_divergence_veto", 0.005)) + + gate_block = None # 门控否决原因,None = 全部通过 + + # 门1:波动率下限 + atr_pct_price = atr / price if price > 0 else 0 + if atr_pct_price < min_vol: + gate_block = f"low_vol({atr_pct_price:.4f}<{min_vol})" + # ── Direction Layer(55分)────────────────────────────────── # cvd_resonance(30分):fast+mid同向 = 有效方向 if cvd_fast > 0 and cvd_mid > 0: @@ -768,6 +782,37 @@ class SymbolState: cvd_resonance = 0 no_direction = True # gate: 方向不一致 → 直接不开仓 + # 门2:鲸鱼推力(大单净方向需与信号方向一致) + if not gate_block and not no_direction: + whale_aligned = any( + (direction == "LONG" and lt[2] == 0 and lt[1] * price >= whale_usd) or + (direction == "SHORT" and lt[2] == 1 and lt[1] * price >= whale_usd) + for lt in self.recent_large_trades + ) + whale_adverse = any( + (direction == "LONG" and lt[2] == 1 and lt[1] * price >= whale_usd) or + (direction == "SHORT" and lt[2] == 0 and lt[1] * price >= whale_usd) + for lt in self.recent_large_trades + ) + # 有明确对立大单时否决(没有大单时不否决,给机会) + if whale_adverse and not whale_aligned: + gate_block = f"whale_adverse(>${whale_usd/1000:.0f}k)" + + # 门3:OBI否决(优先实时WebSocket值,fallback DB) + obi_raw = self.rt_obi if self.rt_obi != 0.0 else to_float(self.market_indicators.get("obi_depth_10")) + if not gate_block and not no_direction and obi_raw is not None: + if direction == "LONG" and obi_raw < -obi_veto: + gate_block = f"obi_veto({obi_raw:.3f}<-{obi_veto})" + elif direction == "SHORT" and obi_raw > obi_veto: + gate_block = f"obi_veto({obi_raw:.3f}>{obi_veto})" + + # 门4:期现背离否决(优先实时WebSocket值,fallback DB) + spot_perp_div = self.rt_spot_perp_div if self.rt_spot_perp_div != 0.0 else to_float(self.market_indicators.get("spot_perp_divergence")) + if not gate_block and not no_direction and spot_perp_div is not None: + if (direction == "LONG" and spot_perp_div < -spd_veto) or \ + (direction == "SHORT" and spot_perp_div > spd_veto): + gate_block = f"spd_veto({spot_perp_div:.4f})" + # p99_flow_alignment(0/10/20分) has_adverse_p99 = any( (direction == "LONG" and lt[2] == 1) or (direction == "SHORT" and lt[2] == 0) @@ -833,12 +878,22 @@ class SymbolState: total_score = min(direction_score + crowding_score + environment_score + aux_score, 100) total_score = max(0, round(total_score, 1)) + # 门控否决时归零分、清方向 + gate_passed = gate_block is None + if not gate_passed: + total_score = 0 + result.update({ "score": total_score, - "direction": direction if not no_direction else None, + "direction": direction if (not no_direction and gate_passed) else None, "atr_value": atr_value, "factors": { "track": "ALT", + "gate_passed": gate_passed, + "gate_block": gate_block, + "atr_pct_price": round(atr_pct_price, 5), + "obi_raw": obi_raw, + "spot_perp_div": spot_perp_div, "direction": { "score": direction_score, "max": 55, "cvd_resonance": cvd_resonance, "p99_flow": p99_flow, "accel_bonus": accel_bonus, @@ -852,7 +907,7 @@ class SymbolState: }, }) - if not no_direction and not in_cooldown: + if not no_direction and gate_passed and not in_cooldown: if total_score >= flip_threshold: result["signal"] = direction result["tier"] = "heavy" @@ -1314,14 +1369,15 @@ async def _ws_spot_perp_stream(symbol: str, state): await asyncio.gather(read_spot(), read_perp()) async def _realtime_ws_runner(states: dict): - """统一启动所有实时WebSocket协程,目前只给BTC""" - btc_state = states.get("BTCUSDT") - if btc_state is None: - return - await asyncio.gather( - _ws_obi_stream("BTCUSDT", btc_state), - _ws_spot_perp_stream("BTCUSDT", btc_state), - ) + """统一启动所有实时WebSocket协程,BTC + ALT (ETH/XRP/SOL)""" + coros = [] + for sym, state in states.items(): + # OBI流:所有symbol都接(perp depth10) + coros.append(_ws_obi_stream(sym, state)) + # 期现背离流:只有有现货+合约的币种(BTC/ETH/XRP/SOL都有) + coros.append(_ws_spot_perp_stream(sym, state)) + if coros: + await asyncio.gather(*coros) def start_realtime_ws(states: dict): """在独立线程里跑asyncio event loop,驱动实时WebSocket采集""" diff --git a/backend/strategies/v53_alt.json b/backend/strategies/v53_alt.json index b5468ee..6b424c7 100644 --- a/backend/strategies/v53_alt.json +++ b/backend/strategies/v53_alt.json @@ -2,7 +2,7 @@ "name": "v53_alt", "version": "5.3", "track": "ALT", - "description": "V5.3 ALT轨(ETH/XRP/SOL): 55/25/15/5权重,删除确认层(解决CVD双重计分),独立BTC走gate-control", + "description": "V5.3 ALT轨(ETH/XRP/SOL): 55/25/15/5权重,删除确认层,per-symbol四门控制", "threshold": 75, "flip_threshold": 85, "weights": { @@ -28,5 +28,25 @@ "tp_maker": true }, "symbols": ["ETHUSDT", "XRPUSDT", "SOLUSDT"], - "signals": ["cvd", "p99", "accel", "ls_ratio", "top_trader", "oi", "coinbase_premium"] + "signals": ["cvd", "p99", "accel", "ls_ratio", "top_trader", "oi", "coinbase_premium"], + "symbol_gates": { + "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 + } + } }