diff --git a/trading_system/binance_client.py b/trading_system/binance_client.py index 7028b0d..75e779e 100644 --- a/trading_system/binance_client.py +++ b/trading_system/binance_client.py @@ -1552,22 +1552,42 @@ class BinanceClient: if sp <= 0: return None - # 触发方向约束: - # - long 止损:价格 <= stopPrice(stopPrice 应 < current) - # - short 止损:价格 >= stopPrice(stopPrice 应 > current) - # - long 止盈:价格 >= stopPrice(stopPrice 应 > current) - # - short 止盈:价格 <= stopPrice(stopPrice 应 < current) + # 触发方向约束(避免立即触发): + # - long 止损:价格 <= stopPrice(stopPrice 应 < current,至少差一个 min_step) + # - short 止损:价格 >= stopPrice(stopPrice 应 > current,至少差一个 min_step) + # - long 止盈:价格 >= stopPrice(stopPrice 应 > current,至少差一个 min_step) + # - short 止盈:价格 <= stopPrice(stopPrice 应 < current,至少差一个 min_step) if cp and cp > 0: if ttype == "STOP_MARKET": - if pd == "BUY" and sp >= cp: - sp = max(0.0, cp - min_step) - if pd == "SELL" and sp <= cp: - sp = cp + min_step + if pd == "BUY": + # 做多止损:止损价必须 < 当前价,至少差一个 min_step + if sp >= cp: + # 如果止损价 >= 当前价,调整为当前价 - min_step(但这样止损太紧,可能不合理) + # 更好的做法是:如果止损价太接近当前价,增加一个安全距离(例如 0.5%) + safety_margin = max(min_step, cp * 0.005) # 至少 0.5% 的安全距离 + sp = max(0.0, cp - safety_margin) + logger.warning(f"{symbol} [止损修正] BUY止损价({sp:.8f})太接近当前价({cp:.8f}),调整为 {sp:.8f}") + elif pd == "SELL": + # 做空止损:止损价必须 > 当前价,至少差一个 min_step + if sp <= cp: + # 如果止损价 <= 当前价,调整为当前价 + min_step(但这样止损太紧,可能不合理) + # 更好的做法是:如果止损价太接近当前价,增加一个安全距离(例如 0.5%) + safety_margin = max(min_step, cp * 0.005) # 至少 0.5% 的安全距离 + sp = cp + safety_margin + logger.warning(f"{symbol} [止损修正] SELL止损价({sp:.8f})太接近当前价({cp:.8f}),调整为 {sp:.8f}") if ttype == "TAKE_PROFIT_MARKET": - if pd == "BUY" and sp <= cp: - sp = cp + min_step - if pd == "SELL" and sp >= cp: - sp = max(0.0, cp - min_step) + if pd == "BUY": + # 做多止盈:止盈价必须 > 当前价,至少差一个 min_step + if sp <= cp: + safety_margin = max(min_step, cp * 0.005) + sp = cp + safety_margin + logger.warning(f"{symbol} [止盈修正] BUY止盈价({sp:.8f})太接近当前价({cp:.8f}),调整为 {sp:.8f}") + elif pd == "SELL": + # 做空止盈:止盈价必须 < 当前价,至少差一个 min_step + if sp >= cp: + safety_margin = max(min_step, cp * 0.005) + sp = max(0.0, cp - safety_margin) + logger.warning(f"{symbol} [止盈修正] SELL止盈价({sp:.8f})太接近当前价({cp:.8f}),调整为 {sp:.8f}") # rounding 规则(提高命中概率,避免“显示等于入场价”的误差带来立即触发/永不触发): # 止损:long 用 UP(更靠近当前价),short 用 DOWN diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 79057b5..8b0ca4a 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -1158,24 +1158,107 @@ class PositionManager: except Exception as e: logger.debug(f"{symbol} 取消旧保护单时出错(可忽略): {e}") - # 获取当前价格(如果未提供) + # 获取当前价格(如果未提供,优先使用标记价格 MARK_PRICE,因为止损单使用 MARK_PRICE) if current_price is None: try: - ticker = await self.client.get_ticker_24h(symbol) - if ticker: - current_price = ticker.get('price') - logger.debug(f"{symbol} 获取当前价格: {current_price}") + # 优先获取标记价格(MARK_PRICE),因为止损单使用 MARK_PRICE 作为触发基准 + positions = await self.client.get_open_positions() + position = next((p for p in positions if p['symbol'] == symbol), None) + if position: + mark_price = position.get('markPrice') + if mark_price and float(mark_price) > 0: + current_price = float(mark_price) + logger.debug(f"{symbol} 从持仓获取标记价格: {current_price}") + else: + # 如果没有标记价格,使用 ticker 价格 + ticker = await self.client.get_ticker_24h(symbol) + if ticker: + current_price = ticker.get('price') + logger.debug(f"{symbol} 从ticker获取当前价格: {current_price}") + else: + # 如果没有持仓,使用 ticker 价格 + ticker = await self.client.get_ticker_24h(symbol) + if ticker: + current_price = ticker.get('price') + logger.debug(f"{symbol} 从ticker获取当前价格: {current_price}") except Exception as e: logger.warning(f"{symbol} 获取当前价格失败: {e}") + + # 如果仍然没有当前价格,记录警告 + if current_price is None: + logger.warning(f"{symbol} ⚠️ 无法获取当前价格,止损单可能无法正确验证触发条件") - sl_order = await self.client.place_trigger_close_position_order( - symbol=symbol, - position_direction=side, - trigger_type="STOP_MARKET", - stop_price=stop_loss, - current_price=current_price, - working_type="MARK_PRICE", - ) + # 在挂止损单前,检查当前价格是否已经触发止损(避免 -2021 错误) + if current_price and stop_loss: + try: + current_price_val = float(current_price) + stop_loss_val = float(stop_loss) + entry_price_val = float(entry_price) if entry_price else None + + # 检查是否已经触发止损 + if side == "BUY": + # 做多:当前价 <= 止损价,说明已触发止损 + if current_price_val <= stop_loss_val: + logger.error(f"{symbol} ⚠️ 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f}),无法挂止损单,应该立即平仓!") + logger.error(f" 入场价: {entry_price_val:.8f if entry_price_val else 'N/A'}") + logger.error(f" 建议: 立即手动平仓或等待WebSocket监控触发平仓") + # 不挂止损单,依赖WebSocket监控立即平仓 + sl_order = None + else: + sl_order = await self.client.place_trigger_close_position_order( + symbol=symbol, + position_direction=side, + trigger_type="STOP_MARKET", + stop_price=stop_loss, + current_price=current_price, + working_type="MARK_PRICE", + ) + elif side == "SELL": + # 做空:当前价 >= 止损价,说明已触发止损 + if current_price_val >= stop_loss_val: + logger.error(f"{symbol} ⚠️ 当前价格({current_price_val:.8f})已触发止损价({stop_loss_val:.8f}),无法挂止损单,应该立即平仓!") + logger.error(f" 入场价: {entry_price_val:.8f if entry_price_val else 'N/A'}") + logger.error(f" 建议: 立即手动平仓或等待WebSocket监控触发平仓") + # 不挂止损单,依赖WebSocket监控立即平仓 + sl_order = None + else: + sl_order = await self.client.place_trigger_close_position_order( + symbol=symbol, + position_direction=side, + trigger_type="STOP_MARKET", + stop_price=stop_loss, + current_price=current_price, + working_type="MARK_PRICE", + ) + else: + sl_order = await self.client.place_trigger_close_position_order( + symbol=symbol, + position_direction=side, + trigger_type="STOP_MARKET", + stop_price=stop_loss, + current_price=current_price, + working_type="MARK_PRICE", + ) + except Exception as e: + logger.warning(f"{symbol} 检查止损触发条件时出错: {e},继续尝试挂单") + sl_order = await self.client.place_trigger_close_position_order( + symbol=symbol, + position_direction=side, + trigger_type="STOP_MARKET", + stop_price=stop_loss, + current_price=current_price, + working_type="MARK_PRICE", + ) + else: + sl_order = await self.client.place_trigger_close_position_order( + symbol=symbol, + position_direction=side, + trigger_type="STOP_MARKET", + stop_price=stop_loss, + current_price=current_price, + working_type="MARK_PRICE", + ) + if sl_order: logger.info(f"{symbol} ✓ 止损单已成功挂到交易所: {sl_order.get('algoId', 'N/A')}") else: