Predictive Hacks

How to Backtest your Crypto Trading Strategies in R

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)

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'
)

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")

```

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.

Get updates and learn from the best

Python

Creating Dynamic Forms with Streamlit: A Step-by-Step Guide

In this blog post, we’ll teach you how to create dynamic forms based on user input using Streamlit’s session state

Python

How to Connect Wikipedia with ChatGPT and LangChain

ChatGPT’s knowledge is limited to its training data, which has the cutoff year of 2021. This implies that we cannot