feat(position_manager): 增加从交易所读取止损和止盈价格的功能

在持仓管理中,新增 `_get_sltp_from_exchange` 方法以从币安获取当前的止损和止盈价格,确保在重启后不覆盖已有的保护。同时,优化了止损和止盈价格的设置逻辑,优先使用从交易所获取的值,提升风险控制能力和策略的灵活性。这一改动旨在增强系统的稳定性与用户友好性,确保交易策略的有效性。
This commit is contained in:
薇薇安 2026-02-25 21:18:00 +08:00
parent 1c33096917
commit d7ccbe38e4

View File

@ -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(