Pine Script Strategy & Execution

Pine Script Trailing Stops: strategy.exit with trail_offset, stop, and limit

A Pine Script trailing stop needs trail_offset plus an activation level: trail_points in ticks from entry, or trail_price as an absolute price. stop and limit also take absolute prices. Do not assume that adding stop to a built-in trailing exit defines a fixed-stop-then-trail policy. When that sequence matters, calculate one stop price explicitly and submit it with strategy.exit(stop = ..., limit = ...), as in the second example below.

Build trailing stop rules

Which strategy.exit parameters use prices or ticks?

Choose the units before writing the exit. A stop-loss and take-profit bracket uses stop and limit; it is not the stop-limit entry order created by strategy.entry with the same two argument names. The exit ID identifies the exit order, while from_entry must match the intended entry ID.

  • In v6, stop/loss and limit/profit pairs also select the level expected to trigger first. Supply one member of each pair unless this selection is intentional.
  • For a long trade, an active trail follows rising highs. For a short trade, it follows falling lows with the stop above price.
  • Use math.max(1, math.round(distance / syminfo.mintick)) when the intended offset must be at least one whole tick. Rounding changes the requested distance by up to half a tick.
ParameterUnitMeaning
stopAbsolute priceStop-loss order level
limitAbsolute priceTake-profit order level
loss / profitTicks from entryRelative stop-loss / take-profit distances
trail_pointsTicks from entryFavorable movement required for activation
trail_priceAbsolute priceAlternative activation level
trail_offsetTicksDistance from the favorable extreme after activation

Convert ATR, pips and price points to ticks

These are arithmetic examples with assumed tick sizes, not verified specifications for a particular exchange feed. Read syminfo.mintick on the selected chart. A price distance is different from cash risk per contract, which also depends on contract value and quantity.

  • trail_points = 150 means 150 ticks. With a 0.01 minimum tick, activation is 1.50 beyond entry, not 150.00.
  • trail_offset = 2 on the same instrument means a 0.02 offset. A narrow offset can produce early exits, but an immediate fill is not guaranteed.
Example price distanceAssumed minimum tickCalculationTick distance
$1.50 on a dollar-quoted instrument0.011.50 / 0.01150
400 price units0.10400 / 0.104,000
25 pips, with one pip defined as 0.00010.000010.0025 / 0.00001250
4 index price points0.254 / 0.2516

Complete v6 example: built-in ATR trailing stop

This long-only example captures ATR on the signal bar and submits the exit alongside the entry. Activation, offset and target distances remain fixed for that trade. The profit argument sets a target in ticks from the eventual fill price; limit would instead require an absolute target price. This example deliberately has no initial stop before trailing activates. The moving-average entry is a demonstration rule, not a performance claim.

Complete strategy: native trailing stop with signal-bar ATR distancespinescript
//@version=6
strategy("ATR native trailing example", overlay = true, pyramiding = 0)

int atrLength = input.int(14, "ATR length", minval = 1)
float armMult = input.float(1.5, "Activation ATR", minval = 0.1)
float offsetMult = input.float(1.0, "Offset ATR", minval = 0.1)
float targetMult = input.float(4.0, "Target ATR", minval = 0.1)
float atr = ta.atr(atrLength)
float fast = ta.ema(close, 9)
float slow = ta.ema(close, 21)
bool signal = ta.crossover(fast, slow)

if signal and strategy.position_size == 0 and not na(atr)
    int armTicks = math.max(1, math.round(atr * armMult / syminfo.mintick))
    int offsetTicks = math.max(1, math.round(atr * offsetMult / syminfo.mintick))
    int targetTicks = math.max(1, math.round(atr * targetMult / syminfo.mintick))
    strategy.entry("Long", strategy.long)
    strategy.exit("Trail", "Long", profit = targetTicks, trail_points = armTicks, trail_offset = offsetTicks)

plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)

Fixed stop first, then a custom trailing stop

For an explicit fallback rule, maintain a single stop price: before activation use the initial stop; afterward keep the greater of the previous stop and peak minus offset for a long position. This example freezes ATR and price levels on the signal bar, so entry gaps change the distances from the actual fill. The initial bracket is submitted with the entry. The trail then updates at bar close, not retrospectively inside that bar. It does not use native trail_offset.

  • If the newly calculated long stop is above the current close, it is already crossed when submitted. Its subsequent execution is not a historical fill at the earlier peak minus offset.
  • For shorts, use the lowest favorable price, place the trail above it and preserve the smaller of the old and candidate stop prices.
  • Separate strategy.exit calls reserve position quantities. Adding a second full-size exit is not a reliable way to create independent full-position protection.
