Skip to content

How to exit trades with a stop-loss order in Pine Script?

How to exit trades with a stop-loss order in Pine Script?

Section titled “How to exit trades with a stop-loss order in Pine Script?”

TL;DR
Use strategy.exit(id, from_entry, loss|stop) right after strategy.entry() so TradingView queues a protective stop-loss the instant the position fills.

DifficultyIntermediate
Time to implement15-20 min
CategoryOrder Management
//@version=5
strategy("Stop-loss demo", overlay=true)
if ta.crossover(close, ta.highest(high, 20))
strategy.entry("Long", strategy.long)
strategy.exit("Long SL", from_entry="Long", loss=150)
Tip. Submit the stop on the same bar as the entry. TradingView delays the exit order until the matching entry fills, so you never risk a gap without protection.

Stop-loss exits are the safety net for every automated strategy. Pairing entries with immediate stops locks in risk per-trade, keeps performance metrics honest, and mirrors how real brokers manage OCO orders. Without them your backtests understate drawdowns and invite outsized losses when the market gaps.

  • Choose between tick-distance (loss) and absolute-price (stop) exits.
  • Attach stops to specific entries so multi-leg systems stay organised.
  • Update or cancel stops safely when market structure changes.
  • Backtest risk reliably by reflecting real-world order timing.
CallPurpose
strategy.exit(id, from_entry, loss, stop)Generates a protective exit for a named entry using ticks or explicit price.
strategy.entry(id, direction)Opens positions to which your stop will attach.
strategy.position_sizeConfirms whether a position exists before modifying exits.
ta.crossover(series1, series2)Common trigger for long entries used in the examples.
ta.atr(length)Produces volatility-based stop distances.
  1. Decide how to measure risk
    Use loss for tick offsets or stop for absolute prices. You can derive either from volatility or structure.

    lossTicks = input.int(150, "Tick stop distance", minval=1)
    stopPercent = input.float(2.0, "Percent stop", step=0.1)
    atrLength = input.int(14, "ATR length")
    atrStops = ta.atr(atrLength) * 1.5
  2. Submit the stop alongside the entry
    Immediately after strategy.entry() call strategy.exit() so the stop queues when the entry fills.

    if ta.crossover(close, ta.highest(high, 20))
    strategy.entry("Long", strategy.long)
    strategy.exit("Long SL", "Long", loss=lossTicks)
    if ta.crossunder(close, ta.lowest(low, 20))
    strategy.entry("Short", strategy.short)
    strategy.exit("Short SL", "Short", loss=lossTicks)
  3. Update or replace stops when conditions change
    Cancelling or reissuing exits lets you trail stops or switch from tick-based to price-based logic mid-trade.

    if strategy.position_size > 0
    newStopPrice = close - atrStops
    strategy.exit("Long SL", from_entry="Long", stop=newStopPrice)
//@version=5
strategy("Breakout with protective stops", overlay=true, initial_capital=50_000, default_qty_type=strategy.percent_of_equity, default_qty_value=5)
lengthHigh = input.int(40, "Breakout length", minval=2)
lengthLow = input.int(40, "Breakdown length", minval=2)
lossTicks = input.int(200, "Initial tick stop", minval=1)
trailATRMult = input.float(1.8, "ATR trail multiplier", step=0.1)
atrLength = input.int(14, "ATR length", minval=1)
highestHigh = ta.highest(high, lengthHigh)
lowestLow = ta.lowest(low, lengthLow)
atrValue = ta.atr(atrLength)
enterLong = ta.crossover(high, highestHigh)
enterShort = ta.crossunder(low, lowestLow)
if enterLong
strategy.entry("Long", strategy.long)
strategy.exit("Long SL", from_entry="Long", loss=lossTicks)
if enterShort
strategy.entry("Short", strategy.short)
strategy.exit("Short SL", from_entry="Short", loss=lossTicks)
// Trail stops using ATR once in the trade
if strategy.position_size > 0
longStop = close - atrValue * trailATRMult
strategy.exit("Long SL", from_entry="Long", stop=longStop)
if strategy.position_size < 0
shortStop = close + atrValue * trailATRMult
strategy.exit("Short SL", from_entry="Short", stop=shortStop)
plot(highestHigh, "Breakout high", color=color.new(color.green, 60))
plot(lowestLow, "Breakout low", color=color.new(color.red, 60))
Why this works.
  • Entries and stops trigger together so the trade is never unprotected.
  • ATR-based updates replace the original tick stop, mirroring a broker-side trailing stop.
  • Named exits ("Long SL", "Short SL") attach to matching entries, preventing cross-contamination between long and short risk.
  • Use unique id values per exit so you can update or cancel them later without confusion.
  • When pyramiding, pass qty_percent or qty in strategy.exit() if each leg needs different stop sizing.
  • strategy.cancel(id) removes a pending exit. Always reissue a new stop on the same bar to avoid gaps.
  • Combine stop-loss orders with alerts so live trading and webhook integrations stay in sync.

I see “Cannot set loss to negative value” errors

Section titled “I see “Cannot set loss to negative value” errors”

Double-check calculations that feed loss or stop. Clamp values or guard with math.max(value, minAllowed) before passing them to strategy.exit().

My stop fires on the same bar as the entry

Section titled “My stop fires on the same bar as the entry”

strategy.exit() uses stop orders. If price gaps through the stop level intra-bar, the order fills immediately. Consider strategy.order() with stop and limit for staged exits if you need more control.

Not when you provide from_entry. TradingView automatically removes the exit when that entry closes. Only call strategy.cancel() if you want to replace or trail the stop mid-position.

  • Call strategy.exit() on the same bar as the entry so every trade is protected.
  • Choose between tick (loss) and price (stop) inputs depending on how you size risk.
  • Update or cancel stops intentionally—TradingView cleans up exits tied to closed entries for you.
  • Consistent stop placement gives you realistic drawdowns and better strategy confidence.

Adapted from tradingcode.net, optimised for Algo Trade Analytics users.