fix(position_manager): 增强止损和止盈同步逻辑及日志记录

在持仓管理中,优化了止损和止盈的同步逻辑,确保在同步失败时记录详细的异常信息。同时,增加了对止损和止盈为空的警告日志,提升了系统的可用性和风险控制能力。此外,调整了移动止损的配置逻辑,确保在未设置时使用默认值。这一改动旨在提升交易策略的稳定性与用户友好性。
This commit is contained in:
薇薇安 2026-02-25 21:48:46 +08:00
parent d7ccbe38e4
commit 41b2a21c3d

View File

@ -1444,9 +1444,13 @@ class PositionManager:
take_profit = None
if not stop_loss or not take_profit:
logger.warning(f"{symbol} 止损或止盈价格为空,跳过挂保护单: stop_loss={stop_loss}, take_profit={take_profit}")
logger.warning(f"[账号{self.account_id}] {symbol} 同步跳过: 止损或止盈为空 stop_loss={stop_loss} take_profit={take_profit}")
return
logger.info(
f"[账号{self.account_id}] {symbol} 开始同步止损/止盈至交易所: SL={float(stop_loss):.4f} TP={float(take_profit):.4f}"
)
# 验证止损价格是否合理。保本/移动止损时:多单止损可≥入场价、空单止损可≤入场价,不得被改回亏损价
entry_price = position_info.get("entryPrice")
if entry_price:
@ -1503,8 +1507,9 @@ class PositionManager:
await self.client.cancel_open_algo_orders_by_order_types(
symbol, {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}
)
logger.info(f"[账号{self.account_id}] {symbol} 已取消旧保护单,准备挂新单")
except Exception as e:
logger.debug(f"{symbol} 取消旧保护单时出错(可忽略): {e}")
logger.warning(f"[账号{self.account_id}] {symbol} 取消旧保护单异常: {e},继续尝试挂新单")
# 获取当前价格(如果未提供):优先 WS 缓存bookTicker/ticker24h→ 持仓 markPrice → REST ticker
if current_price is None:
@ -1854,6 +1859,9 @@ class PositionManager:
f"SL={position_info.get('exchangeSlOrderId') or '-'} "
f"TP={position_info.get('exchangeTpOrderId') or '-'}"
)
logger.info(f"[账号{self.account_id}] {symbol} 止损/止盈同步至交易所完成")
else:
logger.warning(f"[账号{self.account_id}] {symbol} 同步结束但未挂上保护单(止损或止盈挂单均失败),将依赖 WebSocket 监控")
async def check_stop_loss_take_profit(self) -> List[str]:
"""
@ -1965,7 +1973,7 @@ class PositionManager:
# 检查是否启用移动止损默认False需要显式启用
profit_protection_enabled = bool(config.TRADING_CONFIG.get('PROFIT_PROTECTION_ENABLED', True))
use_trailing = profit_protection_enabled and bool(config.TRADING_CONFIG.get('USE_TRAILING_STOP', False))
use_trailing = profit_protection_enabled and bool(config.TRADING_CONFIG.get('USE_TRAILING_STOP', True))
if use_trailing:
logger.debug(f"{symbol} [移动止损] 已启用,将检查移动止损逻辑")
else:
@ -1983,7 +1991,9 @@ class PositionManager:
trailing_activation = trailing_activation / 100.0
if trailing_protect > 1:
trailing_protect = trailing_protect / 100.0
lock_pct = config.TRADING_CONFIG.get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT') or 0
lock_pct = config.TRADING_CONFIG.get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT')
if lock_pct is None:
lock_pct = 0.03
if lock_pct and lock_pct > 1:
lock_pct = lock_pct / 100.0
@ -1997,10 +2007,14 @@ class PositionManager:
position_info['stopLoss'] = breakeven
position_info['breakevenStopSet'] = True
logger.info(f"{symbol} [定时检查] 盈利{pnl_percent_margin:.2f}%≥{lock_pct*100:.0f}%,止损已移至含手续费保本价 {breakeven:.4f}")
logger.info(f"{symbol} [定时检查] 尝试将保本止损同步至交易所")
try:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
except Exception as sync_e:
logger.warning(f"{symbol} 同步保本止损至交易所失败: {sync_e}")
logger.warning(
f"{symbol} [定时检查] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
# 盈利超过阈值后(相对于保证金),激活移动止损
if pnl_percent_margin > trailing_activation * 100:
position_info['trailingStopActivated'] = True
@ -2010,11 +2024,15 @@ class PositionManager:
f"{symbol} 移动止损激活: 止损移至含手续费保本价 {breakeven:.4f} (入场: {entry_price:.4f}) "
f"(盈利: {pnl_percent_margin:.2f}% of margin)"
)
logger.info(f"{symbol} [定时检查] 尝试将移动止损同步至交易所")
try:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
logger.warning(
f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
else:
# 盈利超过阈值后,止损移至保护利润位(基于保证金)
# 如果已经部分止盈,使用剩余仓位计算
@ -2041,7 +2059,10 @@ class PositionManager:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
logger.warning(
f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
else:
new_stop_loss = entry_price + (remaining_pnl - protect_amount) / remaining_quantity
new_stop_loss = min(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL'))
@ -2057,7 +2078,10 @@ class PositionManager:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
logger.warning(
f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
else:
# 未部分止盈,使用原始仓位计算;保护金额至少覆盖手续费
protect_amount = max(margin * trailing_protect, self._min_protect_amount_for_fees(margin, leverage))
@ -2075,7 +2099,10 @@ class PositionManager:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
logger.warning(
f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
else:
# 做空:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量
# 注意:对于做空,止损价应该高于开仓价,所以用加法
@ -2094,8 +2121,11 @@ class PositionManager:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price)
logger.info(f"{symbol} [定时检查] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败: {sync_e}")
logger.warning(
f"{symbol} [定时检查] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
# 检查止损(使用更新后的止损价,基于保证金收益比)
# ⚠️ 重要:止损检查应该在时间锁之前,止损必须立即执行
stop_loss_raw = position_info.get('stopLoss')
@ -3982,9 +4012,9 @@ class PositionManager:
# 4) 及时执行止损/止盈可以保护资金和利润
# 注意如果需要防止秒级平仓可以通过提高入场信号质量MIN_SIGNAL_STRENGTH来实现
# 检查是否启用移动止损默认False需要显式启用
# 检查是否启用移动止损/保本(默认 True与 config.py 一致;显式设为 False 才关闭
profit_protection_enabled = bool(config.TRADING_CONFIG.get('PROFIT_PROTECTION_ENABLED', True))
use_trailing = profit_protection_enabled and bool(config.TRADING_CONFIG.get('USE_TRAILING_STOP', False))
use_trailing = profit_protection_enabled and bool(config.TRADING_CONFIG.get('USE_TRAILING_STOP', True))
if use_trailing:
logger.debug(f"[账号{self.account_id}] {symbol} [实时监控-移动止损] 已启用,将检查移动止损逻辑")
else:
@ -3993,9 +4023,11 @@ class PositionManager:
else:
logger.debug(f"[账号{self.account_id}] {symbol} [实时监控-移动止损] 已禁用USE_TRAILING_STOP=False跳过移动止损检查")
if use_trailing:
trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金
trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金
lock_pct = config.TRADING_CONFIG.get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT') or 0
trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.10) # 相对于保证金,默认 10%
trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.02) # 相对于保证金,默认 2%
lock_pct = config.TRADING_CONFIG.get('LOCK_PROFIT_AT_BREAKEVEN_AFTER_PCT')
if lock_pct is None:
lock_pct = 0.03 # 未配置时默认 3% 移至保本
if lock_pct and lock_pct > 1:
lock_pct = lock_pct / 100.0
@ -4016,10 +4048,14 @@ class PositionManager:
logger.info(
f"[账号{self.account_id}] {symbol} [实时监控] 盈利{pnl_percent_margin:.2f}%≥{lock_pct*100:.0f}%,止损已移至含手续费保本价 {breakeven:.4f}(留住盈利)"
)
logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 尝试将保本止损同步至交易所")
try:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
except Exception as sync_e:
logger.warning(f"{symbol} 同步保本止损至交易所失败: {sync_e}")
logger.warning(
f"[账号{self.account_id}] {symbol} [实时监控] 同步保本止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
# 盈利超过阈值后(相对于保证金),激活移动止损
if pnl_percent_margin > trailing_activation * 100:
position_info['trailingStopActivated'] = True
@ -4040,11 +4076,15 @@ class PositionManager:
f"(盈利: {pnl_percent_margin:.2f}% of margin, 保护: {trailing_protect*100:.1f}% of margin)"
)
# 同步至交易所:取消原止损单并按新止损价重挂,使移动止损也有交易所保护
logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 尝试将移动止损同步至交易所")
try:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败(不影响本地监控): {sync_e}")
logger.warning(
f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
else:
# ⚠️ 优化:如果分步止盈第一目标已触发,移动止损不再更新剩余仓位的止损价
# 原因分步止盈第一目标触发后剩余50%仓位止损已移至成本价(保本),等待第二目标
@ -4068,7 +4108,10 @@ class PositionManager:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败(不影响本地监控): {sync_e}")
logger.warning(
f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
else: # SELL
new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity
new_stop_loss = min(new_stop_loss, self._breakeven_stop_price(entry_price, 'SELL'))
@ -4082,7 +4125,10 @@ class PositionManager:
await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=current_price_float)
logger.info(f"[账号{self.account_id}] {symbol} [实时监控] 已同步移动止损至交易所")
except Exception as sync_e:
logger.warning(f"{symbol} 同步移动止损至交易所失败(不影响本地监控): {sync_e}")
logger.warning(
f"[账号{self.account_id}] {symbol} [实时监控] 同步移动止损至交易所失败: {type(sync_e).__name__}: {sync_e}",
exc_info=False,
)
# 检查止损(基于保证金收益比)
# ⚠️ 重要:止损检查应该在时间锁之前,止损必须立即执行