This week we are going to take a look at Tradingview’s built-in trailing stop loss functionality. In particular, we are going to focus to the mechanics of how the trailing stop loss works and how this can cause some serious expectation issues.
Disclaimer/Special Note: The issue discussed later in this article was present at the time of writing and in version 3 of pine script. Readers may wish to provide some counter arguments for why this may be a “non-issue”. If that is you, we would love to hear from you and will update the article accordingly if it is warranted.
Setting Up A Trailing Stoploss
Before we take a deeper look at the mechanics, we should first understand how to set up a trailing stop. Tradingview’s trailing stop functionality can be found within the
strategy.exit() function. This functionhas 3 parameters that allow us to influence when the trailing stop shall start and the how far it will trail.
Below is a snippet from the official docs:
trail_price (float) An optional parameter. Trailing stop activation level (requires a specific price). If it is specified, a trailing stop order will be placed when the specified price level is reached. The offset (in ticks) to determine initial price of the trailing stop order is specified in the ‘trail_offset’ parameter: X ticks lower than activation level to exit long position; X ticks higher than activation level to exit short position. The default value is ‘NaN’.trail_points (float) An optional parameter. Trailing stop activation level (profit specified in ticks). If it is specified, a trailing stop order will be placed when the calculated price level (specified amount of profit) is reached. The offset (in ticks) to determine initial price of the trailing stop order is specified in the ‘trail_offset’ parameter: X ticks lower than activation level to exit long position; X ticks higher than activation level to exit short position. The default value is ‘NaN’.trail_offset (float) An optional parameter. Trailing stop price (specified in ticks). The offset in ticks to determine initial price of the trailing stop order: X ticks lower than ‘trail_price’ or ‘trail_points’ to exit long position; X ticks higher than ‘trail_price’ or ‘trail_points’ to exit short position. The default value is ‘NaN’.
Just as with stop loss and limit orders, we have two parameters that can be used to set the level at which the trailing stop loss activates. This is either an actual price you want to target or a number of ticks measured from the entry level.
So what do we mean by activation level? Well, sometimes we don’t want a trailing stop to trail immediately as soon as we open a position. We might only want to trail once a certain amount of profit has been met. Setting an activation level allows us to do this.
Finally, we have the trailing offset, this is how far away from price we want to trail. This parameter must be specified in ticks. If ticks are a daunting subject for you, we are here to help! Check out the article: Tradingview: Working With Ticks
The following code example shows how to setup a trailing stop and provides some simple inputs which will allow you to play around with the various input parameters and subsequently, see how they affect the exits.
// Simple Example Code
// Designed to play around with Tradingview's built in Trailing Stop and
// understand the mechanics.
strategy("Trailing Stop Loss Testing", overlay=true)
testStartYear = input(2018, "Backtest Start Year")
testStartMonth = input(06, "Backtest Start Month")
testStartDay = input(06, "Backtest Start Day")
testType = input('Trail Points', "Test Points or Price", options=['Trail Points', 'Trail Price'])
trailLevel = input(1.34193, "Trail Price", type=float, step=0.0001)
trailPoints = input(150, "Trail Points (in ticks)", type=integer)
trailOffset = input(100, "Trail Offset (in ticks)", type=integer)
testPeriodStart = timestamp(testStartYear, testStartMonth, testStartDay, 0, 0)
longCondition = time >= testPeriodStart
strategy.entry("Go Long", strategy.long)
strategy.exit("Trailing Stop", "Go Long", trail_points= trailPoints, trail_offset= trailOffset, when= testType == 'Trail Points')
strategy.exit("Trailing Stop", "Go Long", trail_price= trailLevel, trail_offset= trailOffset, when= testType == 'Trail Price')
The code simply starts buying as soon as the backtest date is reached. Once you enter a position it will setup a trailing stop loss based on the options you specify. Depending on when you copy this code and what instrument you test on, the start date and price levels shall need to be updated accordingly.
Let’s take some time to look at each individual parameter and how it affects our exits. The following examples are aimed to be as simple as possible. As such, the code and examples should be run on FX markets. Why? As we discovered in the working with ticks article, a tick nicely equals one pipette. This will make things easier to measure and verify on the lower FX timeframes.
Trail Price + Trail Offset
In our first example, we will take a look at using the
trail_price parameter. As mentioned above, the trail price determines the level when the trailing stop becomes active. The
trail_offset then determines how much the stop trails below price.
As we can see in the chart, the trailing stop only becomes active once the trailing price is met. Following this, the trailing stop moves up with price following the
highof each bar until it is eventually hit.
Trail Points + Trail Offset
The second example follows exactly the same example but instead this time we use
trail_pointsto set our profit activation level.
As we can see in the second example, trail points are used in exactly the same way. We are able to match the previous example by measuring to our desired activation level and setting the number of ticks appropriately.
Next, try to adjust the offset. You will notice that the exit level moves up and down the wick as you adjust it. It will move up as you make the stop tighter and down as you loosen it off. Eventually, the exit will move to other bars if the settings are adjusted significantly.
Trailing Stop Potential Issues
Next, we move onto take a look at some mechanical issues which can lead to unrealistic expectations. To be fair, these are not issues per se. They are limitations from working with historical data and receiving all of that data at the same time. However, as we will see, I would prefer to see Tradingview take an alternative approach when deciding how to deal with this limitation.
The issue with the mechanics of the trailing stop is that Tradingview always assumes that price moved in your favour first during a bar. If you imagine we receive
closedata all at the same time. We don’t actually know if we hit the highs first or the lows. We also don’t know how many times it bounced between the two. To help visualize this, see below.
Tradingview always give you the benefit of the doubt. If you are in a long position, it will assume price hits the highs before the lows. If you are in a short position, it will assume it hit the lows before the highs. We can see this clearly in the following example:
Worse still, if you set the trail to 0. You will exit at the top/bottom of each wick, giving you maximum profits! I think this example best demonstrates why this is a problem.
Personally, I would prefer it to assume the opposite and take a pessimistic approach. If I am dealt the worst-case scenario and my strategy is still profitable, I know I am onto something. Instead, we can be lead to believe a strategy is very profitable when in fact, we would have very little chance replicating those results in the real world.
In the same way that Tradingview’s backtesting engine will exaggerate profits, it also minimizes losses and drawdown. If we never assume price moved against us first, our drawdown level may not look as bad as it actually would be if some trades go against us.
One final point, these little extra profits add up to a huge return over the course of 100 + trades. To see just how much this can impact your results, use the commission parameter to add a small commission to your strategy and see how that impacts profitability. A small deduction (or addition in the case of trailing stops) can go a long long way!