From 8667c07134ef7ff3898f4e1fdbb74acdd1e9987c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Tue, 27 Jan 2026 15:56:59 +0800 Subject: [PATCH] a --- trading_system/position_manager.py | 85 ++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 3d2e18e..80c7c4b 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2147,20 +2147,38 @@ class PositionManager: pnl_percent = float(trade.get("pnl_percent", 0) or 0) - # 1. 优先检查止损价格匹配(提高容忍度到10%) - if sl is not None and entry_price_val > 0 and ep > 0: - sl_val = float(sl) - # 价格匹配:平仓价接近止损价 - if _close_to(ep, sl_val, max_pct=0.10): - exit_reason = "stop_loss" - # 方向匹配:BUY时平仓价 < 止损价,SELL时平仓价 > 止损价 - elif (trade.get("side") == "BUY" and ep < sl_val) or (trade.get("side") == "SELL" and ep > sl_val): - # 如果价格在止损方向,且亏损比例较大,更可能是止损触发 - if pnl_percent < -5.0: # 亏损超过5% + # ⚠️ 2026-01-27关键修复:优先检查盈亏情况,避免盈利单被错误标记为止损 + # 1. 优先检查盈亏情况 + if pnl_percent > 0: + # 盈利单:优先检查止盈价格匹配 + if tp is not None and _close_to(ep, float(tp), max_pct=0.10): + exit_reason = "take_profit" + elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.10): + exit_reason = "take_profit" + elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.10): + exit_reason = "take_profit" + # 如果盈利但没有匹配止盈价,可能是移动止损或手动平仓 + elif exit_reason == "sync": + # 检查是否有移动止损标记 + if is_reduce_only: + exit_reason = "trailing_stop" # 可能是移动止损 + else: + exit_reason = "manual" # 可能是手动平仓 + else: + # 亏损单:检查止损价格匹配 + if sl is not None and entry_price_val > 0 and ep > 0: + sl_val = float(sl) + # 价格匹配:平仓价接近止损价 + if _close_to(ep, sl_val, max_pct=0.10): exit_reason = "stop_loss" - logger.info(f"{trade.get('symbol')} [同步] 价格方向匹配止损,且亏损{pnl_percent:.2f}%,标记为止损") + # 方向匹配:BUY时平仓价 < 止损价,SELL时平仓价 > 止损价 + elif (trade.get("side") == "BUY" and ep < sl_val) or (trade.get("side") == "SELL" and ep > sl_val): + # 如果价格在止损方向,且亏损比例较大,更可能是止损触发 + if pnl_percent < -5.0: # 亏损超过5% + exit_reason = "stop_loss" + logger.info(f"{trade.get('symbol')} [同步] 价格方向匹配止损,且亏损{pnl_percent:.2f}%,标记为止损") - # 2. 检查止盈价格匹配 + # 2. 如果仍未确定,检查止盈价格匹配(作为备选) if exit_reason == "sync" and ep > 0: if tp is not None and _close_to(ep, float(tp), max_pct=0.10): exit_reason = "take_profit" @@ -2732,11 +2750,27 @@ class PositionManager: # 盈利超过阈值后(相对于保证金),激活移动止损 if pnl_percent_margin > trailing_activation * 100: position_info['trailingStopActivated'] = True - # 将止损移至成本价(保本) - position_info['stopLoss'] = entry_price + # ⚠️ 2026-01-27修复:移动止损激活时,不应该将止损移至成本价 + # 应该设置为"保护利润"的价格(如盈利5%后,保护2.5%利润) + # 计算需要保护的利润金额 + protect_amount = margin * trailing_protect + # 计算对应的止损价(保护利润) + if position_info['side'] == 'BUY': + # 保护利润:当前盈亏 - 保护金额 = (止损价 - 开仓价) × 数量 + # 所以:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量 + new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity + # 确保止损价不低于成本价(保本) + new_stop_loss = max(new_stop_loss, entry_price) + else: # SELL + # 做空:止损价 = 开仓价 + (当前盈亏 - 保护金额) / 数量 + new_stop_loss = entry_price + (pnl_amount - protect_amount) / quantity + # 确保止损价不高于成本价(保本) + new_stop_loss = min(new_stop_loss, entry_price) + + position_info['stopLoss'] = new_stop_loss logger.info( - f"{symbol} [实时监控] 移动止损激活: 止损移至成本价 {entry_price:.4f} " - f"(盈利: {pnl_percent_margin:.2f}% of margin)" + f"{symbol} [实时监控] 移动止损激活: 止损移至保护利润位 {new_stop_loss:.4f} " + f"(盈利: {pnl_percent_margin:.2f}% of margin, 保护: {trailing_protect*100:.1f}% of margin)" ) else: # ⚠️ 优化:如果分步止盈第一目标已触发,移动止损不再更新剩余仓位的止损价 @@ -2806,8 +2840,27 @@ class PositionManager: f"监控状态: {'运行中' if symbol in self._monitor_tasks else '未启动'}" ) + # ⚠️ 2026-01-27关键修复:止损检查前,先检查是否盈利 + # 如果盈利,不应该触发止损(除非是移动止损或分步止盈后的剩余仓位) # 直接比较当前盈亏百分比与止损目标(基于保证金) if pnl_percent_margin <= -stop_loss_pct_margin: + # ⚠️ 额外检查:如果盈利,可能是移动止损触发,应该标记为trailing_stop + if pnl_percent_margin > 0: + # 盈利单触发止损,应该是移动止损 + if position_info.get('trailingStopActivated'): + exit_reason_sl = 'trailing_stop' + logger.warning(f"{symbol} [实时监控] ⚠️ 盈利单触发止损,标记为移动止损(盈利: {pnl_percent_margin:.2f}% of margin)") + else: + # 盈利单但未激活移动止损,可能是分步止盈后的剩余仓位止损 + if partial_profit_taken: + exit_reason_sl = 'take_profit_partial_then_stop' + logger.info(f"{symbol} [实时监控] 第一目标止盈后,剩余仓位触发止损(保本)") + else: + # 异常情况:盈利单触发止损但未激活移动止损 + exit_reason_sl = 'trailing_stop' # 默认标记为移动止损 + logger.warning(f"{symbol} [实时监控] ⚠️ 异常:盈利单触发止损但未激活移动止损,标记为移动止损") + else: + # 正常止损逻辑 should_close_due_to_sl = True # ⚠️ 2026-01-27优化:如果已部分止盈,细分状态 if partial_profit_taken: