Create and Cancel Orders via WebSocket on Binance
How to access the Binance API via WebSocket in Python

Until now it was only possible to receive data from Binance via WebSocket. To send requests to the Binance API, for example to create or cancel orders, you always had to use the slower REST API. This has changed now! ๐
Recently, the Binance Spot Testnet and Binance Spot have added the ability to place orders, cancel orders, and handle other API requests via a WebSocket connection:
Binance WebSocket API Documentation
In Python, UNICORN Binance WebSocket API already supports the new features to send API requests to Binance via WebSocket. To do this, we will go through the following steps:
Binance
โ Account
โ API key and API secret
Installation of requirements
โ PIP
โ Conda
Create a Python script and establish a WebSocket API connection to Binance
Send requests and handle the responses
โ Global async function
โ Global callback function
โ Stream specific async function
โ Stream specific callback function
โ Request specific callback function
โ Save answer in variable
โ Using the
stream_bufferโ Multiple API Streams
Available methods for API requests
โ
ubwa.api.spot.cancel_order()โ
ubwa.api.spot.create_order()Further information
1. Binance
Account
To be able to trade on Binance you need a user account. If you don't have one yet, you can sign up via my referral link. With this we both get 100 USDT cashback vouchers for a 50 USD deposit.
API key and API secret
How to create an API Key/Secret pair you can read in detail here.
2. Installation of requirements
Minimum requirement is a working Python 3.8+ installation. To use the UNICORN Binance WebSocket API in your Python script, you need to install it on the command line using one of the two commands:
With PIP:
This is the default way and should always work. It contains the plain source code and optimized Cython and PyPy Wheels:
pip install unicorn-binance-websocket-api
Or with Conda:
This is only possible within an Anaconda environment:
conda install -c conda-forge unicorn-binance-websocket-api
3. Create a Python script and establish a WebSocket API connection to Binance
First we import unicorn_binance_websocket_api, define the callback function handle_socket_message() to print the received data and then store an instance of BinanceWebSocketApiManager() in the variable ubwa.
If you want to connect BinanceWebSocketApiManager() to the testnet, you need to pass the string 'binance.com-testnet' to the exchange parameter.
With process_stream_data=handle_socket_message we pass BinanceWebSocketApiManager() a global callback function, to this function all received responses of the API are passed by default.
By default, all API requests return a string with a JSON structure. To automatically convert the JSON structure to a Python dictionary, we configure output_default="dict".
https://gist.github.com/oliver-zehentleitner/5a5d739710400c8fbd9f04833c4bf1dc
Next we create the API stream, for this it is important to set the parameter api to True, otherwise a connection to another WebSocket endpoint would be established, where the API requests would not work. Please make sure that you use a valid API key/secret pair and consider the IP whitelist restrictions when testing!
https://gist.github.com/oliver-zehentleitner/b31adc37450a7a45752adb459c6b71d6
Now all the prerequisites are met for us to send and process requests to the Binance API.
4. Send requests and handle the responses
Note: According to this scheme all methods of
ubwa.apican be called and executed.
Using the ubwa.api object we can now send requests to the Binance API, e.g. a connection test with the ubwa.api.spot.ping() method.
There are several ways to handle the API requests:
- Global async function
- Global callback function
- Stream specific async function
- Stream specific callback function
- Request specific callback function
- Save answer in variable
- Using the
stream_buffer - Multiple API Streams
Global async function
When receiving the response from Binance, the function handle_socket_message() is executed which we passed when initiating BinanceWebSocketApiManager(), and this is where you can hook your code.
In our example, we simply output the received data:
https://gist.github.com/oliver-zehentleitner/576ab4518ecbca583c3eec209533def1
Output:
Received data:
{"id":"1cc8812a0c6b-08aa-6098-2742-ac0cedc8","status":200,"result":{},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":2}]}
I will use the example with ubwa.api.spot.ping() to introduce the other methods how you can receive and process the received data.
Global callback function
When receiving the response from Binance, the function handle_socket_message() is executed which we passed when initiating BinanceWebSocketApiManager(), and this is where you can hook your code.
In our example, we simply output the received data:
https://gist.github.com/oliver-zehentleitner/576ab4518ecbca583c3eec209533def1
Output:
Received data:
{"id":"1cc8812a0c6b-08aa-6098-2742-ac0cedc8","status":200,"result":{},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":2}]}
Stream specific callback function
It is possible to pass a callback function to ubwa.create_stream() as well. Then, for receiving responses from this stream, the stream specific callback function is used instead of the global callback function we passed to BinanceWebSocketApiManager().
https://gist.github.com/oliver-zehentleitner/461a06fef1d809dd104e241599ab0f20
Now we can run ping again and this time we would use the callback function handle_stream_message() instead of handle_socket_message().
https://gist.github.com/oliver-zehentleitner/576ab4518ecbca583c3eec209533def1
Output:
Received stream data:
{"id":"1cc8812a0c6b-08aa-6098-2742-ac0cedc8","status":200,"result":{},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":2}]}
Request specific callback function
Since not all types of data are treated the same way, there is also the possibility to process responses of a specific request with a specific callback function.
https://gist.github.com/oliver-zehentleitner/863f346969bbbe315ab808ca6e658013
Output:
Received ping response:
{"id":"1cc8812a0c6b-08aa-6098-2742-ac0cedc8","status":200,"result":{},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":2}]}
Save answer in variable
There is also the possibility to let the called function wait until the response to the request has arrived and then store it directly into a variable. The disadvantage of this method is that the function becomes blocking, whereby several requests can only be processed sequentially.
https://gist.github.com/oliver-zehentleitner/57e1a32959b2019c62e0254f2ad19726
Output:
Awaited ping response:
{"id":"1cc8812a0c6b-08aa-6098-2742-ac0cedc8","status":200,"result":{},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":2}]}
Using the stream_buffer
A completely different approach than the callback functions is the stream_buffer. This is the standard way of BinanceWebSocketApiManager() to process received data, here all received data is stored in a deque() stack and can be used as FIFO as well as LIFO stack to read the received data in a loop.
The main advantage of this solution is the desynchronization between receiving and processing the new data.
What does this mean? UBWA receives the new data in an AsyncIO event loop and processes them asynchronously. This means that the data is not received and processed sequentially, but that the callback functions within the event loop are started in parallel for each data record received, which can easily push your system to its limits during peak load times, e.g. if you receive more data faster than you can store in your database over a longer period for speed reasons.
Because each received record is simply stored in the stream_buffer, the processing is immediately finished for UBWA. You can easily access the new data in the stream_buffer in another thread or better another process and adjust the processing time to your circumstances. Additionally the stream_buffer can be monitored by you and offers further options.
As already mentioned the stream_buffer is the standard method of UBWA and is simply overwritten by setting the callback functions. So if you want to use it, do not pass a callback function in the class initialization of BinanceWebSocketApiManager() or anywhere else.
One way to use callback functions everywhere and still use the stream_buffer in a special situation is to explicitly pass ubwa.add_to_stream_buffer as callback function.
Of course it is also possible to use different stream_buffer, how to do that is described in the wiki.
I now choose a simple example with only one global stream_buffer. If you have now simply omitted passing the callback functions, the global stream_buffer is activated by default and your stream will write its data there.
Hereby you get access to the oldest record in the stack:
https://gist.github.com/oliver-zehentleitner/96d5789f65761f08069b25229ec83af9
Note: If the
stream_bufferis empty,ubwa.pop_stream_data_from_stream_buffer()returns the boolean valueFalse!
For example, if you have problems with your database, you can catch the exception of the database connection in a try block and use ubwa.add_to_stream_buffer() in the except block to save the record back into the stream_buffer and simply retrieve it later. Unfortunately, this messes up the chronological sorting! ๐คจ
https://gist.github.com/oliver-zehentleitner/241061d809c76ac1b08013a9ee40386c
Multiple API streams
If there is more than one API stream (with api=True), the methods must be told which stream to use. The stream_id is used to uniquely identify the streams and is returned by ubwa.create_stream(), alternatively you can also work with a stream_label. For this to work, a stream_label must also be defined when creating the stream with ubwa.create_stream().
Example with stream_id:
https://gist.github.com/oliver-zehentleitner/7fc0c1ed65fa96bd9e65f3173143c17d
Example with stream_label:
https://gist.github.com/oliver-zehentleitner/b2634fd6c45574b460c3fde8414f278c
5. Available methods for API requests
Note: Actions executed via WebSocket are subject to the same filters and rate restrictions as when executed via the REST API, and are also functionally equivalent: they provide the same functionality, accept the same parameters, and return the same status and error codes.
Each API request has its own list of accepted and mandatory parameters, gives different responses, and is subject to different request limits. To understand what is happening in the background, it is always advisable to also study the official documentation of Unicorn Binance WebSocket API and the Binance WebSocket API.
ubwa.api.spot.cancel_order()ubwa.api.spot.create_order()
Cancel open orders
Cancel all open orders on a symbol, including OCO orders.
If you cancel an order that is a part of an OCO pair, the entire OCO is canceled.
https://gist.github.com/oliver-zehentleitner/1ca32c20ff397e91f3466f788a718a6e
Output:
{"id":"d37149a608c4-7951-fbbb-33f6-93d68e2d","status":200,"result":{"symbol":"BUSDUSDT","origClientOrderId":"1","orderId":942396461,"orderListId":-1,"clientOrderId":"rkAnxL9oEZr7wzKaCMiAuQ","price":"1.00000000","origQty":"15.00000000","executedQty":"0.00000000","cummulativeQuoteQty":"0.00000000","status":"CANCELED","timeInForce":"GTC","type":"LIMIT","side":"SELL","selfTradePreventionMode":"NONE"},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":3}]}
Cancel an order
Cancel an active order.
If you cancel an order that is a part of an OCO pair, the entire OCO is canceled.
https://gist.github.com/oliver-zehentleitner/1ca32c20ff397e91f3466f788a718a6e
Output:
{"id":"d37149a608c4-7951-fbbb-33f6-93d68e2d","status":200,"result":{"symbol":"BUSDUSDT","origClientOrderId":"1","orderId":942396461,"orderListId":-1,"clientOrderId":"rkAnxL9oEZr7wzKaCMiAuQ","price":"1.00000000","origQty":"15.00000000","executedQty":"0.00000000","cummulativeQuoteQty":"0.00000000","status":"CANCELED","timeInForce":"GTC","type":"LIMIT","side":"SELL","selfTradePreventionMode":"NONE"},"rateLimits":[{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":3}]}
Create an order
Create a new order.
ubwa.api.spot.create_order() automatically creates a client_order_id and returns it. This way you can always uniquely identify the order in further steps.
https://gist.github.com/oliver-zehentleitner/adf377f0c26740e7195d45d5f6bf93b3
Output:
{"id":"4db8f58ee69e-d597-fb26-8551-786707fa","status":200,"result":{"symbol":"BUSDUSDT","orderId":942394911,"orderListId":-1,"clientOrderId":"1","transactTime":1680911574690,"price":"1.00000000","origQty":"15.00000000","executedQty":"0.00000000","cummulativeQuoteQty":"0.00000000","status":"NEW","timeInForce":"GTC","type":"LIMIT","side":"SELL","workingTime":1680911574690,"fills":[],"selfTradePreventionMode":"NONE"},"rateLimits":[{"rateLimitType":"ORDERS","interval":"SECOND","intervalNum":10,"limit":50,"count":1},{"rateLimitType":"ORDERS","interval":"DAY","intervalNum":1,"limit":160000,"count":20},{"rateLimitType":"REQUEST_WEIGHT","interval":"MINUTE","intervalNum":1,"limit":1200,"count":4}]}
6. Further information
There are many other functions to send requests to the Binance API.
If you find bugs or have suggestions for improving the API implementation, you can open an issue via GitHub.
For more information please read the documentation for unicorn-binance-websocket-api.
I hope you found this tutorial informative and enjoyable! Follow me on GitHub and LinkedIn to stay updated on my latest releases. Your constructive feedback is always appreciated!





