Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions binance/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4024,9 +4024,33 @@ async def ws_futures_create_order(self, **params):
Send in a new order
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api
"""
if "newClientOrderId" not in params:
params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22()
return await self._ws_futures_api_request("order.place", True, params)
# Check if this is a conditional order type that needs to use algo endpoint
order_type = params.get("type", "").upper()
conditional_types = [
"STOP",
"STOP_MARKET",
"TAKE_PROFIT",
"TAKE_PROFIT_MARKET",
"TRAILING_STOP_MARKET",
]

if order_type in conditional_types:
# Route to algo order endpoint
if "clientAlgoId" not in params:
params["clientAlgoId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22()
# Remove newClientOrderId if it was added by default
params.pop("newClientOrderId", None)
if "algoType" not in params:
params["algoType"] = "CONDITIONAL"
# Convert stopPrice to triggerPrice for algo orders
if "stopPrice" in params and "triggerPrice" not in params:
params["triggerPrice"] = params.pop("stopPrice")
return await self._ws_futures_api_request("algoOrder.place", True, params)
else:
# Use regular order endpoint
if "newClientOrderId" not in params:
params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22()
return await self._ws_futures_api_request("order.place", True, params)

async def ws_futures_edit_order(self, **params):
"""
Expand All @@ -4040,12 +4064,21 @@ async def ws_futures_cancel_order(self, **params):
cancel an order
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Cancel-Order
"""
return await self._ws_futures_api_request("order.cancel", True, params)
is_conditional = False
if "algoId" in params or "clientAlgoId" in params:
is_conditional = True

if is_conditional:
return await self._ws_futures_api_request("algoOrder.cancel", True, params)
else:
return await self._ws_futures_api_request("order.cancel", True, params)

async def ws_futures_get_order(self, **params):
"""
Get an order
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Query-Order

Note: Algo/conditional orders cannot be queried via websocket API
"""
return await self._ws_futures_api_request("order.status", True, params)

Expand Down
41 changes: 37 additions & 4 deletions binance/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13862,9 +13862,33 @@ def ws_futures_create_order(self, **params):
Send in a new order
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api
"""
if "newClientOrderId" not in params:
params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22()
return self._ws_futures_api_request_sync("order.place", True, params)
# Check if this is a conditional order type that needs to use algo endpoint
order_type = params.get("type", "").upper()
conditional_types = [
"STOP",
"STOP_MARKET",
"TAKE_PROFIT",
"TAKE_PROFIT_MARKET",
"TRAILING_STOP_MARKET",
]

if order_type in conditional_types:
# Route to algo order endpoint
if "clientAlgoId" not in params:
params["clientAlgoId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22()
# Remove newClientOrderId if it was added by default
params.pop("newClientOrderId", None)
if "algoType" not in params:
params["algoType"] = "CONDITIONAL"
# Convert stopPrice to triggerPrice for algo orders
if "stopPrice" in params and "triggerPrice" not in params:
params["triggerPrice"] = params.pop("stopPrice")
return self._ws_futures_api_request_sync("algoOrder.place", True, params)
else:
# Use regular order endpoint
if "newClientOrderId" not in params:
params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22()
return self._ws_futures_api_request_sync("order.place", True, params)

def ws_futures_edit_order(self, **params):
"""
Expand All @@ -13878,12 +13902,21 @@ def ws_futures_cancel_order(self, **params):
cancel an order
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Cancel-Order
"""
return self._ws_futures_api_request_sync("order.cancel", True, params)
is_conditional = False
if "algoId" in params or "clientAlgoId" in params:
is_conditional = True

if is_conditional:
return self._ws_futures_api_request_sync("algoOrder.cancel", True, params)
else:
return self._ws_futures_api_request_sync("order.cancel", True, params)

def ws_futures_get_order(self, **params):
"""
Get an order
https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Query-Order

Note: Algo/conditional orders cannot be queried via websocket API
"""
return self._ws_futures_api_request_sync("order.status", True, params)

Expand Down
60 changes: 60 additions & 0 deletions tests/test_async_client_ws_futures_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,63 @@ async def test_ws_futures_create_cancel_algo_order(futuresClientAsync):
)

assert cancel_result["algoId"] == order["algoId"]


@pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")
@pytest.mark.asyncio()
async def test_ws_futures_create_conditional_order_auto_routing(futuresClientAsync):
"""Test that conditional order types are automatically routed to algo endpoint"""
ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT")
positions = await futuresClientAsync.ws_futures_v2_account_position(symbol="LTCUSDT")

