a
This commit is contained in:
parent
5abd1c340c
commit
f495815af6
|
|
@ -143,20 +143,11 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise HTTPException(status_code=400, detail=f"{symbol} 数据库缺少止损/止盈价,且无法回退计算,无法补挂")
|
raise HTTPException(status_code=400, detail=f"{symbol} 数据库缺少止损/止盈价,且无法回退计算,无法补挂")
|
||||||
|
|
||||||
# 5) 取消旧的保护单,避免重复
|
# 5) 取消旧的保护单(Algo 条件单),避免重复
|
||||||
try:
|
try:
|
||||||
orders = await client.client.futures_get_open_orders(symbol=symbol)
|
await client.cancel_open_algo_orders_by_order_types(
|
||||||
for o in orders or []:
|
symbol, {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}
|
||||||
try:
|
)
|
||||||
if not isinstance(o, dict):
|
|
||||||
continue
|
|
||||||
otype = str(o.get("type") or "").upper()
|
|
||||||
if otype in {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}:
|
|
||||||
oid = o.get("orderId")
|
|
||||||
if oid:
|
|
||||||
await client.client.futures_cancel_order(symbol=symbol, orderId=int(oid))
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -198,19 +189,22 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str):
|
||||||
tp_round = "DOWN" if side == "BUY" else "UP"
|
tp_round = "DOWN" if side == "BUY" else "UP"
|
||||||
|
|
||||||
close_side = "SELL" if side == "BUY" else "BUY"
|
close_side = "SELL" if side == "BUY" else "BUY"
|
||||||
|
# Algo 条件单使用 triggerPrice(不是 stopPrice)
|
||||||
sl_params = {
|
sl_params = {
|
||||||
|
"algoType": "CONDITIONAL",
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"side": close_side,
|
"side": close_side,
|
||||||
"type": "STOP_MARKET",
|
"type": "STOP_MARKET",
|
||||||
"stopPrice": _fmt(sl_price, sl_round),
|
"triggerPrice": _fmt(sl_price, sl_round),
|
||||||
"closePosition": True,
|
"closePosition": True,
|
||||||
"workingType": "MARK_PRICE",
|
"workingType": "MARK_PRICE",
|
||||||
}
|
}
|
||||||
tp_params = {
|
tp_params = {
|
||||||
|
"algoType": "CONDITIONAL",
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"side": close_side,
|
"side": close_side,
|
||||||
"type": "TAKE_PROFIT_MARKET",
|
"type": "TAKE_PROFIT_MARKET",
|
||||||
"stopPrice": _fmt(tp_price, tp_round),
|
"triggerPrice": _fmt(tp_price, tp_round),
|
||||||
"closePosition": True,
|
"closePosition": True,
|
||||||
"workingType": "MARK_PRICE",
|
"workingType": "MARK_PRICE",
|
||||||
}
|
}
|
||||||
|
|
@ -218,31 +212,30 @@ async def _ensure_exchange_sltp_for_symbol(symbol: str):
|
||||||
sl_params["positionSide"] = "LONG" if side == "BUY" else "SHORT"
|
sl_params["positionSide"] = "LONG" if side == "BUY" else "SHORT"
|
||||||
tp_params["positionSide"] = "LONG" if side == "BUY" else "SHORT"
|
tp_params["positionSide"] = "LONG" if side == "BUY" else "SHORT"
|
||||||
|
|
||||||
sl_order = await client.client.futures_create_order(**sl_params)
|
sl_order = await client.futures_create_algo_order(sl_params)
|
||||||
tp_order = await client.client.futures_create_order(**tp_params)
|
tp_order = await client.futures_create_algo_order(tp_params)
|
||||||
|
|
||||||
# 再查一次未成交委托,确认是否真的挂上(并用于前端展示/排查)
|
# 再查一次未成交委托,确认是否真的挂上(并用于前端展示/排查)
|
||||||
open_orders = []
|
open_orders = []
|
||||||
try:
|
try:
|
||||||
oo = await client.client.futures_get_open_orders(symbol=symbol)
|
oo = await client.futures_get_open_algo_orders(symbol=symbol, algo_type="CONDITIONAL")
|
||||||
if isinstance(oo, list):
|
if isinstance(oo, list):
|
||||||
for o in oo:
|
for o in oo:
|
||||||
try:
|
try:
|
||||||
if not isinstance(o, dict):
|
if not isinstance(o, dict):
|
||||||
continue
|
continue
|
||||||
otype2 = str(o.get("type") or "").upper()
|
otype2 = str(o.get("orderType") or o.get("type") or "").upper()
|
||||||
if otype2 in {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET"}:
|
if otype2 in {"STOP_MARKET", "TAKE_PROFIT_MARKET", "TRAILING_STOP_MARKET", "STOP", "TAKE_PROFIT"}:
|
||||||
open_orders.append(
|
open_orders.append(
|
||||||
{
|
{
|
||||||
"orderId": o.get("orderId"),
|
"algoId": o.get("algoId"),
|
||||||
"type": otype2,
|
"orderType": otype2,
|
||||||
"side": o.get("side"),
|
"side": o.get("side"),
|
||||||
"stopPrice": o.get("stopPrice"),
|
"triggerPrice": o.get("triggerPrice"),
|
||||||
"price": o.get("price"),
|
|
||||||
"workingType": o.get("workingType"),
|
"workingType": o.get("workingType"),
|
||||||
"positionSide": o.get("positionSide"),
|
"positionSide": o.get("positionSide"),
|
||||||
"closePosition": o.get("closePosition"),
|
"closePosition": o.get("closePosition"),
|
||||||
"status": o.get("status"),
|
"algoStatus": o.get("algoStatus"),
|
||||||
"updateTime": o.get("updateTime"),
|
"updateTime": o.get("updateTime"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1298,6 +1298,45 @@ class BinanceClient:
|
||||||
logger.error(f"取消订单失败: {e}")
|
logger.error(f"取消订单失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Algo Orders(条件单/止盈止损/计划委托)
|
||||||
|
# 说明:币安在 2025-12 后将 USDT-M 合约的 STOP/TP/Trailing 等条件单迁移到 Algo 接口:
|
||||||
|
# - POST /fapi/v1/algoOrder
|
||||||
|
# - GET /fapi/v1/openAlgoOrders
|
||||||
|
# - DELETE /fapi/v1/algoOrder
|
||||||
|
# 如果仍用 /fapi/v1/order 下 STOP_MARKET/TAKE_PROFIT_MARKET + closePosition 会报 -4120。
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
async def futures_create_algo_order(self, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||||
|
try:
|
||||||
|
# python-binance 内部会自动补 timestamp / signature
|
||||||
|
res = await self.client._request_futures_api("post", "algoOrder", True, data=params)
|
||||||
|
return res if isinstance(res, dict) else None
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"{params.get('symbol')} 创建 Algo 条件单失败: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def futures_get_open_algo_orders(self, symbol: Optional[str] = None, algo_type: str = "CONDITIONAL") -> List[Dict[str, Any]]:
|
||||||
|
try:
|
||||||
|
data: Dict[str, Any] = {}
|
||||||
|
if symbol:
|
||||||
|
data["symbol"] = symbol
|
||||||
|
if algo_type:
|
||||||
|
data["algoType"] = algo_type
|
||||||
|
res = await self.client._request_futures_api("get", "openAlgoOrders", True, data=data)
|
||||||
|
return res if isinstance(res, list) else []
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"{symbol or ''} 获取 openAlgoOrders 失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def futures_cancel_algo_order(self, algo_id: int) -> bool:
|
||||||
|
try:
|
||||||
|
_ = await self.client._request_futures_api("delete", "algoOrder", True, data={"algoId": int(algo_id)})
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"取消 Algo 条件单失败 algoId={algo_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
async def get_open_orders(self, symbol: str) -> List[Dict]:
|
async def get_open_orders(self, symbol: str) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
获取某交易对的未成交委托(用于防止重复挂保护单)。
|
获取某交易对的未成交委托(用于防止重复挂保护单)。
|
||||||
|
|
@ -1336,6 +1375,33 @@ class BinanceClient:
|
||||||
except Exception:
|
except Exception:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
async def cancel_open_algo_orders_by_order_types(self, symbol: str, order_types: set[str]) -> int:
|
||||||
|
"""
|
||||||
|
取消指定类型的“Algo 条件单”(openAlgoOrders)。
|
||||||
|
返回取消数量。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
want = {str(t).upper() for t in (order_types or set())}
|
||||||
|
if not want:
|
||||||
|
return 0
|
||||||
|
orders = await self.futures_get_open_algo_orders(symbol=symbol, algo_type="CONDITIONAL")
|
||||||
|
cancelled = 0
|
||||||
|
for o in orders or []:
|
||||||
|
try:
|
||||||
|
if not isinstance(o, dict):
|
||||||
|
continue
|
||||||
|
otype = str(o.get("orderType") or o.get("type") or "").upper()
|
||||||
|
algo_id = o.get("algoId")
|
||||||
|
if algo_id and otype in want:
|
||||||
|
ok = await self.futures_cancel_algo_order(int(algo_id))
|
||||||
|
if ok:
|
||||||
|
cancelled += 1
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return cancelled
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
|
|
||||||
async def place_trigger_close_position_order(
|
async def place_trigger_close_position_order(
|
||||||
self,
|
self,
|
||||||
symbol: str,
|
symbol: str,
|
||||||
|
|
@ -1422,32 +1488,34 @@ class BinanceClient:
|
||||||
|
|
||||||
stop_price_str = self._format_price_str_with_rounding(sp, symbol_info, rounding_mode)
|
stop_price_str = self._format_price_str_with_rounding(sp, symbol_info, rounding_mode)
|
||||||
|
|
||||||
|
# Algo 条件单接口使用 triggerPrice(不是 stopPrice)
|
||||||
params: Dict[str, Any] = {
|
params: Dict[str, Any] = {
|
||||||
|
"algoType": "CONDITIONAL",
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"side": close_side,
|
"side": close_side,
|
||||||
"type": ttype,
|
"type": ttype,
|
||||||
"stopPrice": stop_price_str,
|
"triggerPrice": stop_price_str,
|
||||||
"closePosition": True,
|
|
||||||
"workingType": working_type,
|
"workingType": working_type,
|
||||||
|
"closePosition": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 对冲模式:必须指定 positionSide
|
# 对冲模式:必须指定 positionSide
|
||||||
if dual is True:
|
if dual is True:
|
||||||
params["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
|
params["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
|
||||||
|
|
||||||
try:
|
# 走 Algo Order 接口(避免 -4120)
|
||||||
return await self.client.futures_create_order(**params)
|
order = await self.futures_create_algo_order(params)
|
||||||
except BinanceAPIException as e:
|
if order:
|
||||||
code = getattr(e, "code", None)
|
return order
|
||||||
# -4061: positionSide 不匹配 -> 兜底切换一次
|
# 兜底:对冲/单向模式可能导致 positionSide 误判,尝试切换一次
|
||||||
if code == -4061:
|
if dual is True:
|
||||||
retry = dict(params)
|
retry = dict(params)
|
||||||
if "positionSide" in retry:
|
retry.pop("positionSide", None)
|
||||||
retry.pop("positionSide", None)
|
return await self.futures_create_algo_order(retry)
|
||||||
else:
|
else:
|
||||||
retry["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
|
retry = dict(params)
|
||||||
return await self.client.futures_create_order(**retry)
|
retry["positionSide"] = "LONG" if pd == "BUY" else "SHORT"
|
||||||
raise
|
return await self.futures_create_algo_order(retry)
|
||||||
|
|
||||||
except BinanceAPIException as e:
|
except BinanceAPIException as e:
|
||||||
# 常见:Order would immediately trigger(止损/止盈价不在正确一侧)
|
# 常见:Order would immediately trigger(止损/止盈价不在正确一侧)
|
||||||
|
|
|
||||||
|
|
@ -656,7 +656,7 @@ class PositionManager:
|
||||||
oid = info0.get(k)
|
oid = info0.get(k)
|
||||||
if oid:
|
if oid:
|
||||||
try:
|
try:
|
||||||
await self.client.cancel_order(symbol, int(oid))
|
await self.client.futures_cancel_algo_order(int(oid))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
info0.pop("exchangeSlOrderId", None)
|
info0.pop("exchangeSlOrderId", None)
|
||||||
|
|
@ -1090,7 +1090,7 @@ class PositionManager:
|
||||||
|
|
||||||
# 防重复:先取消旧的保护单(仅取消特定类型,避免误伤普通挂单)
|
# 防重复:先取消旧的保护单(仅取消特定类型,避免误伤普通挂单)
|
||||||
try:
|
try:
|
||||||
await self.client.cancel_open_orders_by_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:
|
||||||
|
|
@ -1114,11 +1114,12 @@ class PositionManager:
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
position_info["exchangeSlOrderId"] = sl_order.get("orderId") if isinstance(sl_order, dict) else None
|
# Algo 接口返回 algoId
|
||||||
|
position_info["exchangeSlOrderId"] = sl_order.get("algoId") if isinstance(sl_order, dict) else None
|
||||||
except Exception:
|
except Exception:
|
||||||
position_info["exchangeSlOrderId"] = None
|
position_info["exchangeSlOrderId"] = None
|
||||||
try:
|
try:
|
||||||
position_info["exchangeTpOrderId"] = tp_order.get("orderId") if isinstance(tp_order, dict) else None
|
position_info["exchangeTpOrderId"] = tp_order.get("algoId") if isinstance(tp_order, dict) else None
|
||||||
except Exception:
|
except Exception:
|
||||||
position_info["exchangeTpOrderId"] = None
|
position_info["exchangeTpOrderId"] = None
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user