Predictive Hacks

How to Backtest your Crypto Trading Strategies in R

backtest

Few words about Trading Strategies

One of the biggest challenges is to predict the Market. Many people have developed their own trading strategies, some of them are advanced based on Machine Learning and Artificial Intelligence algorithms like LSTM, xgBoost, Random Forest etc, some others are based on Statistical models like ARIMA, and some others are based on technical analysis.

Whatever Trading Strategy we are about to apply we need to backtest it, meaning to simulate it and finally to evaluate it. Today, we will provide an example of how you can easily backtest your own trading strategy in R.


Define the Trading Strategy

For illustrative purposes, we defined an arbitrary Trading Strategy which does not make much sense, but it is good to work on it as an example. Let’s define the rules:

When the close price of the Cryptocurrency is X consecutive days in the same direction (i.e. 7 consecutive days “up” or 7 consecutive days “down”) then we Open a Position as follows:

  • When the close price is X consecutive days down, then the next day we buy (long) the cryptocurrency at the “open price”
  • When the close price is X consecutive days up, then the next day we sell (short) the cryptocurrency at the “open price”. We assume that short selling is allowed with CFD

Once we have Opened our positions we use the following alerts:

  • TP: Take profit when your P/L is above X%
  • SL: Stop loss when you P/L is below -Y%

Every position is closed at the open price. Of course, we can extend this assumption by considering hourly or even per minute data. Every trade is 1 unit but we can change this to a multiplier or to a fraction. Notice that the assumption of the 1-unit does not affect our outcome, since we communicate the ROI which is the (P/L) as a percentage.


R Code for to backtest the Trading Strategy

You can have a look at how we can get the Cryptocurrency prices in R and how to count the consecutive events in R. Below we build a function which takes as parameters:

  • symbol: The cryptocurrency symbol. For example, BTC is for the Bitcoin.
  • consecutive: The consecutive count of the signs of the closing prices.
  • SL: The percentage that we stop loss.
  • TP: The percentage that we take profit.
  • start_date: The date that we want to start the backtesting strategy.

Notice that the open positions that have not met the alert criteria of SL and TP and still “Active” and we return them with an “Active” status and as “Profit” we return their current “Profit”.


library(tidyverse)
library(crypto)


back_testing<-function(symbol="BTC", consecutive=7, SL=0.1, TP=0.1, start_date = "20180101") {
  
 
  
  df<-crypto_history(coin = symbol, start_date = start_date)
  

  
  df<-df%>%mutate(Sign = ifelse(close>lag(close),"up", "down"))%>%
    mutate(Streak=sequence(rle(Sign)$lengths))
  
  
  df<-df%>%select(symbol, date, open, high, low, close, Sign, Streak)%>%na.omit()%>%
    mutate(Signal = case_when(lag(Sign)=="up" &amp; lag(Streak)%%consecutive==0~'short',
                              lag(Sign)=="down" &amp; lag(Streak)%%consecutive==0~'long',
                              TRUE~""), Dummy=TRUE
    )
  
  
  Trades<-df%>%filter(Signal!="")%>%select(Open_Position_Date=date, Open_Position_Price=open, Dummy, Signal)
  Trades
  
  
  Portfolios<-Trades%>%inner_join(df%>%select(-Signal), by="Dummy")%>%filter(date>Open_Position_Date)%>%select(-Dummy)%>%mutate(Pct_Change=open/Open_Position_Price-1)%>%
    mutate(Alert = case_when(Signal=='long'&amp; Pct_Change>TP~'TP',
                             Signal=='long'&amp; Pct_Change< -SL~'SL',
                             Signal=='short'&amp; Pct_Change>TP~'SL',
                             Signal=='short'&amp; Pct_Change< -SL~'TP'
    )
    )%>%group_by(Open_Position_Date)%>%mutate(Status=ifelse(sum(!is.na(Alert))>0, 'Closed', 'Active'))
  
  
  Active<-Portfolios%>%filter(Status=='Active')%>%group_by(Open_Position_Date)%>%arrange(date)%>%slice(n())%>%
    mutate(Profit=case_when(Signal=='short'~Open_Position_Price-open, 
                            Signal=='long'~open-Open_Position_Price))%>%
    select(symbol, Status, Signal, date, Open_Position_Date, Open_Position_Price, open, Profit)
  
  Closed<-Portfolios%>%filter(Status=='Closed')%>%na.omit()%>%group_by(Open_Position_Date)%>%arrange(date)%>%slice(1)%>%
    mutate(Profit=case_when(Signal=='short'~Open_Position_Price-open, 
                            Signal=='long'~open-Open_Position_Price))%>%
    select(symbol, Status, Signal, date, Open_Position_Date, Open_Position_Price, open, Profit)
  
  final<-bind_rows(Closed,Active)%>%ungroup()%>%arrange(date)%>%mutate(ROI=Profit/Open_Position_Price, Running_ROI=cumsum(Profit)/cumsum(Open_Position_Price))
  
 
  return(final)
  
  
}