# Create a STOP_MARKET order using ws_futures_create_order
# It should automatically route to the algo endpoint
# Use a price above current market price for BUY STOP
trigger_price = float(ticker["askPrice"]) * 1.5
order = await futuresClientAsync.ws_futures_create_order(
symbol=ticker["symbol"],
side="BUY",
positionSide=positions[0]["positionSide"],
type="STOP_MARKET",
quantity=1,
triggerPrice=trigger_price,
)

assert order["symbol"] == ticker["symbol"]
assert "algoId" in order
assert order["algoType"] == "CONDITIONAL"

# Cancel the order using algoId
cancel_result = await futuresClientAsync.ws_futures_cancel_order(
symbol=ticker["symbol"], algoId=order["algoId"]
)
assert cancel_result["algoId"] == order["algoId"]


@pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")
@pytest.mark.asyncio()
async def test_ws_futures_conditional_order_with_stop_price(futuresClientAsync):
"""Test that stopPrice is converted to triggerPrice for conditional orders"""
ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT")
positions = await futuresClientAsync.ws_futures_v2_account_position(symbol="LTCUSDT")

# Create a TAKE_PROFIT_MARKET order with stopPrice (should be converted to triggerPrice)
# Use a price above current market price for SELL TAKE_PROFIT
trigger_price = float(ticker["askPrice"]) * 1.5
order = await futuresClientAsync.ws_futures_create_order(
symbol=ticker["symbol"],
side="SELL",
positionSide=positions[0]["positionSide"],
type="TAKE_PROFIT_MARKET",
quantity=1,
stopPrice=trigger_price, # This should be converted to triggerPrice
)

assert order["symbol"] == ticker["symbol"]
assert "algoId" in order
assert order["algoType"] == "CONDITIONAL"

# Cancel the order
await futuresClientAsync.ws_futures_cancel_order(
symbol=ticker["symbol"], algoId=order["algoId"]
)
55 changes: 55 additions & 0 deletions tests/test_client_ws_futures_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,58 @@ def test_ws_futures_create_cancel_algo_order(futuresClient):
)

assert cancel_result["algoId"] == order["algoId"]


@pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")
def test_ws_futures_create_conditional_order_auto_routing(futuresClient):
"""Test that conditional order types are automatically routed to algo endpoint"""
ticker = futuresClient.ws_futures_get_order_book_ticker(symbol="LTCUSDT")
positions = futuresClient.ws_futures_v2_account_position(symbol="LTCUSDT")

trigger_price = float(ticker["askPrice"]) * 1.5
order = futuresClient.ws_futures_create_order(
symbol=ticker["symbol"],
side="BUY",
positionSide=positions[0]["positionSide"],
type="STOP_MARKET",
quantity=1,
triggerPrice=trigger_price,
)

assert order["symbol"] == ticker["symbol"]
assert "algoId" in order
assert order["algoType"] == "CONDITIONAL"

# Cancel using algoId parameter
cancel_result = futuresClient.ws_futures_cancel_order(
symbol=ticker["symbol"], algoId=order["algoId"]
)
assert cancel_result["algoId"] == order["algoId"]


@pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")
def test_ws_futures_conditional_order_with_stop_price(futuresClient):
"""Test that stopPrice is converted to triggerPrice for conditional orders"""
ticker = futuresClient.ws_futures_get_order_book_ticker(symbol="LTCUSDT")
positions = futuresClient.ws_futures_v2_account_position(symbol="LTCUSDT")

# Create a TAKE_PROFIT_MARKET order with stopPrice (should be converted to triggerPrice)
# Use a price above current market price for SELL TAKE_PROFIT
trigger_price = float(ticker["askPrice"]) * 1.5
order = futuresClient.ws_futures_create_order(
symbol=ticker["symbol"],
side="SELL",
positionSide=positions[0]["positionSide"],
type="TAKE_PROFIT_MARKET",
quantity=1,
stopPrice=trigger_price, # This should be converted to triggerPrice
)

assert order["symbol"] == ticker["symbol"]
assert "algoId" in order
assert order["algoType"] == "CONDITIONAL"

# Cancel the order
futuresClient.ws_futures_cancel_order(
symbol=ticker["symbol"], algoId=order["algoId"]
)
Loading