How to Code A Trailing Stop Loss in Pine Script

This lesson demonstrates various methods we can use to code trailing stops in Pine to lock in profits - including ATR and Percentage-based variants.

Check out my other free lessons!

Source Code

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ZenAndTheArtOfTrading / PineScriptMastery.com
// @version=5
strategy("www.PineScriptMastery.com", overlay=true)

// Get user input
tradeType           = input.string(title="Trade Direction", defval="Long", options=["Long", "Short"], confirm=true)
trailSource         = input.string(title="Trail Source", defval="Lows/Highs", options=["Lows/Highs", "Close", "Open"], confirm=true)
trailMethod         = input.string(title="Trail Method", defval="ATR", options=["ATR", "Percent"], confirm=true)
trailPercent        = input.float(title="Trail Percent", defval=10, minval=0.1, confirm=true)
swingLookback       = input.int(title="Lookback", defval=7, confirm=true)
atrPeriod           = input.int(title="ATR Period", defval=14, confirm=true)
atrMultiplier       = input.float(title="ATR Multiplier", defval=1.0, confirm=true)
barTime             = input.time(title="Bar Time", defval=timestamp("01 Jan 2021 13:30 +0000"), confirm=true)

// Enter mock trade (if bar is the one we clicked on and we have no open trades or previous trades)
if time >= barTime and strategy.position_size == 0 and strategy.closedtrades == 0
    strategy.entry("Trade", direction = (tradeType == "Long" ? strategy.long : strategy.short))

// Declare trailing price variable (stores our trail stop value)
var float trailPrice    = na
float next_trailPrice   = na

// Get required trailing stop variables
atrValue                = ta.atr(atrPeriod) * atrMultiplier
float swingLow          = ta.lowest(low, swingLookback)
float swingHigh         = ta.highest(high, swingLookback)

// Get trailing stop price
if trailMethod == "ATR" // Determine ATR trailing stop price based on price source
    next_trailPrice := switch trailSource
        "Close"     => strategy.position_size > 0 ? close - atrValue : close + atrValue
        "Open"      => strategy.position_size > 0 ? open - atrValue : open + atrValue
        => strategy.position_size > 0 ? swingLow - atrValue : swingHigh + atrValue
else if trailMethod == "Percent" // Determine percentage trailing stop price based on price source
    float percentMulti = strategy.position_size > 0 ? (100 - trailPercent) / 100 : (100 + trailPercent) / 100
    next_trailPrice := switch trailSource
        "Close"     => close * percentMulti
        "Open"      => open * percentMulti
        => strategy.position_size > 0 ? swingLow * percentMulti : swingHigh * percentMulti

// Check for trailing stop update
if strategy.position_size != 0 and barstate.isconfirmed
    // Trail long stop ONLY IF temp trailPrice is not set or is a higher price
    if (next_trailPrice > trailPrice or na(trailPrice)) and strategy.position_size > 0
        trailPrice := next_trailPrice
        // Trigger alert
        alert(message="Trailing Stop updated for " + syminfo.tickerid + ": " + str.tostring(trailPrice, "#.#####"), freq=alert.freq_once_per_bar_close)
    // Trail short stop ONLY IF temp trailPrice is not set or is a lower price
    if (next_trailPrice < trailPrice or na(trailPrice)) and strategy.position_size < 0
        trailPrice := next_trailPrice
        // Trigger alert
        alert(message="Trailing Stop updated for " + syminfo.tickerid + ": " + str.tostring(trailPrice, "#.#####"), freq=alert.freq_once_per_bar_close)

// Draw data to chart
plot(strategy.position_size != 0 ? trailPrice : na, color=color.red, title="Trailing Stop")

// Exit trade if stop is hit
strategy.exit(id="Trail Exit", from_entry="Trade", limit=na, stop=trailPrice)

//if (strategy.position_size > 0 and close < trailPrice) or (strategy.position_size < 0 and close > trailPrice)
//    strategy.close("Trade")