Tutorial — making a trading bot asynchronous using Python’s “unsync” library
By: Matt Gosden
Tags: Python, Trading Bot, Async, Tutorial, Threading

The Python “unsync” library is a very easy way to create async code. This gives a practical example of how to use on a simple trading bot.

There are many ways to skin the async cat

In Python there are many valid ways of parallelising your code, including:

The older way — using the threading and multiprocessing libraries

— using the and libraries The newer way — using async and await from the asyncio library embedded into core Python from 3.7 onwards

— using and from the library embedded into core Python from 3.7 onwards The easier way (I think)— using the @unsync decorator from the Python unsync library

What is so great about ‘unsync’?

I’ve used all the above methods on different projects and they all work fine. But for me, unsync seems to be the easiest to use.

If you have code that is synchronous today and you want to make it asynchronous quickly and easily, then try the unsync library.

Simple example using unsync

The current unsync documentation is on the GitHub repo.

Install unsync

Install the library using pip or conda:

pip install unsync

Decorate your synchronous functions

You only need to do three things to take synchronous code and enable it to work in parallel.

Import the decorator into your code — from unsync import unsync Add the @unsync decorator to a function to make it asynchronous. Note that when you now call that function, it will no longer block the thread until it finishes. Instead, that function call will start the coroutine running and will immediately return an unsync future object, which enables you to get at the return value later when it is finished. To get the return value of the function later, you need to call the .result() method on the unsync future object. This will wait for the coroutine to finish and will give you the final return value.

A simple unsync code example

This method is very quick and easy to implement, and significantly less mind-bending for me than adding asyncio or threading to this routine.

There are other ways to use unsync which include defining async functions. Check out the readme file on the GitHub repo.

A more complex example — parallelising a simple trading bot

This is an example to show how easily unsync can be used on code which may have a number of levels of parallelisation.

What does this trading bot do?

This code represents a simple trading bot that is fired up at intervals, checks the latest market prices on lots of markets and then decides on trades. It uses APIs to fetch the market data and make trades. It uses a database to store history and the current state.

Here is a diagram of the logic followed by each trading cycle:

Logic flow for trading bot

Trading bot — synchronous code

Code for this trading bot example can be found on GitHub here.

Firstly, here is our bot written using synchronous code. I’ve removed the API, database and logic calls to keep the codebase short for this example.

Running our bot (synchronous mode)

When we run this bot we can see from the output below that the markets are run in sequence and the thread is blocked until each market completes. You can also see that the API calls and database calls block the thread.

The bot takes over 16 seconds to complete the cycle.

2020-06-07 18:04:31,569 - Starting up the trading bot. 2020-06-07 18:04:31,569 - AAPL - Starting the bot cycle. 2020-06-07 18:04:31,569 - AAPL - Started fetching market data. 2020-06-07 18:04:32,570 - AAPL - Finished fetching market data. 2020-06-07 18:04:32,571 - AAPL - Started calling database. 2020-06-07 18:04:33,076 - AAPL - Finished fetching database data. 2020-06-07 18:04:33,076 - AAPL - Trading logic decision => exit position. 2020-06-07 18:04:33,076 - AAPL - Posting exit position trade. 2020-06-07 18:04:38,081 - AAPL - exit position trade successful. 2020-06-07 18:04:38,081 - AAPL - Started updating database. 2020-06-07 18:04:38,786 - AAPL - Finished updating database data. 2020-06-07 18:04:38,786 - AAPL - Finished the bot cycle. 2020-06-07 18:04:38,786 - AMZN - Starting the bot cycle. 2020-06-07 18:04:38,786 - AMZN - Started fetching market data. 2020-06-07 18:04:39,792 - AMZN - Finished fetching market data. 2020-06-07 18:04:39,792 - AMZN - Started calling database. 2020-06-07 18:04:40,295 - AMZN - Finished fetching database data. 2020-06-07 18:04:40,295 - AMZN - Trading logic decision => None. 2020-06-07 18:04:40,295 - AMZN - No trade to post. 2020-06-07 18:04:40,296 - AMZN - Started updating database. 2020-06-07 18:04:40,999 - AMZN - Finished updating database data. 2020-06-07 18:04:40,999 - AMZN - Finished the bot cycle. 2020-06-07 18:04:40,999 - MSFT - Starting the bot cycle. 2020-06-07 18:04:40,999 - MSFT - Started fetching market data. 2020-06-07 18:04:42,003 - MSFT - Finished fetching market data. 2020-06-07 18:04:42,003 - MSFT - Started calling database. 2020-06-07 18:04:42,504 - MSFT - Finished fetching database data. 2020-06-07 18:04:42,504 - MSFT - Trading logic decision => exit position. 2020-06-07 18:04:42,504 - MSFT - Posting exit position trade. 2020-06-07 18:04:47,509 - MSFT - exit position trade successful. 2020-06-07 18:04:47,509 - MSFT - Started updating database. 2020-06-07 18:04:48,213 - MSFT - Finished updating database data. 2020-06-07 18:04:48,213 - MSFT - Finished the bot cycle. 2020-06-07 18:04:48,213 - Summary of trades: [('AAPL', 'exit position'), ('AMZN', None), ('MSFT', 'exit position')] 2020-06-07 18:04:48,213 - Finished everything. Took 16.644033193588257 seconds.