Complete strategy: one persistent stop price plus a fixed limit targetpinescript
//@version=6
strategy("Fixed stop then custom ATR trail", overlay = true, pyramiding = 0)

float riskMult = input.float(2.0, "Initial stop ATR", minval = 0.1)
float armMult = input.float(1.5, "Activation ATR", minval = 0.1)
float offsetMult = input.float(1.0, "Trailing offset ATR", minval = 0.1)
float targetMult = input.float(4.0, "Target ATR", minval = 0.1)
float atr = ta.atr(14)
float fast = ta.ema(close, 9)
float slow = ta.ema(close, 21)
bool signal = ta.crossover(fast, slow)

var float stopPrice = na
var float targetPrice = na
var float activationPrice = na
var float offsetPrice = na
var float peak = na
var bool active = false

if strategy.position_size == 0
    stopPrice := na
    targetPrice := na
    activationPrice := na
    offsetPrice := na
    peak := na
    active := false
    if signal and not na(atr)
        stopPrice := math.round_to_mintick(close - atr * riskMult)
        targetPrice := math.round_to_mintick(close + atr * targetMult)
        activationPrice := math.round_to_mintick(close + atr * armMult)
        offsetPrice := math.max(syminfo.mintick, math.round_to_mintick(atr * offsetMult))
        strategy.entry("Long", strategy.long)
        strategy.exit("Exit", "Long", stop = stopPrice, limit = targetPrice)

if strategy.position_size > 0
    peak := na(peak) ? high : math.max(peak, high)
    active := active or peak >= activationPrice
    if active
        stopPrice := math.max(stopPrice, math.round_to_mintick(peak - offsetPrice))
    strategy.exit("Exit", "Long", stop = stopPrice, limit = targetPrice)

plot(stopPrice, "Submitted stop", color.red, style = plot.style_linebr)
plot(targetPrice, "Target", color.green, style = plot.style_linebr)
plot(activationPrice, "Activation", color.orange, style = plot.style_linebr)

Trace activation, ratcheting and exit in order

This constructed price sequence explains a native trailing exit; it is not a backtest or an OHLC reconstruction. Assume an entry filled at 100.00, minimum tick 0.01, an already-submitted trail_points of 600, trail_offset of 200 and limit of 115.00. Assume no slippage or gaps and that prices occur in the stated order.

  • Alternative path: reaching 115.00 before crossing the active trail fills the target first.
  • An OHLC candle alone does not prove whether activation, the new peak or the pullback occurred first. Use the emulator path or available lower-timeframe data when diagnosing a chart.
Ordered price eventTrailing stateResult
Price rises from 100.00 to 105.50InactiveActivation is 106.00; trailing-only order offers no fixed stop yet
Price reaches 106.00ActiveInitial trailing level is 104.00
Price rises to 109.00 without a 2.00 pullbackActiveTrail advances to 107.00
Price rises to 112.50 without crossing the trailActiveTrail advances to 110.50
Price then falls through 110.50TriggeredStop exit fills under the stated assumptions; remaining target is canceled

Verify behavior before optimizing parameters

Compile each example, then inspect individual trades on standard candles. Record the symbol, timeframe, date range, input values and strategy properties so another run can reproduce the comparison. Syntax validation does not establish execution correctness or profitability.

  • Check the entry ID and from_entry, tick conversion, activation level and time the exit was submitted before investigating an absent exit.
  • Compare Bar Magnifier on and off where available. It uses lower-timeframe data, not guaranteed tick history, and historical coverage can be limited.
  • calc_on_every_tick affects realtime recalculation; it does not restore historical ticks. calc_on_order_fills can introduce additional executions and unrealistic historical assumptions.
  • Inspect activation-bar exits, gaps through stops, target-before-trail exits and a new trade after the prior trade closes. Stops can fill worse than their requested level.
  • Include fees and slippage, hold out a date range, and compare neighboring parameter values before selecting an optimizer result.
Where Pineify fits

Pine Script AI Coding Agent

Draft or revise exit rules from a written specification, inspect the Pine code and review syntax diagnostics before checking trades in TradingView.

Also useful: Strategy Optimizer. Compare input combinations for activation, offset and target distances after the exit behavior is verified. Parameter selection does not guarantee future performance.

Build trailing stop rules

Frequently asked questions

Educational programming examples, not investment advice. These examples have not been tested for trading performance. Simulated fills, fees, slippage and live execution can differ; backtests do not guarantee future returns.

Sources and verification