Discover more from All About Trading!
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.
Knowledge must be accessible to everyone. This is why, from now on, a purchase of either one of my new books “Contrarian Trading Strategies in Python” or “Trend Following Strategies in Python” comes with free PDF copies of my first three books (Therefore, purchasing one of the new books gets you 4 books in total). The two new books listed above feature a lot of advanced indicators and strategies with a GitHub page. You can use the below link to purchase one of the two books (Please specify which one and make sure to include your e-mail in the note).
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)
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)
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
Check out my weekly market sentiment report to understand the current positioning and to estimate the future direction of several major markets through complex and simple models working side by side. Find out more about the report through this link that covers the analysis between 07/08/2022 and 14/08/2022:
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 want to see how to create all sorts of algorithms yourself, feel free to check out Lumiwealth. From algorithmic trading to blockchain and machine learning, they have hands-on detailed courses that I highly recommend.
Learn Algorithmic Trading with Python Lumiwealth
Learn how to create your own trading algorithms for stocks, options, crypto and more from the experts at Lumiwealth. Click to learn more
To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.
I recommend you always follow the the below steps whenever you come across a trading technique or strategy:
Have a critical mindset and get rid of any emotions.
Back-test it using real life simulation and conditions.
If you find potential, try optimizing it and running a forward test.
Always include transaction costs and any slippage simulation in your tests.
Always include risk management and position sizing in your tests.
Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.