1
This commit is contained in:
parent
3e6ce55663
commit
199c4a95dd
|
|
@ -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} ✓ 止损平仓成功(止损单挂单失败后的保护措施)")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user