Results of the Backtest

Let’s assume that we want to backtest the trading strategy that we described earlier with the following parameters:

symbol="BTC", consecutive=5, SL=0.1, TP=0.5, start_date = "20180101"
ttt<-back_testing(symbol="BTC", consecutive=5, SL=0.1, TP=0.15, start_date = "20180101")

symbolStatusSignalClosing_DateOpen_Position_DateOpen_Position_PriceClosing_PriceProfitROIRunning_ROI
BTCClosedshort1/9/20181/7/20181752715124240413.70%13.70%
BTCClosedshort3/8/20183/6/2018115009951154913.50%13.60%
BTCClosedlong3/18/20183/11/201888537891-962-10.90%7.89%
BTCClosedshort4/25/20184/15/201879999701-1702-21.30%2.81%
BTCClosedshort8/9/20187/18/201873156306101013.80%4.32%
BTCClosedlong8/9/20188/4/201874396306-1133-15.20%1.92%
BTCClosedlong11/20/201811/17/201855794864-715-12.80%0.68%
BTCClosedshort12/28/201812/21/20184134365348111.60%1.32%
BTCClosedshort4/3/20192/20/201939474880-933-23.60%0.00%
BTCClosedshort5/10/20194/21/201953366176-840-15.70%-1.06%
BTCClosedshort5/12/20195/5/201958317204-1372-23.50%-2.59%
BTCClosedshort5/27/20195/12/201972048674-1471-20.40%-3.98%
BTCClosedshort6/5/20195/28/201988037704109812.50%-2.55%
BTCClosedshort6/23/20196/17/2019898910697-1708-19.00%-3.89%
BTCClosedshort6/27/20196/24/20191085413017-2163-19.90%-5.32%
BTCClosedshort8/30/20198/4/2019108229515130712.10%-3.90%
BTCClosedshort9/25/20199/4/2019106218603201819.00%-2.20%
BTCClosedlong9/25/20199/18/2019102488603-1644-16.00%-3.12%
BTCClosedlong1/15/202011/23/201972968825152921.00%-2.03%
BTCClosedlong1/15/202012/5/201972538825157221.70%-1.00%
BTCClosedshort1/31/20201/8/202081629508-1346-16.50%-1.72%
BTCClosedshort2/27/20202/10/2020101168825129012.80%-0.93%
BTCClosedlong3/13/20202/29/202086715018-3653-42.10%-2.77%
BTCClosedshort5/2/20204/27/202076798869-1190-15.50%-3.25%
BTCClosedlong7/28/20206/28/2020904811017196921.80%-2.18%

Discussion

As we can see the Trading Strategy ended with -2.18% P/L without taking into account the transaction fees. You can see also that in some periods was profitable (2018) and in some other periods was not (2019), that is why is very important to backtest a trading strategy for many different periods.

Notice that with this function we can backtest hundred of backtest strategies with some small tweaks. Instead of this simple trading strategy could be an advanced machine learning model. Once we define the trading signals, then the backtest is the same.

Share This Post

Share on facebook
Share on linkedin
Share on twitter
Share on email

1 thought on “How to Backtest your Crypto Trading Strategies in R”

Leave a Comment

Subscribe To Our Newsletter

Get updates and learn from the best

More To Explore

Python

Image Captioning with HuggingFace

Image captioning with AI is a fascinating application of artificial intelligence (AI) that involves generating textual descriptions for images automatically.

Python

Intro to Chatbots with HuggingFace

In this tutorial, we will show you how to use the Transformers library from HuggingFace to build chatbot pipelines. Let’s