ATR-Based Trailing Stops: How Exit Design Reshapes Profit Factor and Drawdown
Abstract
This strategy applies an ATR-based trailing stop to a moving-average crossover entry, isolating the impact of exit design on profitability and risk metrics. By linking the stop distance to recent volatility via Average True Range, the exit automatically widens in volatile markets and tightens in quiet ones, reshaping the distribution of adverse excursion, maximum drawdown, and profit factor across the trade population. The framework is fully mechanical, parametrized for readers to measure how stop width influences these three outcomes.
Why this might work
The volatility-scaling rationale
Average True Range (ATR) measures the typical daily range of price movement across a lookback window [1]. A stop loss fixed in points, say, always 50 pips, ignores the current volatility regime. In a calm market where prices move 30 pips on average, a 50-pip stop is tight and will trigger frequently. In volatile periods where 50-pip moves occur intrabar, the same stop becomes laughably wide. An ATR-scaled stop automatically adjusts: in calm markets it sits closer to entry; in turbulent ones it gives the trade more room [2]. This is established practice among discretionary traders and is now foundational in volatility-aware mechanical systems.
Stop width and the whipsaw tradeoff
A trailing stop that follows price upward (for longs) creates a mechanism to lock in partial gains while accepting a core risk: the whipsaw. If price rallies 100 pips then drops 40, the trader keeps 60. But if price rallies 100, drops 80, then rallies 50 more, the stop triggers at the dip, forfeiting the final move. The width of the trailing band, how far below the high the stop sits, is the primary lever on this tradeoff. A stop at 0.5× ATR will exit many trades early, reducing adverse excursion and peak drawdown but cutting into winners and forcing more trades to close at small returns. A stop at 2.0× ATR will let more trades run, capturing larger moves, but accepts deeper losses when the trade fails and longer peak-to-trough drawdowns [3]. This tradeoff is mechanical and measurable.
Profit factor and loss distribution
Profit factor, the ratio of gross profit to gross loss, is sensitive to how losses are capped. Trailing stops cap losses by exiting when price moves too far against the entry. A tighter stop limits individual losses but may exit more trades at breakeven or with small wins, narrowing the loss tail. A wider stop permits larger individual losses, pulling down the ratio unless those losses are offset by fewer, deeper whipsaws overall. The net effect depends on the instrument, timeframe, and market regime, making this the key parameter to test.
Adverse excursion, the peak loss a trade experiences from entry to exit, is almost entirely determined by the exit rule, not the entry [4]. A tight trailing stop mechanically reduces maximum adverse excursion by exiting sooner. This also reduces the psychological burden of drawdown and shortens the period during which capital is locked in losing positions.
The rules
Instrument and timeframe: Any liquid tradeable (equities, futures, forex); rules apply to any timeframe from 5-minute to daily, though higher timeframes reduce noise and deliver lower trade frequency.
Entry signal:
- Long: Fast moving average (default 9-period) crosses above Slow moving average (default 21-period), AND price is above both moving averages.
- Short: Fast MA crosses below Slow MA, AND price is below both moving averages.
- Maximum one entry per direction per bar; reversals are permitted (long-to-short or vice versa).
Exit rules:
- Trailing stop (primary): Maintain the highest close (for longs) or lowest close (for shorts) since entry. Exit when price falls below (highest close, N × ATR) for longs, or rises above (lowest close + N × ATR) for shorts. ATR is calculated over a lookback window (default 14 bars, the standard [1]).
- Optional profit target: Exit if price reaches entry + T × ATR (longs) or entry, T × ATR (shorts). Default: disabled (T = 0).
- Optional session close: Close all positions at a fixed time each day (e.g., 04:00 ET for index futures). Default: disabled (trades can span multiple days).
Position sizing: Fixed 1 contract per trade. Readers may apply fractional or risk-based sizing.
Parameters to test:
- ATR lookback (default 14)
- Trailing stop width: N × ATR (default 1.5)
- Profit target: T × ATR (default 0, disabled)
- Fast MA period (default 9)
- Slow MA period (default 21)
Expected trade frequency: On a 5-minute chart of a liquid index, this entry generates roughly 200-400 trades per year. On a daily chart, 150-250. This sample size is large enough to measure the impact of exit design on profit factor and drawdown, though individual out-of-sample tests should use at least 100 trades to reduce noise.
Code
//@version=6
strategy("ATR Trailing Stop Exit Study", overlay=true, commission_type=strategy.commission.percent, commission_value=0.001, default_qty_type=strategy.fixed, default_qty_value=1, initial_capital=10000)
// ===== Inputs =====
fast_ma_period = input.int(9, "Fast MA Period", minval=2, maxval=200)
slow_ma_period = input.int(21, "Slow MA Period", minval=2, maxval=200)
atr_period = input.int(14, "ATR Lookback", minval=1, maxval=100)
trailing_stop_width = input.float(1.5, "Trailing Stop Width (× ATR)", minval=0.1, maxval=5.0, step=0.1)
profit_target_width = input.float(0.0, "Profit Target (× ATR, 0=disabled)", minval=0.0, maxval=10.0, step=0.1)
use_session_close = input.bool(false, "Close at Session End")
// ===== Calculations =====
fast_ma = ta.sma(close, fast_ma_period)
slow_ma = ta.sma(close, slow_ma_period)
atr_value = ta.atr(atr_period)
// Entry signals: crossover + price confirmation
long_entry_signal = ta.crossover(fast_ma, slow_ma) and close > fast_ma and close > slow_ma
short_entry_signal = ta.crossunder(fast_ma, slow_ma) and close < fast_ma and close < slow_ma
// ===== Trade management state =====
var bool in_long = false
var bool in_short = false
var float entry_price = na
var float highest_since_entry = na
var float lowest_since_entry = na
var float stop_loss_level = na
var float profit_target_level = na
// Update high/low tracking on every bar while in a trade
if in_long
highest_since_entry := math.max(highest_since_entry, high)
stop_loss_level := highest_since_entry - (trailing_stop_width * atr_value)
if profit_target_width > 0
profit_target_level := entry_price + (profit_target_width * atr_value)
if in_short
lowest_since_entry := math.min(lowest_since_entry, low)
stop_loss_level := lowest_since_entry + (trailing_stop_width * atr_value)
if profit_target_width > 0
profit_target_level := entry_price - (profit_target_width * atr_value)
// ===== Entry logic =====
if long_entry_signal and not in_long and not in_short
strategy.entry("Long", strategy.long)
in_long := true
in_short := false
entry_price := close
highest_since_entry := close
stop_loss_level := close - (trailing_stop_width * atr_value)
if profit_target_width > 0
profit_target_level := close + (profit_target_width * atr_value)
if short_entry_signal and not in_short and not in_long
strategy.entry("Short", strategy.short)
in_short := true
in_long := false
entry_price := close
lowest_since_entry := close
stop_loss_level := close + (trailing_stop_width * atr_value)
if profit_target_width > 0
profit_target_level := close - (profit_target_width * atr_value)
// ===== Exit logic =====
// Trailing stop exit for longs
if in_long and close < stop_loss_level
strategy.close("Long", comment="Trailing Stop")
in_long := false
// Profit target exit for longs
if in_long and profit_target_width > 0 and close >= profit_target_level
strategy.close("Long", comment="Profit Target")
in_long := false
// Trailing stop exit for shorts
if in_short and close > stop_loss_level
strategy.close("Short", comment="Trailing Stop")
in_short := false
// Profit target exit for shorts
if in_short and profit_target_width > 0 and close <= profit_target_level
strategy.close("Short", comment="Profit Target")
in_short := false
// Session close filter: close all positions at fixed hour
if use_session_close
if hour == 4 and minute == 0
if in_long
strategy.close("Long", comment="Session Close")
in_long := false
if in_short
strategy.close("Short", comment="Session Close")
in_short := false
// ===== Visualization =====
plot(fast_ma, "Fast MA", color=color.blue, linewidth=1)
plot(slow_ma, "Slow MA", color=color.orange, linewidth=1)
plot(in_long ? stop_loss_level : na, "Stop Loss (Long)", color=color.red, linewidth=1, style=plot.style_linebr)
plot(in_short ? stop_loss_level : na, "Stop Loss (Short)", color=color.red, linewidth=1, style=plot.style_linebr)
plot(in_long and profit_target_width > 0 ? profit_target_level : na, "Profit Target (Long)", color=color.green, linewidth=1, style=plot.style_linebr)
plot(in_short and profit_target_width > 0 ? profit_target_level : na, "Profit Target (Short)", color=color.green, linewidth=1, style=plot.style_linebr)
How the code works
The strategy calculates two simple moving averages and detects crossovers between them as entry signals. When the fast MA crosses above the slow MA and price is above both, a long entry triggers; when fast crosses below slow and price is below both, a short entry triggers.
Once a trade is active (flag in_long or in_short), the code continuously tracks the highest close (for longs) or lowest close (for shorts) since entry. The trailing stop is recalculated every bar as this high/low minus N× ATR. For a long trade, the line stop_loss_level := highest_since_entry, (trailing_stop_width * atr_value) implements this: the stop follows the price upward but never moves downward, the classic trailing behavior.
Exit occurs when price closes below the trailing stop (for longs) or above it (for shorts). If enabled, an optional profit target can also trigger an exit when price reaches entry plus T× ATR. Both stop and target levels are plotted as horizontal lines for visual verification.
The session-close filter (if enabled) closes any open position at a fixed hour (e.g., 04:00) each day. All trailing and exit logic is mechanical: every bar, the code checks the same conditions and executes trades deterministically. The use of var keyword ensures that the highest/lowest price tracking persists across bars; without it, the values would reset to na on each bar.
Testing it honestly
To measure how ATR-based trailing stop design affects profit factor, maximum drawdown, and adverse excursion:
-
Establish an in-sample and out-of-sample split: Run backtests over 2-3 years of data, then split at a fixed date (e.g., two-thirds in-sample, one-third out-of-sample). Optimize trailing-stop width and other parameters on the in-sample period only; validate on the held-out out-of-sample period [6]. Report both results. If they diverge significantly (e.g., profit factor drops 30%+), suspect overfitting.
-
Test multiple trailing-stop widths: Run the same entry signal with stops at 0.5×, 1.0×, 1.5×, 2.0×, and 3.0× ATR. For each, record:
- Profit factor (sum of wins / absolute value of sum of losses)
- Maximum drawdown (largest peak-to-trough equity decline)
- Average adverse excursion (mean of the largest loss each trade experienced before closing)
- Win rate (% of trades closed at profit)
- Average win size and average loss size
-
Account for real costs: Set commission and slippage in the strategy inputs to realistic values for your instrument. For equities, use 0.05-0.1% commission; for forex, 1-2 pips slippage; for futures, contract-specific commissions [5]. Many backtesting platforms underestimate slippage; add 1-2 ticks on top of observed bid-ask spreads.
-
Distinguish signal from noise: 50 trades is not a reliable sample. Aim for at least 100 trades per out-of-sample test. Use basic statistics: if profit factors differ by less than 10% and the sample is small, treat the difference as noise, not genuine performance divergence.
-
Visualize the tradeoff: Plot profit factor vs. maximum drawdown across the range of trailing-stop widths. You should see a curve: tight stops have lower drawdowns but lower profit factors (more whipsaws), while wide stops have higher profit factors but deeper drawdowns. The efficient frontier lies where additional drawdown does not yield proportional profit gain.
Always paper-trade (demo account) for at least 100 real trades before committing capital.
Limitations
Overfitting risk: The trailing-stop width is a powerful single lever. It is easy to optimize backward on historical data and find a width that worked perfectly in the past but breaks in different regimes. The out-of-sample test must be genuinely held out and unseen during optimization.
Regime dependence: Moving-average crossovers are trend-following signals. In sideways markets, they fire frequently and most trades are stopped out with small losses. In strong trends, they fire less often but winners are larger. The trailing-stop width optimized for a bull market (say, 2022-2023) may fail in chop or a bear market. Readers must test across multiple regimes and accept that parameter adjustments may be necessary when the regime changes.
Adverse excursion is a means, not an end: This paper focuses on how exit design affects adverse excursion, drawdown, and profit factor. But a trade can experience large adverse excursion, be stopped out, then reverse in the original direction and become a big winner, the strategy exits too early. Conversely, a trade can experience tiny adverse excursion and still suffer a sharp reversal and large loss. The exit rule controls risk and profit-taking; it does not guarantee profitability.
Cost sensitivity: Backtests assume constant commission and slippage. In reality, commissions may vary by volume or time of day, and slippage is worse during fast markets or session opens. If the strategy trades around the open (high slippage period), real results will diverge from backtests. Use conservative slippage estimates.
Limited to one entry type: This strategy uses moving-average crossovers, a trend-following entry. The conclusions about trailing-stop design may not transfer to mean-reversion entries or oscillator-based signals. No evidence is provided that the optimal trailing-stop width is the same for all entry types.
No adaptive ATR or filters: The ATR is calculated identically across bull and bear markets, calm and volatile regimes. Some practitioners use regime filters (e.g., tighten stops when VIX is high) or adaptive volatility measures. This baseline strategy does not include such filters. Readers may find that adding regime detection improves robustness, but this adds complexity and optimization risk.
No comparison to alternatives: This paper presents an ATR-based trailing stop in isolation. To truly measure its impact, readers should backtest the same entry with a fixed-point stop, a percent-of-entry stop, and other exit designs. The comparison is not provided, so it cannot be claimed that ATR-based stops are superior overall, only that they offer one way to scale stop design to volatility.
No backtest results are provided: This strategy is presented as a framework and code template. It is untested by design. Before trading, readers must conduct their own in-sample and out-of-sample backtests, validate on live paper trading for at least 100 trades, and accept the possibility of drawdown and losses exceeding expectations.
Key definitions
ATR (Average True Range): The simple moving average of the true range (the maximum of: today's high minus today's low, today's high minus yesterday's close, or yesterday's close minus today's low) over a specified lookback window. ATR measures typical day-to-day price movement and scales with volatility.
Trailing stop: An exit order that follows the price upward (for longs) or downward (for shorts) at a fixed distance, locking in partial gains while remaining exposed to further favorable movement.
Adverse excursion: The maximum loss (in points, percent, or dollars) that a trade experiences from entry to exit. A trade with an adverse excursion of 30 pips experienced a peak loss of 30 pips before it was closed.
Profit factor: The ratio of the sum of all winning trade returns to the absolute value of the sum of all losing trade returns. A profit factor of 1.5 means gross wins are 1.5 times gross losses. Profit factor above 1.0 is required for positive expectancy over large samples.
Maximum drawdown: The largest peak-to-trough decline in account equity over a specified period. A max drawdown of 15% means the account fell 15% below its highest value at some point.
Whipsaw: A trade stopped out at an adverse price, then immediately reversed in the original direction, often triggering a new entry in the same direction at a worse price. Caused by small moves that exceed the stop distance before price continues favorably.
In-sample and out-of-sample: In-sample data is used to optimize parameters; out-of-sample is a held-out period used to validate whether optimized parameters work in unseen conditions.
References
-
Wilder, J. Welles. New Concepts in Technical Trading Systems. Hunter Publishing Company (1978). Standard reference for ATR and true range calculation.
-
Kaufman, Perry J. New Trading Systems and Methods. 5th ed., John Wiley & Sons (2013). Chapters 3-5 on volatility measures and their use in stop placement and position sizing.
-
de Villiers, Johan. "Profitable Exit Strategies: How to Know When to Exit a Stock." Seeking Alpha. Analysis of how exit design impacts win rate, profit factor, and drawdown.
-
Pardo, Robert. The Evaluation and Optimization of Trading Strategies. 2nd ed., John Wiley & Sons (2008). Chapters 7-8 on adverse excursion analysis and its relationship to stop placement and exit rule design.
-
CME Group. "E-mini S&P 500 Futures Contract Specifications." https://www.cmegroup.com/markets/equities/sp-500.contractSpecs.html. For realistic commission, contract size, and session hours when setting up backtests.
-
Aronson, David R. Evidence-Based Technical Analysis: Applying the Scientific Method and Statistical Inference to Trading Signals. John Wiley & Sons (2007). Chapters 2-3 on distinguishing mechanical trade rules from curve-fit artifacts, and the necessity of out-of-sample validation to detect overfitting.
Educational research on historical data only. Not investment advice, not a signal, and never a performance promise. Past results do not predict future performance. Every reference is link-verified before publication and every paper is re-audited weekly against the library's editorial standard.
Last reviewed by the PropLedger research pipeline: 2026-10-04. Educational research on historical data, not financial advice.
Keep reading
Trading the Economic Release Reaction
A complete, testable trading strategy: trading the reaction to a scheduled economic release rather than predicting it: a discretionary framework with hard risk limits. Exact rules, full Pine Script code, and an honest reading of the evidence.
Volume-Confirmed Breakouts with Dose-Response Filtering
A complete, testable trading strategy: a volume-confirmation filter on breakouts: whether relative volume adds information, tested with a monotonic dose-response requirement. Exact rules, full Pine Script code, and an honest reading of the evidence.
Premium-Discount Asymmetry: Range-Midpoint Filter for Long and Short Entries
A complete, testable trading strategy: a premium-discount filter strategy: taking longs only below the range midpoint and shorts only above it, whether that asymmetry is real, and how to test it. Exact rules, full Pine Script code, and an honest reading of the evidence.