This commit is contained in:
薇薇安 2026-03-12 16:01:08 +08:00
parent 3e6ce55663
commit 199c4a95dd

View File

@ -183,6 +183,8 @@ class PositionManager:
# 自动平仓去抖/限流(避免止损触发后反复下单/刷屏) # 自动平仓去抖/限流(避免止损触发后反复下单/刷屏)
self._last_auto_close_attempt_ms: Dict[str, int] = {} self._last_auto_close_attempt_ms: Dict[str, int] = {}
self._last_auto_close_fail_log_ms: Dict[str, int] = {} self._last_auto_close_fail_log_ms: Dict[str, int] = {}
# 「当前价已触发止损价,无法挂止损单→立即市价平仓」去重,避免同一 symbol 短时间多次打日志/重复平仓
self._sl_protection_close_triggered_at: Dict[str, float] = {} # symbol -> time.time()
async def _get_open_positions(self, force_rest: bool = False) -> List[Dict]: async def _get_open_positions(self, force_rest: bool = False) -> List[Dict]:
"""优先使用 User Data Stream 持仓缓存Redis无缓存或未启动时走 REST。多账号时必须传 account_id 读对应缓存。""" """优先使用 User Data Stream 持仓缓存Redis无缓存或未启动时走 REST。多账号时必须传 account_id 读对应缓存。"""
@ -203,6 +205,18 @@ class PositionManager:
return bal return bal
return await self.client.get_account_balance() return await self.client.get_account_balance()
def _should_skip_sl_protection_close(self, symbol: str, cooldown_sec: float = 60.0) -> bool:
"""同一 symbol 在 cooldown_sec 内已触发过「价格已触发止损→立即市价平仓」则跳过,避免重复日志与重复平仓。"""
now = time.time()
to_remove = [s for s, t in self._sl_protection_close_triggered_at.items() if now - t > cooldown_sec]
for s in to_remove:
del self._sl_protection_close_triggered_at[s]
return symbol in self._sl_protection_close_triggered_at
def _mark_sl_protection_close_triggered(self, symbol: str) -> None:
"""标记该 symbol 已触发保护平仓(用于去重)。"""
self._sl_protection_close_triggered_at[symbol] = time.time()
@staticmethod @staticmethod
def _pct_like_to_ratio(v: float) -> float: def _pct_like_to_ratio(v: float) -> float:
""" """
@ -1887,10 +1901,12 @@ class PositionManager:
if side == "BUY": if side == "BUY":
# 做多:当前价 <= 止损价,说明已触发止损 # 做多:当前价 <= 止损价,说明已触发止损
if current_price_val <= stop_loss_val: if current_price_val <= stop_loss_val:
if self._should_skip_sl_protection_close(symbol):
return
self._mark_sl_protection_close_triggered(symbol)
entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A' entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A'
logger.error( logger.warning(
f"{symbol} ⚠️ 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f}),无法挂止损单,立即执行市价平仓保护!" f"{symbol} 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f}),无法挂止损单,立即执行市价平仓保护 | 入场价: {entry_price_str}"
f" | 入场价: {entry_price_str}"
) )
# 立即执行市价平仓 # 立即执行市价平仓
await self.close_position(symbol, reason='stop_loss') await self.close_position(symbol, reason='stop_loss')
@ -1908,10 +1924,12 @@ class PositionManager:
elif side == "SELL": elif side == "SELL":
# 做空:当前价 >= 止损价,说明已触发止损 # 做空:当前价 >= 止损价,说明已触发止损
if current_price_val >= stop_loss_val: if current_price_val >= stop_loss_val:
if self._should_skip_sl_protection_close(symbol):
return
self._mark_sl_protection_close_triggered(symbol)
entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A' entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A'
logger.error( logger.warning(
f"{symbol} ⚠️ 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f}),无法挂止损单,立即执行市价平仓保护!" f"{symbol} 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f}),无法挂止损单,立即执行市价平仓保护 | 入场价: {entry_price_str}"
f" | 入场价: {entry_price_str}"
) )
# 立即执行市价平仓 # 立即执行市价平仓
await self.close_position(symbol, reason='stop_loss') await self.close_position(symbol, reason='stop_loss')
@ -1943,7 +1961,10 @@ class PositionManager:
# 检查是否是 -2021 (立即触发) # 检查是否是 -2021 (立即触发)
error_msg = str(e) error_msg = str(e)
if "-2021" in error_msg or "immediately trigger" in error_msg: if "-2021" in error_msg or "immediately trigger" in error_msg:
logger.error(f"{symbol} ⚠️ 止损单会立即触发(-2021),视为已触发止损,立即执行市价平仓") if self._should_skip_sl_protection_close(symbol):
return
self._mark_sl_protection_close_triggered(symbol)
logger.warning(f"{symbol} 止损单会立即触发(-2021),视为已触发止损,立即执行市价平仓")
await self.close_position(symbol, reason='stop_loss') await self.close_position(symbol, reason='stop_loss')
return return
@ -1964,7 +1985,10 @@ class PositionManager:
except Exception as retry_e: except Exception as retry_e:
retry_msg = str(retry_e) retry_msg = str(retry_e)
if "-2021" in retry_msg or "immediately trigger" in retry_msg: if "-2021" in retry_msg or "immediately trigger" in retry_msg:
logger.error(f"{symbol} ⚠️ 重试挂止损单会立即触发(-2021),立即执行市价平仓") if self._should_skip_sl_protection_close(symbol):
return
self._mark_sl_protection_close_triggered(symbol)
logger.warning(f"{symbol} 重试挂止损单会立即触发(-2021),立即执行市价平仓")
await self.close_position(symbol, reason='stop_loss') await self.close_position(symbol, reason='stop_loss')
return return
logger.error(f"{symbol} 重试挂止损单失败: {retry_e}") logger.error(f"{symbol} 重试挂止损单失败: {retry_e}")
@ -1986,7 +2010,10 @@ class PositionManager:
except Exception as e: except Exception as e:
error_msg = str(e) error_msg = str(e)
if "-2021" in error_msg or "immediately trigger" in error_msg: if "-2021" in error_msg or "immediately trigger" in error_msg:
logger.error(f"{symbol} ⚠️ 止损单会立即触发(-2021),视为已触发止损,立即执行市价平仓") if self._should_skip_sl_protection_close(symbol):
return
self._mark_sl_protection_close_triggered(symbol)
logger.warning(f"{symbol} 止损单会立即触发(-2021),视为已触发止损,立即执行市价平仓")
await self.close_position(symbol, reason='stop_loss') await self.close_position(symbol, reason='stop_loss')
return return
logger.error(f"{symbol} 挂止损单失败API/网络): {e}") logger.error(f"{symbol} 挂止损单失败API/网络): {e}")
@ -2041,15 +2068,14 @@ class PositionManager:
should_close = True should_close = True
if should_close: if should_close:
if self._should_skip_sl_protection_close(symbol):
return
self._mark_sl_protection_close_triggered(symbol)
entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A' entry_price_str = f"{entry_price_val:.8f}" if entry_price_val is not None else 'N/A'
logger.error("=" * 80) logger.warning(
logger.error(f"{symbol} ⚠️ 止损单挂单失败,但当前价格已触发止损,立即执行市价平仓保护!") f"{symbol} 止损单挂单失败,当前价格已触发止损,立即执行市价平仓保护 | "
logger.error(f" 当前价格: {current_price_val:.8f}") f"当前价: {current_price_val:.8f}, 止损价: {stop_loss_val:.8f}, 入场价: {entry_price_str}"
logger.error(f" 止损价格: {stop_loss_val:.8f}") )
logger.error(f" 入场价格: {entry_price_str}")
logger.error(f" 持仓方向: {side}")
logger.error(f" 价格偏离: {abs(current_price_val - stop_loss_val):.8f} ({abs(current_price_val - stop_loss_val)/stop_loss_val*100:.2f}%)")
logger.error("=" * 80)
# 立即执行市价平仓 # 立即执行市价平仓
if await self.close_position(symbol, reason='stop_loss'): if await self.close_position(symbol, reason='stop_loss'):
logger.info(f"{symbol} ✓ 止损平仓成功(止损单挂单失败后的保护措施)") logger.info(f"{symbol} ✓ 止损平仓成功(止损单挂单失败后的保护措施)")