diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 8c6ec93..1fcd895 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -72,6 +72,9 @@ class PositionManager: self._monitoring_enabled = True # 是否启用实时监控 self._pending_entry_orders: Dict[str, Dict] = {} # 未成交的入场订单(避免重复挂单) self._last_entry_attempt_ms: Dict[str, int] = {} # 每个symbol的最近一次尝试(冷却/去抖) + # 自动平仓去抖/限流(避免止损触发后反复下单/刷屏) + self._last_auto_close_attempt_ms: Dict[str, int] = {} + self._last_auto_close_fail_log_ms: Dict[str, int] = {} @staticmethod def _pct_like_to_ratio(v: float) -> float: @@ -2456,6 +2459,20 @@ class PositionManager: # 如果触发止损止盈,执行平仓 if should_close: + # 自动平仓限流:避免同一 symbol 短时间内反复触发平仓请求(WebSocket 高频推送下很常见) + try: + now_ms = int(time.time() * 1000) + except Exception: + now_ms = None + if now_ms is not None: + cooldown_sec = int(config.TRADING_CONFIG.get("AUTO_CLOSE_COOLDOWN_SEC", 20) or 0) + last_ms = self._last_auto_close_attempt_ms.get(symbol) + if last_ms and cooldown_sec > 0 and now_ms - last_ms < cooldown_sec * 1000: + # 不重复刷屏:仅 debug + logger.debug(f"{symbol} [自动平仓] 冷却中({cooldown_sec}s),跳过重复平仓尝试") + return + self._last_auto_close_attempt_ms[symbol] = now_ms + logger.info( f"{symbol} [自动平仓] 开始执行平仓操作 | " f"原因: {exit_reason} | " @@ -2478,7 +2495,37 @@ class PositionManager: except Exception as sync_error: logger.warning(f"{symbol} [自动平仓] 状态同步失败: {sync_error}") else: - logger.error(f"{symbol} [自动平仓] ❌ 平仓失败") + # 平仓失败:先二次核对币安是否已无仓位(常见于竞态/网络抖动/幂等场景) + live_amt = None + try: + live_amt = await self._get_live_position_amt(symbol, position_side=None) + except Exception: + live_amt = None + + if live_amt is not None and abs(live_amt) <= 0: + logger.warning(f"{symbol} [自动平仓] 平仓返回失败,但币安持仓已为0,按已平仓处理(避免误报)") + # 尝试同步一次,让DB与界面尽快一致(失败也不刷屏) + try: + await self.sync_positions_with_binance() + except Exception: + pass + return + + # 仍有仓位:减少刷屏(按时间窗口合并/限流) + should_log = True + if now_ms is not None: + log_cd = int(config.TRADING_CONFIG.get("AUTO_CLOSE_FAIL_LOG_COOLDOWN_SEC", 600) or 0) + last_log = self._last_auto_close_fail_log_ms.get(symbol) + if last_log and log_cd > 0 and now_ms - last_log < log_cd * 1000: + should_log = False + else: + self._last_auto_close_fail_log_ms[symbol] = now_ms + + if should_log: + logger.error(f"{symbol} [自动平仓] ❌ 平仓失败(币安持仓仍存在: {live_amt})") + else: + logger.warning(f"{symbol} [自动平仓] 平仓仍失败(已在短时间内记录,暂不重复输出)") + # 即使平仓失败,也尝试同步状态(可能币安已经平仓了) try: await self.sync_positions_with_binance()