There are some existing posts/articles about the triple barrier method. They are not very detailed, and I will write a quite indepth one here. So, I will go over the method very exhaustively. I hope that you can just Ctrl + F (or Cmd + F on Mac) to find the part that you are interested in.
Introducing the triple barrier method
The triple barrier method is a method to label data for asset price prediction. The method is coined by De Prado (2018), and it has been gaining popularity in the quantitative finance community. The method may seem foreign for the academic community, but it is actually what day traders have been doing manually for a long time. When a day trader places an order, they set a certain price point to trigger automatically to either take their profit or stop their loss.
Above you can see the 2 horizontal barriers that are placed to trigger exit orders. You may ask where is third barrier of the triple barrier method. The third barrier will be a vertical barrier, which is the time horizon \(h\). The time horizon is the maximum time that the position can be held. The label \(y_t\) for a given time \(t\) is defined as follows:
\[
y_t=\left\{\begin{array}{cl}
-1 & \text { if hit upper barrier } \\
0 & \text { if hit vertical barrier } \\
1 & \text { if hit lower barrier }
\end{array}\right.
\]
Intuition on consistency
Now, let me wear my statistician hat and make some remarks. After we have our labels, we want to believe that the label \(y_t\) for time \(t\) is depends on some conditional probability distribution \(f(y_t|x_t)\) for given futures \(x_t\). Since, we are constructing our own labels, it relies on us to construct sound labels. If some orders trigger profit in 5 minutes and some in 5 days, the labels would not be consistent. By consistency, I mean that we should expect to have similar market conditions for each label class. Our predictive futures \(x_t\) captures the market conditions at time \(t\). If the market conditions are drastically different for each label class, the conditional probability distribution \(f(y_t|x_t)\) would not have much predictive power.
Intuition on barrier heights
There are other variants of the triple barrier method with dynamic barriers, but for the vanilla version, the barriers are static. Thus, in order to implement you need to have 3 ingredients for each time \(t\):
upper barrier \(u_t\),
lower barrier \(l_t\), and
horizon \(h_t\).
In practice, you can not have a fixed horizon \(h_t\) for each time \(t\), since markets are not 7/24 open. Based on the resolution of your data, you can either filter out the entry points that does not fit your time horizon, or you can use the maximum possible time horizon \(h_t = \max\{ h^{\text{max}}, h_t^* \}\), where \(h_t^*\) is the remaining time that the market closes
To calculate the upper and lower barriers, you can either use a (1) fixed value, or you can use a (2) volatility based value to calculate the barriers. I have seen cases where both methods are viable. The interpreatation of the both cases are crucial when you are in the feature engineering stage. Case (2) is scaled by the volatility, so may think of it as the effect of the volatility is removed. This could be advantageous if the trends in the market are volatility independent. Case (1) is sensitive to the volatility, so it may be more suitable if the trends in the market are volatility dependent. I usually use both cases and see which one performs better, but the crucial part is to interpret the labels correctly.
For Case (2), I have seen many people estimating volatility using prices, but I think it is better to use returns. Moreover, I also do not advise using the built-in standard deviation functions in your programming language. I take the square of the returns, and then I use the exponential moving average (EMA) to smooth the volatility. Taking the square of the returns means that you are asuming the expected returns are 0, which is how market operates for many assets (e.g. forex). In my experience, I have not seen much difference between using EMA or other moving average functions. The more important part is the span of those moving average functions. I usually use a span of 20, but this is another tuning parameter that you can play with.
Let \(\hat{\sigma}_t\) be the estimated volatility of the asset. The upper and lower barriers are calculated as follows:
where I consider \(\alpha\) as a tuning parameter. The higher the value of \(\alpha\), the less \(1\) and \(-1\) labels are generated.
For demonstration, I will code in R, since I am using Quarto to write this article, and it’s aesthetically pleasing to read. Let’s download Nikkei 225 data from Yahoo Finance and see how it works.
Implementation on R
library(tidyverse)library(quantmod)library(xts)# download datagetSymbols("^N225", from ="2017-01-01", to ="2017-12-31")
[1] "^N225"
colnames(N225) <-c("open", "high", "low", "close", "volume", "adjusted")# convert to tibble and drop missing valuesalpha <-3N225 <- N225 %>%as.data.frame() %>%mutate(date =index(N225)) %>%select(date, high, low, close) %>%drop_na() %>%# drop missing valuesmutate(returns = close /lag(close) -1) %>%mutate(volatility =EMA(returns **2, 20) **0.5) %>%mutate(u_bar = close * (1+ volatility * alpha), l_bar = close * (1- volatility * alpha)) %>%drop_na() %>%# drop missing valuesas_tibble() # convert to tibble# take a look at the datahead(N225)
Let’s visualize the data. At the moment I am testing out the library rtemis, a library for machine learning and visualization. It’s not on CRAN yet, but you can install it from GitHub.
# plotplot_ly(N225, x =~date, y =~close, type ="scatter", mode ="lines", name ="close", color =I(colorp[[24]])) %>%add_trace(y =~u_bar, mode ="lines", name ="upper barrier", line =list(dash ='dot'), color =I(colorp[[7]])) %>%add_trace(y =~l_bar, mode ="lines", name ="lower barrier", line =list(dash ='dot'), color =I(colorp[[16]])) %>%layout(title ="Nikkei 225", yaxis =list(title ="Price", type ='log'), xaxis =list(title ="Date"))