Dynamic Stop Loss & Take Profit

Why fixed SL/TP values break over time on indices — and three methods for building adaptive, future-proof targets.

Fixed stop loss and take profit values are among the most common and damaging mistakes in strategy development — particularly on indices, which trend upward over long periods and change their volatility characteristics significantly over time.

The core problem: a fixed point value represents a different fraction of an instrument's value at different price levels. A 50-point stop on the S&P 500 represented roughly 5% of the index in 2000. Today it represents less than 1%. A strategy using a 50-point stop was calibrated for an instrument near 1,000. Running that same strategy on an instrument near 5,500 means the stop is five times tighter relative to price — it will be hit on ordinary intraday noise that the 2000 version of the strategy would have survived.

This isn't a theoretical problem. It is why strategies that "worked great in the 2000s" often collapse when forward-tested. The instrument changed; the strategy did not adapt.

The three methods for dynamic SL/TP

Method 1: ATR-based

The most widely used approach. Stop and target distances are set as multiples of the current ATR(14). Since ATR measures how much the instrument is actually moving, a 2× ATR stop always means "two average bar ranges" — it scales with both the price level and the current volatility regime.

`stopDistance = ta.atr(14) * 2.0` `takeProfitDistance = ta.atr(14) * 4.0`

This is robust because it adapts to two things simultaneously: price level (ATR is denominated in price units, so it naturally grows as the instrument's price grows) and current volatility (a market in crisis has a much larger ATR than the same market during a quiet summer).

When to use it: general-purpose stops and targets, especially for strategies that will be run across long backtests or multiple instruments.

Method 2: Indicator range-based (code example above)

The distance between Bollinger Band® upper and lower bands at entry represents the current volatility range of the instrument — a measure of how much price swings within its "normal" range. Using this as the basis for SL/TP anchors the distances to a statistically derived volatility measure.

`bbRange = bbUpper - bbLower` `sl = close - bbRange * 0.75` `tp = close + bbRange * 1.5`

The Bollinger Band® width scales with price (since the underlying SMA scales with price) and with volatility (since the band width is standard deviation based). This makes it naturally self-adjusting across different market environments.

When to use it: mean-reversion strategies where Bollinger Bands® are already part of the signal logic — the SL/TP are then dimensionally consistent with the indicator generating the signal.

Method 3: Percentage-based

The simplest adaptive method: stops and targets are defined as a percentage of the entry price. A 1.5% stop on a $100 stock is $1.50; on a $5,000 index it's $75. The absolute dollar amount scales automatically with the instrument's price.

`sl = close * (1 - stopPercent / 100)` `tp = close * (1 + targetPercent / 100)`

When to use it: cross-instrument strategies where simplicity is more important than precise volatility calibration. Less precise than ATR but far more robust than fixed points.

How the three methods compare

| Method | Adapts to price level | Adapts to volatility regime | Complexity | |---|---|---|---| | ATR-based | Yes (ATR grows with price) | Yes (ATR tracks current ranges) | Low | | Indicator range | Yes (BB width scales with price) | Yes (BB width is StdDev based) | Medium | | Percentage | Yes (% of current price) | No (fixed % regardless of regime) | Very low |

For most strategies, ATR is the recommended starting point. It is conceptually simple, well-understood in the trading community, and gives genuinely good results across different instruments and time periods.

Validating across time periods

The clearest test of whether your SL/TP approach is adaptive is to split your backtest into time periods and look at how the stop and target distances behave:

  1. Run the backtest from 2000–2010. Note the average stop distance in points.
  2. Run the backtest from 2015–2024. Note the average stop distance in points.
  3. If using ATR or percentage-based methods, the average stop distance should be roughly proportional to the instrument's price level in each period.
  4. If using fixed points, the distance should be identical — and the strategy will be progressively more broken in the later period.

A well-calibrated dynamic strategy will show consistent risk-to-reward ratios and consistent profit factors across time periods. A fixed-point strategy will show degradation over time on any upward-trending instrument.

Common mistake: optimising fixed values on historical data

When a strategy uses fixed stops and targets, optimisation will find the values that worked best over the historical data set. But those values worked because the instrument happened to be in a particular price and volatility range during that test period. They are not reliable going forward.

Dynamic approaches remove this problem by definition — the values are derived from current conditions, not from an historical optimisation. This is why they are fundamentally more durable, even if the backtest results look less spectacular than a curve-fitted fixed-value system.

//@version=6
strategy("Dynamic SL/TP — Price Action Range", overlay=true)

// === Method 1: Indicator-Based Range ===
// Use the distance between Bollinger Band® upper and lower
// as a volatility-aware range for SL and TP

bbLen    = input.int(20, "BB Length")
bbMult   = input.float(2.0, "BB Multiplier")
tpMult   = input.float(1.5, "TP Multiplier (x range)")
slMult   = input.float(0.75, "SL Multiplier (x range)")

basis = ta.sma(close, bbLen)
dev   = bbMult * ta.stdev(close, bbLen)
upper = basis + dev
lower = basis - dev

// Dynamic range in points — scales with price level
bbRange = upper - lower

// Entry logic (simple EMA cross for demonstration)
fast = ta.ema(close, 9)
slow = ta.ema(close, 21)

longSignal  = ta.crossover(fast, slow)
shortSignal = ta.crossunder(fast, slow)

if longSignal and strategy.opentrades == 0
    sl = close - bbRange * slMult
    tp = close + bbRange * tpMult
    strategy.entry("Long", strategy.long)
    strategy.exit("Long Exit", "Long", stop=sl, limit=tp)

if shortSignal and strategy.opentrades == 0
    sl = close + bbRange * slMult
    tp = close - bbRange * tpMult
    strategy.entry("Short", strategy.short)
    strategy.exit("Short Exit", "Short", stop=sl, limit=tp)

// Visualise the dynamic range
plot(upper, "BB Upper", color=color.new(color.gray, 60))
plot(lower, "BB Lower", color=color.new(color.gray, 60))