2020 started off good for crypto investors, with BTC prices increasing from levels of 7000USD to over 10,000USD in early February. Since then the disease COVID-19 has shook the markets to their cores. To the surprise of many, especially to strong believers in the independence of crypto assets from the traditional markets, Bitcoin portrayed a similar downtrend to equities over the course of February and especially in the first two weeks of March (see Figure 1), especially on Friday March 13th, where BTC dropped over 30% in one day.

Figure 1: BTCUSDT price developments 01.01.2020 - 31.03.2020

Naturally, the important question that investors ask themselves in times like these is what they can do in order not to have to surrender to such terrible market conditions. Is there a way to avoid losing over 30% of your investment's worth in one single day? The short answer to this question is "yes, there can be a way": there are means and ways to trade successfully in this market and they rely on a technique called algorithmic trading.

Sounds complicated? Well it doesn't need to be! At Trality, our goal is to make professional-grade algorithmic trading available to private investors so that everyone can profit from it - not just the professionals. We do that with the help of trading bots! These are are technical vehicles for trading algorithms that control the automated buying and selling of (in this case: crypto-) assets. The algorithms use mathematical criteria based on assets' price or volume data to determine when a certain asset should be bought or sold. The trading bot forwards the resulting signals in the form of an order to a crypto exchange. Trading bots can help users to increase their profits in buying and selling assets by exploiting their volatility.

Try for yourself for free

The Strategy

So how can even a simple version of a trading algorithm be used during a downward market environment such as the one starting mid-February? Let us give you a beginner-level example strategy, based on an indicators commonly used in technical analysis: an exponential moving average (EMA), specifically, the crossover of two EMAs.

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 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 constitutes a buy signal, as the trend is shifting up. Meanwhile, when the shorter-term MA crosses below the longer-term MA, it's a sell signal, as the trend is shifting down. (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 80% of available base currency to each trade. We use the Trality Python Bot Code Editor to craft this algorithm:

	# Define crossovers
def crossed_upwards(short, lng):
    return short[-2] < lng[-2] and short[-1] > lng[-1]

def crossed_downwards(short, lng):
    return short[-2] > lng[-2] and short[-1] < lng[-1]

@schedule(interval="1h", symbol="BTCUSDT")
def handler(state, data):

    # Catch basic indicators
    ema_long = data.ema(40)
    ema_short = data.ema(20)

    # Empty values check, e.g. maintenance windows, etc.
    if any(param is None for param in [ema_long.last, ema_short.last]):
        return

    # Check whether we currently have an open position that's not dust
    has_position = has_open_position(data.symbol, truncated=True)

    # Define EMA conditions
    ema_crossed_upwards = crossed_upwards(ema_short['real'], ema_long['real'])
    ema_crossed_downwards = crossed_downwards(ema_short['real'], ema_long['real'])

    # Compute amount we want to buy (we use 80% of our portfolio here)
    balance_base = float(query_balance_free(data.base))
    balance_quoted = float(query_balance_free(data.quoted))
    buy_amount = balance_quoted * 0.80 / data.close_last

    # Check for buy and sell conditions and allow one open position at a time
    if ema_crossed_upwards and not has_position:
       create_order(symbol=data.symbol,amount=buy_amount)

    elif ema_crossed_downwards and has_position:
       close_position(data.symbol)
By the way: 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 without the need for code!

This Strategy Broken Down

Let's break down the bot above line-by-line

def crossed_upwards(short, lng):
    return short[-2] < lng[-2] and short[-1] > lng[-1]

def crossed_downwards(short, lng):
    return short[-2] > lng[-2] and short[-1] < lng[-1]

First, we define the conditions for each crossover type: upwards and downwards crossovers. An upward cross is defined when two candle periods in the past the price of  the long EMA lies above the short EMA and one candle period in the past this relation has switched so that the short EMA price lies above the long EMA. The opposite relation is defined as a downward cross.

@schedule(interval="1h", symbol="BTCUSDT")

What you are seeing second is the Schedule decorator. You can annotate any handler function in your code with this decorator to make it run in a specific interval, for a specified set of symbols. In this example, the function will be called every 5 minutes and the handler function will receive data for the symbol or trading pair BTCUSDT - where BTC is the base asset and USDT is the quoted asset.

def handler(state, data):

The specified handler function that you annotate will receive two arguments: state and data. While the State object can be used to store any variables you like between different handlers, the Data object contains your requested information on the symbol BTCUSDT. This object has many built-in functions, such as the more than 100 technical analysis indicators that can be computed directly from the data object.

    ema_long = data.ema(40)
    ema_short = data.ema(20)

