This commit is contained in:
薇薇安 2026-01-23 17:31:47 +08:00
parent 9e70f90260
commit efbd149f1f
2 changed files with 115 additions and 8 deletions

View File

@ -1327,8 +1327,38 @@ class BinanceClient:
# python-binance 内部会自动补 timestamp / signature # python-binance 内部会自动补 timestamp / signature
res = await self.client._request_futures_api("post", "algoOrder", True, data=params) res = await self.client._request_futures_api("post", "algoOrder", True, data=params)
return res if isinstance(res, dict) else None return res if isinstance(res, dict) else None
except BinanceAPIException as e:
error_code = e.code if hasattr(e, 'code') else None
error_msg = str(e)
symbol = params.get('symbol', 'UNKNOWN')
trigger_type = params.get('type', 'UNKNOWN')
# 详细错误日志
logger.error(f"{symbol} ❌ 创建 Algo 条件单失败({trigger_type}): {error_msg}")
logger.error(f" 错误代码: {error_code}")
logger.error(f" 参数: {params}")
# 常见错误码处理
if error_code == -4014:
logger.error(f" 原因: 价格步长错误triggerPrice 需要调整到 tickSize 的倍数")
elif error_code == -4164:
logger.error(f" 原因: 订单名义价值不足(至少需要 5 USDT")
elif error_code == -2022:
logger.error(f" 原因: ReduceOnly 订单被拒绝(可能没有持仓或持仓方向不对)")
elif error_code == -4120:
logger.error(f" 原因: 不支持的条件单类型(可能需要使用 Algo 接口)")
elif "immediately trigger" in error_msg.lower() or "would immediately trigger" in error_msg.lower():
logger.error(f" 原因: 触发价格会导致立即触发(止损/止盈价不在正确一侧)")
elif "position" in error_msg.lower():
logger.error(f" 原因: 持仓相关问题(可能没有持仓或持仓方向不匹配)")
return None
except Exception as e: except Exception as e:
logger.warning(f"{params.get('symbol')} 创建 Algo 条件单失败: {e}") symbol = params.get('symbol', 'UNKNOWN')
logger.error(f"{symbol} ❌ 创建 Algo 条件单失败: {type(e).__name__}: {e}")
logger.error(f" 参数: {params}")
import traceback
logger.debug(f" 堆栈跟踪: {traceback.format_exc()}")
return None return None
async def futures_get_open_algo_orders(self, symbol: Optional[str] = None, algo_type: str = "CONDITIONAL") -> List[Dict[str, Any]]: async def futures_get_open_algo_orders(self, symbol: Optional[str] = None, algo_type: str = "CONDITIONAL") -> List[Dict[str, Any]]:
@ -1522,22 +1552,67 @@ class BinanceClient:
order = await self.futures_create_algo_order(params) order = await self.futures_create_algo_order(params)
if order: if order:
return order return order
# 兜底:对冲/单向模式可能导致 positionSide 误判,尝试切换一次 # 兜底:对冲/单向模式可能导致 positionSide 误判,尝试切换一次
logger.warning(f"{symbol} 首次挂保护单失败,尝试切换 positionSide 重试...")
if dual is True: if dual is True:
retry = dict(params) retry = dict(params)
retry.pop("positionSide", None) retry.pop("positionSide", None)
return await self.futures_create_algo_order(retry) logger.debug(f"{symbol} 重试1: 移除 positionSide对冲模式 -> 单向模式)")
retry_order = await self.futures_create_algo_order(retry)
if retry_order:
logger.info(f"{symbol} ✓ 重试成功(移除 positionSide")
return retry_order
else: else:
retry = dict(params) retry = dict(params)
retry["positionSide"] = "LONG" if pd == "BUY" else "SHORT" retry["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
return await self.futures_create_algo_order(retry) logger.debug(f"{symbol} 重试1: 添加 positionSide={retry['positionSide']}(单向模式 -> 对冲模式)")
retry_order = await self.futures_create_algo_order(retry)
if retry_order:
logger.info(f"{symbol} ✓ 重试成功(添加 positionSide")
return retry_order
# 如果还是失败,记录详细参数用于调试
logger.error(f"{symbol} ❌ 所有重试都失败,保护单挂单失败")
logger.error(f" 参数: {params}")
logger.error(f" 对冲模式: {dual}")
return None
except BinanceAPIException as e: except BinanceAPIException as e:
# 常见Order would immediately trigger止损/止盈价不在正确一侧) error_code = e.code if hasattr(e, 'code') else None
logger.warning(f"{symbol} 挂保护单失败({trigger_type}): {e}") error_msg = str(e)
# 详细错误日志
logger.error(f"{symbol} ❌ 挂保护单失败({trigger_type}): {error_msg}")
logger.error(f" 错误代码: {error_code}")
logger.error(f" 触发价格: {stop_price:.8f} (格式化后: {stop_price_str})")
logger.error(f" 当前价格: {cp if cp else 'N/A'}")
logger.error(f" 持仓方向: {pd}")
logger.error(f" 平仓方向: {close_side}")
logger.error(f" 工作类型: {working_type}")
if symbol_info:
logger.error(f" 价格精度: {pp}, 价格步长: {tick}")
# 常见错误码处理
if error_code == -4014:
logger.error(f" 原因: 价格步长错误,需要调整到 tickSize 的倍数")
elif error_code == -4164:
logger.error(f" 原因: 订单名义价值不足(至少需要 5 USDT")
elif error_code == -2022:
logger.error(f" 原因: ReduceOnly 订单被拒绝(可能没有持仓或持仓方向不对)")
elif "immediately trigger" in error_msg.lower() or "would immediately trigger" in error_msg.lower():
logger.error(f" 原因: 触发价格会导致立即触发(止损/止盈价不在正确一侧)")
logger.error(f" 建议: 检查止损/止盈价格计算是否正确")
elif "position" in error_msg.lower():
logger.error(f" 原因: 持仓相关问题(可能没有持仓或持仓方向不匹配)")
return None return None
except Exception as e: except Exception as e:
logger.warning(f"{symbol} 挂保护单失败({trigger_type}): {e}") logger.error(f"{symbol} ❌ 挂保护单失败({trigger_type}): {type(e).__name__}: {e}")
logger.error(f" 触发价格: {stop_price:.8f}")
logger.error(f" 持仓方向: {pd}")
import traceback
logger.debug(f" 堆栈跟踪: {traceback.format_exc()}")
return None return None
async def set_leverage(self, symbol: str, leverage: int = 10) -> bool: async def set_leverage(self, symbol: str, leverage: int = 10) -> bool:

View File

@ -1131,15 +1131,42 @@ class PositionManager:
take_profit = None take_profit = None
if not stop_loss or not take_profit: if not stop_loss or not take_profit:
logger.warning(f"{symbol} 止损或止盈价格为空,跳过挂保护单: stop_loss={stop_loss}, take_profit={take_profit}")
return return
# 验证止损价格是否合理
entry_price = position_info.get("entryPrice")
if entry_price:
try:
entry_price_val = float(entry_price)
stop_loss_val = float(stop_loss)
# 验证止损价格方向BUY时止损价应低于入场价SELL时止损价应高于入场价
if side == "BUY" and stop_loss_val >= entry_price_val:
logger.error(f"{symbol} ❌ 止损价格错误: BUY时止损价({stop_loss_val:.8f})应低于入场价({entry_price_val:.8f})")
return
if side == "SELL" and stop_loss_val <= entry_price_val:
logger.error(f"{symbol} ❌ 止损价格错误: SELL时止损价({stop_loss_val:.8f})应高于入场价({entry_price_val:.8f})")
return
except Exception as e:
logger.warning(f"{symbol} 验证止损价格时出错: {e}")
# 防重复:先取消旧的保护单(仅取消特定类型,避免误伤普通挂单) # 防重复:先取消旧的保护单(仅取消特定类型,避免误伤普通挂单)
try: try:
await self.client.cancel_open_algo_orders_by_order_types( await self.client.cancel_open_algo_orders_by_order_types(
symbol, {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"} symbol, {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}
) )
except Exception: except Exception as e:
pass logger.debug(f"{symbol} 取消旧保护单时出错(可忽略): {e}")
# 获取当前价格(如果未提供)
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}")
except Exception as e:
logger.warning(f"{symbol} 获取当前价格失败: {e}")
sl_order = await self.client.place_trigger_close_position_order( sl_order = await self.client.place_trigger_close_position_order(
symbol=symbol, symbol=symbol,
@ -1153,6 +1180,11 @@ class PositionManager:
logger.info(f"{symbol} ✓ 止损单已成功挂到交易所: {sl_order.get('algoId', 'N/A')}") logger.info(f"{symbol} ✓ 止损单已成功挂到交易所: {sl_order.get('algoId', 'N/A')}")
else: else:
logger.error(f"{symbol} ❌ 止损单挂单失败将依赖WebSocket监控但可能无法及时止损") logger.error(f"{symbol} ❌ 止损单挂单失败将依赖WebSocket监控但可能无法及时止损")
logger.error(f" 止损价格: {stop_loss:.8f}")
logger.error(f" 当前价格: {current_price if current_price else 'N/A'}")
logger.error(f" 持仓方向: {side}")
logger.error(f" ⚠️ 警告: 没有交易所级别的止损保护,如果系统崩溃或网络中断,可能无法及时止损!")
logger.error(f" 💡 建议: 检查止损价格计算是否正确,或手动在币安界面设置止损")
tp_order = await self.client.place_trigger_close_position_order( tp_order = await self.client.place_trigger_close_position_order(
symbol=symbol, symbol=symbol,