From 41b2a21c3d65b24c40fe6e2510452b0d81b95f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Wed, 25 Feb 2026 21:48:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(position=5Fmanager):=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E6=AD=A2=E6=8D=9F=E5=92=8C=E6=AD=A2=E7=9B=88=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=8F=8A=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在持仓管理中,优化了止损和止盈的同步逻辑,确保在同步失败时记录详细的异常信息。同时,增加了对止损和止盈为空的警告日志,提升了系统的可用性和风险控制能力。此外,调整了移动止损的配置逻辑,确保在未设置时使用默认值。这一改动旨在提升交易策略的稳定性与用户友好性。 --- trading_system/position_manager.py | 86 +++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index e80b693..797535e 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -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, + ) # 检查止损(基于保证金收益比) # ⚠️ 重要:止损检查应该在时间锁之前,止损必须立即执行