From d7ccbe38e47c3c4ba1369ab48c7bdb5ff2bd54be 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:18:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(position=5Fmanager):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BB=8E=E4=BA=A4=E6=98=93=E6=89=80=E8=AF=BB=E5=8F=96=E6=AD=A2?= =?UTF-8?q?=E6=8D=9F=E5=92=8C=E6=AD=A2=E7=9B=88=E4=BB=B7=E6=A0=BC=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在持仓管理中,新增 `_get_sltp_from_exchange` 方法以从币安获取当前的止损和止盈价格,确保在重启后不覆盖已有的保护。同时,优化了止损和止盈价格的设置逻辑,优先使用从交易所获取的值,提升风险控制能力和策略的灵活性。这一改动旨在增强系统的稳定性与用户友好性,确保交易策略的有效性。 --- trading_system/position_manager.py | 88 +++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index a030258..e80b693 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -1350,6 +1350,41 @@ class PositionManager: pass return False + async def _get_sltp_from_exchange(self, symbol: str, side: str) -> tuple: + """从币安条件单读取当前止损价、止盈价,便于重启后不覆盖已有保护。(stop_price, take_profit_price),缺的为 None。""" + sl_price, tp_price = None, None + try: + for o in (await self.client.get_open_orders(symbol)) or []: + t = str(o.get("type") or "").upper() + sp = o.get("stopPrice") or o.get("triggerPrice") + try: + p = float(sp) if sp is not None else None + except (TypeError, ValueError): + p = None + if p is None: + continue + if t in ("STOP_MARKET", "STOP"): + sl_price = p + elif t in ("TAKE_PROFIT_MARKET", "TAKE_PROFIT"): + tp_price = p + # 条件单多在 CONDITIONAL 里 + for o in (await self.client.futures_get_open_algo_orders(symbol, algo_type="CONDITIONAL")) or []: + t = str(o.get("orderType") or o.get("type") or "").upper() + sp = o.get("stopPrice") or o.get("triggerPrice") or o.get("tp") + try: + p = float(sp) if sp is not None else None + except (TypeError, ValueError): + p = None + if p is None: + continue + if t in ("STOP_MARKET", "STOP"): + sl_price = p + elif t in ("TAKE_PROFIT_MARKET", "TAKE_PROFIT"): + tp_price = p + except Exception as e: + logger.debug(f"{symbol} 从交易所读取止损/止盈失败: {e}") + return (sl_price, tp_price) + def _breakeven_stop_price(self, entry_price: float, side: str, fee_buffer_pct: Optional[float] = None) -> float: """含手续费的保本止损价:做多=入场*(1+费率),做空=入场*(1-费率),平仓后不亏。""" if fee_buffer_pct is None: @@ -3611,26 +3646,33 @@ class PositionManager: except (ValueError, TypeError): leverage = 10.0 - stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08) - # ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式) - if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1: - stop_loss_pct_margin = stop_loss_pct_margin / 100.0 - take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15) - # ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式) - if take_profit_pct_margin is not None and take_profit_pct_margin > 1: - take_profit_pct_margin = take_profit_pct_margin / 100.0 - # 如果配置中没有设置止盈,则使用止损的2倍作为默认 - if take_profit_pct_margin is None or take_profit_pct_margin == 0: - take_profit_pct_margin = stop_loss_pct_margin * 2.0 - - stop_loss_price = self.risk_manager.get_stop_loss_price( - entry_price, side, quantity, leverage, - stop_loss_pct=stop_loss_pct_margin - ) - take_profit_price = self.risk_manager.get_take_profit_price( - entry_price, side, quantity, leverage, - take_profit_pct=take_profit_pct_margin - ) + # 优先从币安读取当前止损/止盈,避免用“初始止损”覆盖已有保本/移动止损 + sl_from_ex, tp_from_ex = await self._get_sltp_from_exchange(symbol, side) + if sl_from_ex is not None or tp_from_ex is not None: + stop_loss_price = float(sl_from_ex) if sl_from_ex is not None else None + take_profit_price = float(tp_from_ex) if tp_from_ex is not None else None + else: + stop_loss_price = None + take_profit_price = None + if stop_loss_price is None or take_profit_price is None: + stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08) + if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1: + stop_loss_pct_margin = stop_loss_pct_margin / 100.0 + take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15) + if take_profit_pct_margin is not None and take_profit_pct_margin > 1: + take_profit_pct_margin = take_profit_pct_margin / 100.0 + if take_profit_pct_margin is None or take_profit_pct_margin == 0: + take_profit_pct_margin = (stop_loss_pct_margin or 0.05) * 2.0 + if stop_loss_price is None: + stop_loss_price = self.risk_manager.get_stop_loss_price( + entry_price, side, quantity, leverage, + stop_loss_pct=stop_loss_pct_margin or 0.05 + ) + if take_profit_price is None: + take_profit_price = self.risk_manager.get_take_profit_price( + entry_price, side, quantity, leverage, + take_profit_pct=take_profit_pct_margin + ) entry_reason = 'manual_entry_temp' if sync_create_manual else 'sync_recovered' position_info = { @@ -3684,6 +3726,12 @@ class PositionManager: await self._ensure_exchange_sltp_orders(symbol, position_info, current_price=mp or current_price) except Exception as e: logger.warning(f"{symbol} 补挂币安止盈止损失败(不影响监控): {e}") + # 重启后立即按当前价做一次保本/移动止损检查并同步,不依赖首条 WS 推送 + try: + _px = float(position.get("markPrice", 0) or 0) or float(current_price or entry_price) + await self._check_single_position(symbol, _px) + except Exception as e: + logger.debug(f"{symbol} 启动时保本/移动止损检查失败: {e}") except Exception as e: err_msg = str(e) or repr(e) or type(e).__name__ logger.error(