As you can see, you can directly compute financial indicators from the data object. In this case we are computing the Exponential Moving Average with two different periods (40 and 20 candles) and then selecting the last value.

    if any(param is None for param in [ema_long.last, ema_short.last]):
        return

Next the code checks that if empty values, for example caused by maintenance windows, etc exist and continues even if that is the case.

    has_position = has_open_position(data.symbol, truncated=True)

In the strategy we want to avoid having more than 1 open position for the symbol BTCUSDT. We use one of the many Utility functions to retrieve whether we currently have an open position. The truncated parameter assures that very small positions (e.g. dust) are not considered as an open position.

    ema_crossed_upwards = crossed_upwards(ema_short['real'], ema_long['real'])
    ema_crossed_downwards = crossed_downwards(ema_short['real'], ema_long['real'])

Next our previously defined crossovers are computed with the ema_short and ema_long data which we received above.

balance_base = float(query_balance_free(data.base))
balance_quoted = float(query_balance_free(data.quoted))
buy_amount = balance_quoted * 0.80 / data.close_last

Next, the code computes the amount the strategy should allocate to any buy order. In this case, 80% of the current balance of the quoted asset is allocated.

if ema_crossed_upwards and not has_position:
   create_order(symbol=data.symbol,amount=buy_amount)

elif ema_crossed_downwards and has_position:
   close_position(data.symbol)

This is the basic logic of the trading bot. Note, that we are comparing both the moving averages and the has_position flag to decide whether to buy or sell. A buy order for the previously computed buy_amount is signaled if an upward cross of the two EMAs happens, however only if no open position already exists. This opened position is closed again in the case of a downward cross and the existence of an open position.

The create_order function places a request on the respective exchange to buy the desired amount. The close_position function on the other hand closes an already existing BTC position should it exist.

Try for yourself for free

Backtesting the Strategy

We subsequently use the Trality backtesting module to evaluate our strategy's returns:

Figure 2: Performance of Trading Bot (1h) BTCUSDT EMA Crossover (20,40) 10.02.2020-31.03.2020

Figure 2 displays the results of the aforementioned algorithm over a period from Feb 10 to March 31, 2020. Backtests on Trality always include exchange fees and can be modeled to account for slippage. As can be seen from the results, the strategy was backtested on a timeframe where the market significantly trended downwards and recovered only towards the end (see dark blue line). BTC lost over 34% of its value during that period. During the same period however our trading bot was able to amass a small profit of 2.71% by trading 14 times and thereby eliminating the large declines, which BTC would have otherwise been exposed to. Cumulated fees totaling 1.32% are deducted from the results, which can be seen depicted as the light blue line.

It is fair to argue at this point that this is only one backtest done over a short period of time and that backtests shouldn't be trusted as a sole measure for future success! While this is absolutely correct, the above above example nevertheless shows that even a trading bot as simple as based on an EMA crossover strategy can help to significantly cut ones losses during times of downturn - which it did in this particular case. For the sake of argument, however, let's take a look at a longer period of time and how our strategy might have performed during changing market environments.

Figure 3: Performance of Trading Bot (1h) BTCUSDT EMA Crossover (20,40) 01.01.2020-31.03.2020

As can be seen from Figure 3, over a 3-month period, the price of BTC initially rose significantly before enduring the aforementioned decline from mid-February to mid-March until finally climbing again to current levels. It becomes obvious that our simple EMA Crossover strategy is not as successful as above in all these varying market environments: It significantly under-performs the market in the first 1.5 months of our backtest, because a strong upward market doesn't result in enough trend reversals for the exponential moving averages to cross and hence result in trade signals. The algorithm unleashes its worth only in the second half of the backtest, where it ends up outperforming the market by a total of 34.71%.

Algorithmic trading isn't a "set it and forget it" type of operation!

This example trading bot and the two backtests go to show you that algorithmic trading isn't a "set it and forget it" type of operation. To be successful in the long-run, strategies need to be crafted meticulously, back- as well as forward tested and be flexibly adaptable to changing market environments. What does that mean in detail? In this series of guest blog posts we outline the process how sophistication can be added to trading bot development in order for added flexibility in changing market environments. Remember, all of the above strategy design is done in perfect hindsight about the situation and solely based on backtests.

Try for yourself for free

Disclaimer: Backtests are not indicative of future results, the above article is merely an opinion piece and does not represent any kind of trading advice or suggestions how trading bots or trading algorithms can or should be used.