We can make our bot better by using asynchronous logic in three areas

We don’t want the trading bot to wait until one market stock is completely finished processing before moving onto the next one. We want to be able to run all the markets in parallel so that all market prices collected are at the same time. The API calls to fetch data and post trades are slow processes that will benefit from using async. The database calls and updates are much faster than the API but may give some gain from parallelisation.

Using unsync to parallelise the trading bot

Using just the @unsync decorators and the .result() methods, we can transform the bot code into an async version with only few small changes.

The code changes from the original synchronous version are shown with an # ADDED comment.

That’s it. Very simple and the code now runs fully async.

Rerun the bot — now in async mode

Rerunning the bot and examining the output below, we see that it does work correctly in parallel. We get a significant speed up both in parallelisation of the markets and the parallelisation of the API and database calls.

The full run is now completed in under 7 seconds.

2020-06-07 18:02:22,658 - Starting up the trading bot. 2020-06-07 18:02:22,658 - AAPL - Starting the bot cycle. 2020-06-07 18:02:22,659 - AAPL - Started fetching market data. 2020-06-07 18:02:22,659 - AMZN - Starting the bot cycle. 2020-06-07 18:02:22,659 - AMZN - Started fetching market data. 2020-06-07 18:02:22,660 - AAPL - Started calling database. 2020-06-07 18:02:22,660 - MSFT - Starting the bot cycle. 2020-06-07 18:02:22,660 - AMZN - Started calling database. 2020-06-07 18:02:22,661 - MSFT - Started fetching market data. 2020-06-07 18:02:22,661 - MSFT - Started calling database. 2020-06-07 18:02:23,164 - AMZN - Finished fetching database data. 2020-06-07 18:02:23,164 - MSFT - Finished fetching database data. 2020-06-07 18:02:23,164 - AAPL - Finished fetching database data. 2020-06-07 18:02:23,664 - AMZN - Finished fetching market data. 2020-06-07 18:02:23,664 - AAPL - Finished fetching market data. 2020-06-07 18:02:23,664 - AMZN - Trading logic decision => go short. 2020-06-07 18:02:23,664 - AAPL - Trading logic decision => None. 2020-06-07 18:02:23,665 - AAPL - No trade to post. 2020-06-07 18:02:23,665 - AMZN - Posting go short trade. 2020-06-07 18:02:23,665 - MSFT - Finished fetching market data. 2020-06-07 18:02:23,666 - MSFT - Trading logic decision => go short. 2020-06-07 18:02:23,666 - AAPL - Started updating database. 2020-06-07 18:02:23,667 - MSFT - Posting go short trade. 2020-06-07 18:02:24,370 - AAPL - Finished updating database data. 2020-06-07 18:02:24,370 - AAPL - Finished the bot cycle. 2020-06-07 18:02:28,668 - AMZN - go short trade successful. 2020-06-07 18:02:28,669 - AMZN - Started updating database. 2020-06-07 18:02:28,671 - MSFT - go short trade successful. 2020-06-07 18:02:28,672 - MSFT - Started updating database. 2020-06-07 18:02:29,374 - AMZN - Finished updating database data. 2020-06-07 18:02:29,374 - AMZN - Finished the bot cycle. 2020-06-07 18:02:29,376 - MSFT - Finished updating database data. 2020-06-07 18:02:29,377 - MSFT - Finished the bot cycle. 2020-06-07 18:02:29,377 - Summary of trades: [('AAPL', None), ('AMZN', 'go short'), ('MSFT', 'go short')] 2020-06-07 18:02:29,377 - Finished everything. Took 6.718775033950806 seconds.

Alternative ways of creating async code

We could have used other methods to make this trading bot asynchronous.

1. Using the threading library

We can use the threading library’s Thread object, kick off the threads and wait for them to complete using the .join() method.

Alternatively we could use the ThreadPoolExecutor instead (which is actually what unsync uses under the hood in the above example).

2. Using asyncio

From Python 3.7+ we can use the asyncio library. We add async to the functions to make them asynchronous. We add await to the async function calls so that it waits for the coroutine to finish and deliver the final return value.

We create a list of async call tasks where we want to use parallelisation and use something like asyncio.gather(*tasks) to kick them off and wait for them to finish.

But these alternatives take a lot more time to implement

Because I wrote synchronous code first, and then only later thought about parallelising, moving to async using the threading or asyncio libraries requires a lot more changes, debugging and coding time.