Creating the Trailing Stop Indicator in Python.
How to Create a Trailing Stop Indicator Using Python for Trading.
Trading is a combination of four things, research, implementation, risk management, and post-trade evaluation. The bulk of what we spend our time doing is the first two, meaning that we spend the vast majority of the time searching for a profitable strategy and implementing it (i.e. trading). However, we forget that the pillar of trading is not losing money. It is even more important than gaining money because it is fine to spend time trading and still have the same capital or slightly less than to spend time trading and find yourself wiped out.
In this article, we will discuss the third pillar as a way of enhancing returns and capital protection. Every trading strategy must be accompanied by its own personalized risk management protocol.
I have just released a new book after the success of my previous one “The Book of Trading Strategies”. It features advanced trend-following indicators and strategies with a GitHub page dedicated to the continuously updated code. Also, this book features the original colors after having optimized for printing costs. If you feel that this interests you, feel free to visit the below Amazon link, or if you prefer to buy the PDF version, you could contact me on LinkedIn.
The Average True Range
To understand the Average True Range, we must first understand the concept of Volatility. It is a key concept in finance, whoever masters it holds a tremendous edge in the markets.
Unfortunately, we cannot always measure and predict it with accuracy. Even though the concept is more important in options trading, we need it pretty much everywhere else. Traders cannot trade without volatility nor manage their positions and risk. Quantitative analysts and risk managers require volatility to be able to do their work. Before we discuss the different types of volatility, why not look at a graph that sums up the concept? Check out the below image to get you started.
You can code the above in Python yourself using the following snippet:
# Importing the necessary libraries
import numpy as np
import matplotlib.pyplot as plt
# Creating high volatility noise
hv_noise = np.random.normal(0, 1, 250)
# Creating low volatility noise
lv_noise = np.random.normal(0, 0.1, 250)
# Plotting
plt.plot(hv_noise, color = 'red', linewidth = 1.5, label = 'High Volatility')
plt.plot(lv_noise, color = 'green', linewidth = 1.5, label = 'Low Volatility')
plt.axhline(y = 0, color = 'black', linewidth = 1)
plt.grid()
plt.legend()
The different types of volatility around us can be summed up in the following:
Historical volatility: It is the realized volatility over a certain period of time. Even though it is backward looking, historical volatility is used more often than not as an expectation of future volatility. One example of a historical measure is the standard deviation, which we will see later. Another example is the Average True Range, the protagonist of this article.
Implied volatility: In its simplest definition, implied volatility is the measure that when inputted into the Black-Scholes equation, gives out the option’s market price. It is considered as the expected future actual volatility by market participants. It has one time scale, the option’s expiration.
Forward volatility: It is the volatility over a specific period in the future.
Actual volatility: It is the amount of volatility at any given time. Also known as local volatility, this measure is hard to calculate and has no time scale.
Volatility is the average distance away from the mean that we expect to find when we analyze the different components of the time series.
In technical analysis, an indicator called the Average True Range -ATR- can be used as a gauge for historical volatility. Although it is considered as a lagging indicator, it gives some insights as to where volatility is now and where has it been last period (day, week, month, etc.).
But first, we should understand how the True Range is calculated (the ATR is just the average of that calculation). Consider an OHLC data composed of an timely arranged open, high, low, and close prices. For each time period (bar), the true range is simply the greatest of the three price differences:
High — Low
High — Previous close
Previous close — Low
Once we have got the maximum out of the above three, we simply take a smoothed average of n periods of the true ranges to get the average true range. Generally, since in periods of panic and price depreciation we see volatility go up, the ATR will most likely trend higher during these periods, similarly in times of steady uptrends or downtrends, the ATR will tend to go lower. One should always remember that this indicator is lagging and therefore has to be used with extreme caution.
Since it has been created by Welles Wilder Jr., also the creator of the relative strength index, it uses Wilder’s own type of moving average, the smoothed kind. To simplify things, the smoothed moving average can be found through a simple transformation of the exponential moving average.
The above formula means that a 100 smoothed moving average is the same thing as (100 x 2) -1 = 199 exponential moving average. While we are on that, we can code the exponential moving average using this function:
def adder(Data, times):
for i in range(1, times + 1):
new = np.zeros((len(Data), 1), dtype = float)
Data = np.append(Data, new, axis = 1)
return Data
def deleter(Data, index, times):
for i in range(1, times + 1):
Data = np.delete(Data, index, axis = 1)
return Data
def jump(Data, jump):
Data = Data[jump:, ]
return Data
def ma(Data, lookback, close, where):
Data = adder(Data, 1)
for i in range(len(Data)):
try:
Data[i, where] = (Data[i - lookback + 1:i + 1, close].mean())
except IndexError:
pass
# Cleaning
Data = jump(Data, lookback)
return Data
def ema(Data, alpha, lookback, what, where):
alpha = alpha / (lookback + 1.0)
beta = 1 - alpha
# First value is a simple SMA
Data = ma(Data, lookback, what, where)
# Calculating first EMA
Data[lookback + 1, where] = (Data[lookback + 1, what] * alpha) + (Data[lookback, where] * beta)
# Calculating the rest of EMA
for i in range(lookback + 2, len(Data)):
try:
Data[i, where] = (Data[i, what] * alpha) + (Data[i - 1, where] * beta)
except IndexError:
pass
return Data
And now, to calculate the average true range, we can define the below function:
def atr(data, lookback, high, low, close, where):
# adding columns
data = adder(data, 2)
# true range
for i in range(len(data)):
try:
data[i, where] = max(data[i, high] - data[i, low], abs(data[i, high] - data[i - 1, close]), abs(data[i, low] - data[i - 1, close]))
except ValueError:
pass
data[0, where] = 0
# average true range
data = ema(data, 2, (lookback * 2) - 1, where, where + 1)
# Cleaning
data = deleter(data, where, 1)
data = jump(data, lookback)
return data
The Trailing Stop Indicator
The indicator as its name suggests gives us a trailing stop that helps us in out trend-following strategies. Naturally, when we initiate a trend-following position, we are hoping to squeeze out the most of the underlying move and ride it until it ends. Unfortunately, it is extremely complicated to sell exactly at the end of the move. Therefore, we can just move up our stop as the position goes in our favor. Here is a simple illustration:
A buy position is opened on the EURUSD at 1.1000 in expectation that the bullish move will continue. We place the stop at 1.0900 and no take-profit order as we want to remain long as much as possible.
Two days later, the EURUSD is trading at 1.1100. This is a 0.90% increase that we would like to keep at least some of it and to lock in some profits. Therefore, we move our stop to 1.1010. Now, we have a position that is up 100 pips and at worst case will be closed at a profit of 10 pips because we have moved our stop from 1.0900 to 1.1010 in order to lock it.
Five days later the EURUSD is trading at 1.1300. We have bought initially at 1.1000. Therefore, the market is up 2.72% since opening the position. We can move our stop to 1.1200 to lock in at least a 1.81% profit.
Finally, a day after reaching 1.1300, the EURUSD corrects to 1.1800. Our stop is triggered at 1.1200 with a profit of 2.72%.
The trailing stop indicator is an overlay moving line that gives us where exactly we must move our stop when following the move. It is based on volatility, thus uses the average true range. The default version uses 13-period ATR and a multiplier of 3. The multiplier can be thought of as an ingredient in determining the position of the new stop.
def atr_trailing_stop(data, atr_column, multiplier, close, where):
# adding columns
data = adder(data, 1)
# atr trailing stop
for i in range(len(data)):
try:
# stop
stop = multiplier * data[i, atr_column]
if data[i, close] > data[i - 1, where] and data[i - 1, close] > data[i - 1, where]:
data[i, where] = max(data[i - 1, where], data[i, close] - stop)
elif data[i, close] < data[i - 1, where] and data[i - 1, close] < data[i - 1, where]:
data[i, where] = min(data[i - 1, where], data[i, close] + stop)
elif data[i, close] > data[i - 1, where] and data[i - 1, close] < data[i - 1, where]:
data[i, where] = data[i, close] - stop
elif data[i, close] < data[i - 1, where] and data[i - 1, close] > data[i - 1, where]:
data[i, where] = data[i, close] + stop
except ValueError:
pass
return data
The indicator can also be used to determine changes in the market regime which is very useful for trend-following. It however needs to be optimized in every market.
Trailing stops are indispensable in trend-following but they must be used correctly. In a future article, we will see how to use this indicator to determine the market regime.
If you are also interested by more technical indicators and strategies, then my book might interest you:
Conclusion
Remember to always do your back-tests. You should always believe that other people are wrong. My indicators and style of trading may work for me but maybe not for you.
I am a firm believer of not spoon-feeding. I have learnt by doing and not by copying. You should get the idea, the function, the intuition, the conditions of the strategy, and then elaborate (an even better) one yourself so that you back-test and improve it before deciding to take it live or to eliminate it. My choice of not providing specific Back-testing results should lead the reader to explore more herself the strategy and work on it more.
To sum up, are the strategies I provide realistic? Yes, but only by optimizing the environment (robust algorithm, low costs, honest broker, proper risk management, and order management). Are the strategies provided only for the sole use of trading? No, it is to stimulate brainstorming and getting more trading ideas as we are all sick of hearing about an oversold RSI as a reason to go short or a resistance being surpassed as a reason to go long. I am trying to introduce a new field called Objective Technical Analysis where we use hard data to judge our techniques rather than rely on outdated classical methods.