# Centralized handler for Binance's terrible API design (effective Dec 9, 2025)
# Conditional orders (STOP_MARKET) moved to separate /algoOrder endpoints
# This class abstracts the mess so rest of code stays clean
class BinanceOrderManager:
"""Handles routing between regular and algo order APIs + normalization"""
CONDITIONAL_TYPES = {'STOP_MARKET'}
def init(self, client):
self.client = client
def isconditional(self, order_type):
"""Check if order type requires algo API"""
return order_type in self.CONDITIONAL_TYPES
def create_order(self, symbol, side, order_type, **params):
"""
Unified order creation - routes to correct API
Args:
symbol: Trading pair (e.g., 'BTCUSDT')
side: 'BUY' or 'SELL'
order_type: 'MARKET', 'LIMIT', 'STOP_MARKET', etc.
**params: Order parameters (quantity, price, stopPrice, etc.)
Returns:
Order response with normalized fields
"""
if self._is_conditional(order_type):
# Algo API: Map stopPrice → triggerPrice, add algoType
algo_params = {
'symbol': symbol,
'side': side,
'algoType': 'CONDITIONAL',
'quantity': params.get('quantity'),
'triggerPrice': params.get('stopPrice'), # KEY MAPPING
}
# Algo API doesn't support reduceOnly parameter - omit it
if 'timeInForce' in params:
algo_params['timeInForce'] = params['timeInForce']
if 'closePosition' in params:
algo_params['closePosition'] = params['closePosition']
response = self.client.futures_create_algo_order(**algo_params)
# Normalize response: algoId → orderId, triggerPrice → stopPrice
return {
'orderId': response.get('algoId'),
'algoId': response.get('algoId'),
'symbol': response.get('symbol'),
'type': order_type,
'side': response.get('side'),
'stopPrice': response.get('triggerPrice'),
'origQty': response.get('totalQty'),
'status': response.get('algoStatus'),
'isAlgoOrder': True
}
else:
# Regular API: Use as-is
response = self.client.futures_create_order(
symbol=symbol,
side=side,
type=order_type,
**params
)
response['isAlgoOrder'] = False
return response
def get_open_orders(self, symbol):
"""
Fetch ALL open orders (regular + algo) and normalize
Returns:
List of orders with unified structure (all have orderId, stopPrice, etc.)
"""
try:
# Fetch from both APIs
regular_orders = self.client.futures_get_open_orders(symbol=symbol)
algo_orders = self.client.futures_get_open_algo_orders(symbol=symbol)
# Normalize algo orders to match regular structure
normalized_algo = []
for order in algo_orders:
normalized_algo.append({
'orderId': order.get('algoId'),
'algoId': order.get('algoId'),
'symbol': order.get('symbol'),
'type': order.get('algoType', 'STOP_MARKET'),
'side': order.get('side'),
'price': order.get('price'),
'stopPrice': order.get('triggerPrice'), # KEY MAPPING
'origQty': order.get('totalQty'),
'executedQty': order.get('executedQty', 0),
'status': order.get('algoStatus'),
'timeInForce': order.get('timeInForce'),
'reduceOnly': order.get('reduceOnly', False),
'closePosition': order.get('closePosition', False),
'updateTime': order.get('updateTime'),
'isAlgoOrder': True
})
return regular_orders + normalized_algo
except:
return []
def cancel_order(self, symbol, order_id, is_algo=None):
"""
Cancel single order - auto-detects if algo order
Args:
symbol: Trading pair
order_id: Order ID (or algoId)
is_algo: If known, pass True/False. Otherwise auto-detect via API calls.
Returns:
Cancellation response
"""
if is_algo is None:
# Try regular first, fall back to algo
try:
return self.client.futures_cancel_order(symbol=symbol, orderId=order_id)
except:
return self.client.futures_cancel_algo_order(symbol=symbol, algoId=order_id)
elif is_algo:
return self.client.futures_cancel_algo_order(symbol=symbol, algoId=order_id)
else:
return self.client.futures_cancel_order(symbol=symbol, orderId=order_id)
def cancel_all_orders(self, symbol):
"""Cancel ALL orders (regular + algo) for symbol"""
try:
self.client.futures_cancel_all_open_orders(symbol=symbol)
self.client.futures_cancel_all_algo_open_orders(symbol=symbol)
return True
except Exception as e:
print(f"❌ Error cancelling orders: {e}")
return False