Python Binance Asyncio Maker Order/Trade With Web Socket Stream

May 3, 2021

This example will use

NOTE:

  • This sample will attemp to create marker order (usually with lower fee), thus need to handle complex scenario like order would immediately match (maker order becomes taker order), order would not match after certain duration (price too high or too low) and order partially match.
  • Binance REST API to query order status usually delay by 1 to 5 seconds, and could delay up to minutes during super peak session. Binance Websocket Stream is more reliable to get latest order status.

Setup

import asyncio
import binance_client

from unicorn_binance_websocket_api.unicorn_binance_websocket_api_manager import BinanceWebSocketApiManager

BINANCE_API_KEY = ...
BINANCE_SECRET_KEY = ...

running = True
find_orders = {}

Helper functions

async def create_taker_order(binance, symbol, side):
    # try to place marker order
    order_id = None
    while not order_id:
        prices = await binance.get_price(symbol=symbol)
        price = float(prices['price'])

        # adjust price lower/higher to prevent immediate match
        if side == 'buy':
            price *= 0.9995
        elif side == 'sell':
            price *= 1.0005

        # buy BUSD 12 worth of BTC
        quantity = 12 / price

        price = f"{price:.2f}"
        quantity = f"{ quantity:.6f}"
        print('price', price, 'quantity', quantity)

        try:
            data = await binance.order_limit_maker(symbol=symbol, side=side, quantity=quantity, price=price)
            order_id = data['orderId']
            print('order_id', order_id)
        except binance_client.BinanceException as e:
            if e.code == -2010 and e.msg == 'Order would immediately match and take.':
                await asyncio.sleep(1)
            else:
                raise e

    return order_id
async def check_order(binance, symbol, order_id):
    loop_count = 0
    while True:
        # try to get order status
        status = None
        while not status:
            try:
                # check websocket order data
                data = find_orders.get(order_id)
                if data:
                    print('order data from websocket')
                    data = data[-1] # get the latest update

                if not data:
                    # check order data via api
                    print('order data from api')
                    data = await binance.query_order(symbol=symbol, order_id=order_id)

                if data:
                    status = data['status']
                    print('status', status)
                else:
                    await asyncio.sleep(1)
            except binance_client.BinanceException as e:
                if e.code == -2013 and e.msg == 'Order does not exist.':
                    await asyncio.sleep(1)
                else:
                    raise e

        if status in ['FILLED', 'REJECTED', 'EXPIRED']: # 'CANCELED', 'PENDING_CANCEL', 
            print('success', status)
            return True


        loop_count += 1
        if loop_count > 10:
            if status in ['PARTIALLY_FILLED']:
                print('partial', status)
                return True

            data = await binance.cancel_order(symbol=symbol, order_id=order_id)
            status = data['status']
            print('cancel', status)
            return False

        await asyncio.sleep(1)

Main

async def binance_websocket():
    binance_websocket_api_manager = BinanceWebSocketApiManager()
    binance_websocket_api_manager.create_stream('arr', '!userData', api_key=BINANCE_API_KEY, stream_label="UnicornFy", output="UnicornFy")

    while running:
        if binance_websocket_api_manager.is_manager_stopping():
            break

        oldest_stream_data_from_stream_buffer = binance_websocket_api_manager.pop_stream_data_from_stream_buffer()

        if oldest_stream_data_from_stream_buffer:
            stream = oldest_stream_data_from_stream_buffer

            if 'event_type' in stream:
                if stream['event_type'] == 'executionReport':
                    order_id = stream['order_id']
                    orders = find_orders.get(order_id)
                    if not orders:
                        orders = []
                        find_orders[order_id] = orders
                    # try to conver to query_order format
                    orders.append({
                        'clientOrderId': stream['client_order_id'],
                        # 'cummulativeQuoteQty': 
                        'executedQty': stream['last_executed_quantity'],
                        'icebergQty': stream['iceberg_quantity'],
                        'orderId': order_id,
                        'origQty': stream['order_quantity'],
                        # 'origQuoteOrderQty':
                        'price': stream['order_price'],
                        'side': stream['side'],
                        'status': stream['current_order_status'],
                        'symbol': stream['symbol'],
                        'time': stream['order_creation_time'],
                        'updateTime': stream['transaction_time'],
                        'eventTime': stream['event_time'],
                        'source': 'userData'
                    })

                    print('find_orders', len(find_orders))
                else:
                    print('event_type', stream['event_type'])
        else:
            await asyncio.sleep(1)


    binance_websocket_api_manager.stop_manager_with_all_streams()

    print('binance_websocket END')

async def binance_order():
    global running
    binance = binance_client.BinanceClientAsync(api_key=BINANCE_API_KEY, secret_key= BINANCE_SECRET_KEY)
    # binance_helper = binance_client.BinanceHelper(client=binance)

    symbol = 'BTCBUSD'
    side = 'buy'

    # wait binance_websocket start, else order data not received in time
    print('wait ...')
    await asyncio.sleep(5)

    try:
        result = False
        while not result:
            order_id = await create_taker_order(binance, symbol, side)
            result = await check_order(binance, symbol, order_id)
            print('result', result)
        
    except binance_client.BinanceException as e:
        print(e)

    await binance.close()
    running = False
    print('binance_order END')

async def main():
    tasks = [binance_websocket(), binance_order()]
    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.