Using Percentiles to Generate Trading Signals
Creating an Indicator that Uses Percentiles to Derive Trading Signals
Statistical concepts are always present in trading whether in risk management or in strategy elaboration. Percentiles form one of the pillars of descriptive statistics, but are they helpful in trading? This article will discuss the use of percentiles to generate trades.
I have just published a new book after the success of New Technical Indicators in Python. It features a more complete description and addition of complex trading strategies with a Github page dedicated to the continuously updated code. If you feel that this interests you, feel free to visit the below link, or if you prefer to buy the PDF version, you could contact me on Linkedin.
The Concept of Percentiles
A percentile is simply a threshold where a certain percentage of value lies beneath the current value. Here is a more detailed example:
If your score in the CFA level II exam is around the 90th percentile, it means that you have scored better than 90% of the candidates.
If the closing price on the EURUSD lies in the 100th percentile with regards to the last 10 price bars, then it means that the closing price is the highest among the latest 10 values.
It is common in statistics to refer to the 25th percentile as a Quartile, the 50th percentile as the Median, and the 75th percentile as the Third Quartile.
The Percentile Range Indicator
The Percentile Range Indicator will loop around the market price and simply output the percentile of the current market price with respect to the selected lookback period. For example, consider the following three latest values:
When we re-arrange them from smallest to largest, we get the following table:
We find that our latest value 1.42848 ranks in the middle. This is the equivalent of 50th percentile. So, we can also say that 1.42848 is the Median in this range of three values.
The idea of the indicator is to do exactly the above but with a wider lookback periods. This can help us generate signals around the extremes.
To create the Percentile Range Indicator, we need to have an OHLC data array with an extra empty column. This can be done by using the following code:
# Defining the function that adds a certain number of columns def adder(Data, times): for i in range(1, times + 1): z = np.zeros((len(Data), 1), dtype = float) Data = np.append(Data, z, axis = 1)
# Adding 1 extra column my_data = adder(my_data, 1)
Next, we should import the necessary libraries:
import numpy as np from scipy import stats
And finally, simply defining the function that calculates the percentiles on a rolling basis:
def percentile_range_indicator(Data, lookback, what, where):
for i in range(len(Data)): Data[i, where] = stats.percentileofscore(Data[i - lookback + 1:i, what], Data[i, what]) return Data
# Using the function my_data = percentile_range_indicator(my_data, 14, 3, 4)
Even though we will not be back-testing the below idea (rather, we will back-test the above raw indicator), it is also interesting to know that we can apply a 3-period moving average on the indicator to smooth it out and to use it discretionary trading. The code for the moving average can be written down as the following:
def ma(Data, lookback, what, where): for i in range(len(Data)): try: Data[i, where] = (Data[i - lookback + 1:i + 1, what].mean()) except IndexError: pass return Data
If we want to keep only the moving average, we can get the above smoothed plot:
Back-testing a Simple Strategy
As with any proper research method, the aim is to back-test the indicator and to be able to see for ourselves whether it is worth having as an add-on to our pre-existing trading framework. Note that the below only back-tests one time frame on only 10 currency pairs for the last 10 years. It is possible that this is not the optimal time frame for the strategy, but we are just trying to find a one-shoe-size-almost-fits-all strategy.
The conditions are as expected:
Go long (Buy) whenever the Percentile Range Indicator reaches 0.00 with the two previous values greater than 0.00. Hold this position until getting a new signal (where the position is closed).
Go short (Sell) whenever the Percentile Range Indicator reaches 100 with the two previous values below 100. Hold this position until getting a new signal (where the position is closed).
We want to input 1’s in the column we call “buy” and -1 in the column we call “sell”. This later allows you to create a function that calculates the profit and loss by looping around these two columns and taking differences in the market price to find the profit and loss of a close-to-close strategy. Then you can use a risk management function that uses stops and profit orders.
The below is an example of a signal chart with the green arrows as the buy triggers and red arrows as the sell short triggers.
def signal(Data, what, buy, sell): for i in range(len(Data)): if Data[i, what] == lower_barrier and Data[i - 1, what] > lower_barrier and Data[i - 2, what] > lower_barrier : Data[i, buy] = 1 if Data[i, what] == upper_barrier and Data[i - 1, what] < upper_barrier and Data[i - 2, what] < upper_barrier : Data[i, sell] = -1
No risk management technique will be used for this back-test. The transaction costs amount to 0.5 pips per round trade. The data back-tested is hourly since January 2010.