How to build your own Python trading bot
In order to get you started with the Trality Bot Code Editor and your first Python trading bot, we use this post to cover a fairly basic approach to building a simple trading algorithm. It is built up of standard technical analysis (TA) but also includes some features of the Trality API that can help you to create more sophisticated trading bots as you go along. Moreover, we analyze a resulting backtest which tests our algorithm over the span of the year 2020.
2020 was a good year for crypto, especially for BTC, the price of which increased over 220% year to date at the writing of this article. Nonetheless, we all remember those uncertain times at the beginning of the year, when the Corona crisis hit the financial markets with full force and even Bitcoin had to endure some strong price adjustments. Especially in turbulent times like these, automating your trading can help you to significantly cut losses. In fact the bot which we discuss in the article at hand, would have significantly protected your assets in such uncertain times!
Figure 1: BTCUSDT price developments 01.01.2020–21.12.2020
So, while in retrospect 2020 turned out to be a great year for BTC, it was also characterized by high uncertainty due to the Corona Crisis and high resulting volatility. Compare this earlier blog post of us, written in the midst of the first corona wave. Hence, investors ask themselves no other question more important than: how can I ensure constant high profits in difficult and uncertain times, even if they might be characterized by high profits already?
The answer to this question is: with trading bots!
What is a Python trading bot?
A trading bot is a software that connects to your favorite exchange’s API to interact with it automatically and execute trades on your behalf based on its analysis of market data. The bot will buy/sell an asset when the market condition meets a predefined set of criteria.
How do crypto trading bots work?
Cryptocurrency trading bots will make decisions on behalf of its trader based on information such as price movements within the market, generating a reaction based upon a predefined set of criteria. These bots typically look at items such as market volume, current orders, prices, and time and decide whether or not it’s a good moment for a trade. Trading bots can interpret and merge traditional market signals such as price, volume, orders and time with more sophisticated analyses signals such as machine learning models or social media sentiment and if you’re still not sure then please take a look at our introduction to crypto trading bots here.
So how do we use a simple cryptocurrency trading bot to protect our assets in a turbulent year like 2020? We will show you a beginner-level example strategy which is based on a TA indicator, commonly used: an exponential moving average (EMA), the crossover of two EMAs to be precise.
See the Trality Documentation for a detailed explanation of the EMA crossover strategy. In short: An EMA is a type of moving average (MA) that places a greater weight and significance on the most recent data points. An exponentially weighted moving average reacts more significantly to recent price changes than a simple moving average (SMA). A crossover strategy applies two moving averages to a chart: one longer and one shorter. When the shorter-term MA crosses above the longer-term MA, it’s a buy signal, as it indicates that the trend is shifting up. Meanwhile, when the shorter-term MA crosses below the longer-term MA, it’s a sell signal, as it indicates that the trend is shifting down. (based on Investopedia)
We hence develop a strategy with two EMAs (20 and 40 candles look back period). The strategy trades on 1 hour candles, making it sensitive to mid- to short-term price movements. It moreover allocates always 95% of available base currency to each trade. We use the Trality Python code editor to craft this algorithm. Lets go through each of the main building blocks, one step at a time:
Step 0: Define handler function
To begin with, every function that is annotated with our schedule decorator is run on a specified time interval and receives symbol data. We call these annotated functions handler, but you can name them however you want. They just have to take two arguments. We call the first one state and the second one data. The second argument will always receive the symbol data for the interval you specified. In this particular bot, we trade in 1 hour candle intervals and we specify a trading symbol, which is BTCUSDT. Of course multiple symbols are possible to trade on as well!
def initialize(state):
state.number_offset_trades = 0;
@schedule(interval="1h", symbol="BTCUSDT")
def handler(state, data):
Step 1: Compute indicators from data
In a first step of our algorithm creation, we define two exponential moving averages, EMA, one with a longer look-back period of 40 candles and one with a longer of 20 candles. We also define that possible missing data are skipped, should they occur.
ema_long = data.ema(40).last
ema_short = data.ema(20).last
# on erronous data return early (indicators are of NoneType)
if ema_long is None:
return
current_price = data.close_last
Step 2: Fetch portfolio
In a second step, we fetch a current snapshot of our portfolio, on which the bot is trading, including information on the current balance of our quoted asset, USDT. The latter ensures that we have enough liquidity to open up a possible new position. We also define that once we open such a position, they are of a certain size, namely the buy_value. We resolve buy_value to be of 95% of the current liquidity.
portfolio = query_portfolio()
balance_quoted = portfolio.excess_liquidity_quoted
# we invest only 95% of available liquidity
buy_value = float(balance_quoted) * 0.95
Step 3: Fetch position for symbol
In a third step we query for any open position by symbol. By calling this function we receive a boolean value indicating whether an open position for that symbol exists or not.
position = query_open_position_by_symbol(data.symbol,include_dust=False)
has_position = position is not None
Step 4: Resolve buy or sell signals
In a fourth step, the heart and soul of our algorithm is defined: its trading strategy. We use the order API to create orders. Specifically, the algorithm places a market order going long if the the shorter EMA crosses above the longer, for the amount as defined above in buy_value.
Moreover, we print the relevant position information, so that we can track all all orders in the logs.
We also define a sell logic, which closes the open position if the algorithm detects an open position and the shorter crosses below the longer EMA.
if ema_short > ema_long and not has_position:
print("-------")
print("Buy Signal: creating market order for {}".format(data.symbol))
print("Buy value: ", buy_value, " at current market price: ", data.close_last)
order_market_value(symbol=data.symbol, value=buy_value)
elif ema_short < ema_long and has_position:
print("-------")
logmsg = "Sell Signal: closing {} position with exposure {} at current market price {}"
print(logmsg.format(data.symbol,float(position.exposure),data.close_last))
close_position(data.symbol)
Putting the pieces together
If we put all these steps together, we get the following little code snippet, which we can subsequently put through our first backtest emerges:
def initialize(state):
state.number_offset_trades = 0;
@schedule(interval="1h", symbol="BTCUSDT")
def handler(state, data):
'''
1) Compute indicators from data
'''
ema_long = data.ema(40).last
ema_short = data.ema(20).last
# on erronous data return early (indicators are of NoneType)
if ema_long is None:
return
current_price = data.close_last
'''
2) Fetch portfolio
'''
portfolio = query_portfolio()
balance_quoted = portfolio.excess_liquidity_quoted
# we invest only 95% of available liquidity
buy_value = float(balance_quoted) * 0.95
'''
3) Fetch position for symbol
'''
position = query_open_position_by_symbol(data.symbol,include_dust=False)
has_position = position is not None
'''
4) Resolve buy or sell signals
'''
if ema_short > ema_long and not has_position:
print("-------")
print("Buy Signal: creating market order for {}".format(data.symbol))
print("Buy value: ", buy_value, " at current market price: ", data.close_last)
order_market_value(symbol=data.symbol, value=buy_value)
elif ema_short < ema_long and has_position:
print("-------")
logmsg = "Sell Signal: closing {} position with exposure {} at current market price {}"
print(logmsg.format(data.symbol,float(position.exposure),data.close_last))
close_position(data.symbol)
Side note: if this seems like too much code for your liking, then you should try out our Trality Rule Builder, which lets you design simple strategies such as the one in this blog post based on boolean logic:
Backtesting on historical data
In order to evaluate our beginner-level cryptocurrency trading bot, we run the above created code in the Trality backtester and obtain the following results:
Figure 3 shows the results of our beginner-level trading bot over a period from Jan 1 to Dec 21, 2020. Backtests on Trality always include exchange fees and can be modelled to account for slippage. You can see from the figure, that, as discussed initially, the backtest was undertaken during a period of significant volatility but also significant market gains in the long run. As evident by the individual positions, the bot manages quite well to avoid the extrem downturns, such as for example mid-march. It closes all positions (as evident by the red arrow before the large dip) and buys again at the bottom (green arrow after the dip). Similarly it buys before larger rallys and sells at the top of the price chart.
Adding more analyses to our algorithm
Backtesting our bot like this is all fair and good, however often times, traders need more immediate feedback on their bot, especially when it comes to the profitability of individual positions. For this reason, we included a position management system into the Trality API, that lets you track key metrics of your positions, directly in the logs as you test your bot.
In the following step, we first check whether the current trade has reduced a position’s exposure, i.e. whether it is an offsetting trade. If so, we can dig deeper and gather insights into the trade’s PnL. Doing so, we analyze the overall number of offsetting trades so far, the subset of winning offsetting trades, the best and worse return per trade as well as the average profit and loss per winning respectively losing trade.
if state.number_offset_trades < portfolio.number_of_offsetting_trades:
pnl = query_portfolio_pnl()
print("-------")
print("Accumulated Pnl of Strategy: {}".format(pnl))
offset_trades = portfolio.number_of_offsetting_trades
number_winners = portfolio.number_of_winning_trades
print("Number of winning trades {}/{}.".format(number_winners,offset_trades))
print("Best trade Return : {:.2%}".format(portfolio.best_trade_return))
print("Worst trade Return : {:.2%}".format(portfolio.worst_trade_return))
print("Average Profit per Winning Trade : {:.2f}".format(portfolio.average_profit_per_winning_trade))
print("Average Loss per Losing Trade : {:.2f}".format(portfolio.average_loss_per_losing_trade))
# reset number offset trades
state.number_offset_trades = portfolio.number_of_offsetting_trades
Takeaways for your Python trading bot
It is important to note that this is a fairly simple trading bot which is meant as a starting point for your analysis. Trality offers many more possibilities to create bots that not only protect you from down swings but instead also may help you to significantly outperform the market. For this, more nuanced elements of your code might become necessary such as trading on multiple intervals and with multiple coins; or using a sophisticated order management with multiple order types.
Do it yourself!
With Trality’s flexible and browser-based Python Code Editor, you can create cutting-edge bots in a snap. Make use of numpy, pandas and other popular libraries to seamlessly create any number of bots. It comes with a state-of-the-art Python API, end-to-end encryption, and many more flexible featres to assist you in developing top-performing algorithms.
Disclaimer: None of what is found in this article should be considered investment advice. The above article is merely an opinion piece and does not represent any kind of trading advice or suggestions on how to invest, how to trade or in which assets to invest in or suggestions on how trading bots or trading algorithms can or should be used! Always do your own research before investing and always (!) only invest what you can afford to lose! Backtests are not indicative of future results.
Originally published at https://www.trality.com/blog/ on December 22, 2020.