A practical, code-first guide to adding a trailing stop to a Pine Script v6 strategy using strategy.exit() with trail_points, trail_offset, and trail_price. Covers a fixed-distance trailing stop, an ATR-based version that breathes with volatility, and how to combine a hard stop-loss with a trailing stop in one call — with point units explained via syminfo.mintick. This is educational material only, not financial advice; trailing stops can lock in less on strong runs and get whipsawed in noise, and a backtest result is never a forecast of live performance.
A fixed stop-loss sits at one price and never moves. A trailing stop is different: it follows price in the favourable direction, ratcheting your exit level along behind a winning trade so that if the market reverses, you give back only a bounded amount of the move. It only ever tightens; it never loosens. That one property is what makes it appealing for trend-following and what makes it frustrating in chop.
In Pine Script v6, a trailing stop attached through strategy.exit() has two distinct moving parts, and confusing them is the single most common mistake authors make. The first is trail_points — the activation distance. It is how far price must move into profit, in points, before the trailing stop turns on at all. Until price has travelled that far past your entry, no trailing exit exists. The second is trail_offset — the trailing distance. Once activated, it is how far behind the best price the stop sits and rides. A small offset trails tightly (quick to lock in, quick to get shaken out); a large offset trails loosely (more room to breathe, more given back on a reversal).
There is also a lower-level parameter, trail_price, which lets you specify the absolute price level at which trailing should activate instead of expressing it as a trail_points distance from entry. You use one or the other for activation, not both. For most strategies the points-based form is easier to reason about, so this guide leads with trail_points and trail_offset.
A framing note before any code: everything here is educational only. A trailing stop shapes how a trade exits; it does not make a strategy profitable, and nothing here is a recommendation to trade. Trading carries a real risk of loss, and the goal is correct mechanics, not returns.
The parameters trail_points and trail_offset are measured in points, and a "point" in this context is one syminfo.mintick — the smallest price increment the instrument trades in. This is where beginners get numbers that look absurd. On an S&P 500 CFD a mintick might be 0.25; on EUR/USD it is often 0.00001; on a stock it might be 0.01. So trail_offset = 100 does not mean "100 dollars" or "100 pips" — it means 100 minticks, and what that is worth in price depends entirely on the symbol.
This matters most when you mix the points-based trailing parameters with an absolute-price stop, because the two speak different languages. trail_points and trail_offset are in minticks; the stop= parameter of strategy.exit() expects a full price. To convert a points distance into a price distance you multiply by syminfo.mintick:
//@version=6 // Educational only — validate before trading; not financial advice. // Convert a points distance into an absolute price distance. hardStopPoints = input.int(400, "Hard stop (points)") priceDistance = hardStopPoints * syminfo.mintick
Here priceDistance is a real price offset you can subtract from an entry price to get a stop level. Keeping this distinction crisp — minticks for the trailing parameters, full price for `stop=` and `limit=` — prevents the classic bug where a trailing stop that behaved sensibly on one symbol becomes hair-trigger or comically wide the moment you drop the same script onto a different instrument. When in doubt, plot your computed levels and read them against the chart's price scale before trusting any tester output. A quick visual check is cheaper than a corrupted backtest.
Here is a complete, compilable v6 strategy that trails at a fixed number of points. The entry is a plain SMA cross used only as a vehicle; the focus is the trailing block. Note that the entry signal is gated on barstate.isconfirmed so we never act on a still-repainting bar, while the exit order itself is registered unconditionally so the protection stays live.
//@version=6
// Educational only — validate before trading; not financial advice.
strategy("Fixed trailing stop", overlay = true)
trailTrig = input.int(200, "Trail activation (points)")
trailDist = input.int(100, "Trail offset (points)")
longSig = ta.crossover(close, ta.sma(close, 20))
if longSig and barstate.isconfirmed and strategy.position_size == 0
strategy.entry("Long", strategy.long)
// Trailing stop: activates once price is trailTrig points in profit,
// then rides trailDist points behind the best price.
if strategy.position_size > 0
strategy.exit("Exit", "Long",
trail_points = trailTrig,
trail_offset = trailDist)Reading it: on a confirmed cross with no open position, strategy.entry() opens a long. While that long is open, strategy.exit() registers a trailing stop. With trailTrig = 200, nothing trails until price has moved 200 points into profit; before that, the position has no trailing protection at all — a deliberate design choice you should be conscious of. Once activated, trailDist = 100 keeps the stop 100 points behind the highest price seen, sliding up as new highs print and freezing (never dropping) when price pulls back.
Because trail_points and trail_offset are both in minticks, the feel of these numbers is symbol-dependent — the previous section's mintick point applies directly. The second positional argument, "Long", is the from_entry id; it must match the id you passed to strategy.entry() or the exit will not bind to the position.
A fixed point offset has the same weakness a fixed stop-loss does: 100 points can be a tight leash on a volatile instrument and a loose one on a quiet session. A common alternative is to express the trailing distance as a multiple of the Average True Range, ta.atr(), so the stop widens when the market is volatile and tightens when it calms down.
The wrinkle is that trail_offset wants points (minticks), while ta.atr() returns a value in price. So you convert ATR back into points by dividing by syminfo.mintick, then round to an integer:
//@version=6
// Educational only — validate before trading; not financial advice.
strategy("ATR trailing stop", overlay = true)
atrLen = input.int(14, "ATR length", minval = 1)
atrMult = input.float(2.0, "ATR offset multiple", minval = 0.1)
trigMult = input.float(1.0, "ATR activation multiple", minval = 0.1)
atr = ta.atr(atrLen)
// Convert an ATR-based price distance into integer points (minticks).
offsetPts = math.round(atr * atrMult / syminfo.mintick)
trigPts = math.round(atr * trigMult / syminfo.mintick)
longSig = ta.crossover(close, ta.sma(close, 20))
if longSig and barstate.isconfirmed and strategy.position_size == 0
strategy.entry("Long", strategy.long)
if strategy.position_size > 0
strategy.exit("Exit", "Long",
trail_points = trigPts,
trail_offset = offsetPts)The structure is identical to the fixed version — only the way we arrive at the point counts changed. With atrMult = 2.0, the stop trails roughly two ATRs behind the best price, so in a volatile stretch it sits further away (fewer premature exits) and in a quiet stretch it tightens automatically. Using math.round() keeps the arguments as whole points, which is what the parameters expect.
One honest caveat specific to the ATR form: because atr updates every bar, offsetPts is recomputed continuously, but the trailing stop still only ever ratchets tighter — TradingView will not loosen an already-activated trail even if ATR expands. That is correct trailing behaviour, not a bug, but it means the ATR width mainly governs the initial trail rather than constantly re-widening a stop that has already locked in gains.
In practice many authors want both: a hard stop-loss that caps the worst case from the moment of entry, and a trailing stop that locks in profit once a move runs. You can attach both in a single strategy.exit() call. The hard stop protects you during the window before the trail activates — remember, with a trail_points activation distance the position has no trailing protection until price has travelled that far into profit.
//@version=6
// Educational only — validate before trading; not financial advice.
strategy("Trailing stop example", overlay = true)
trailTrig = input.int(200, "Trail activation (points)")
trailDist = input.int(100, "Trail offset (points)")
hardStop = input.int(400, "Hard stop (points)")
longSig = ta.crossover(close, ta.sma(close, 20))
if longSig and barstate.isconfirmed and strategy.position_size == 0
strategy.entry("Long", strategy.long)
// Hard stop AND a trailing stop on the same position.
if strategy.position_size > 0
strategy.exit("Exit", "Long",
stop = strategy.position_avg_price - hardStop * syminfo.mintick,
trail_points = trailTrig,
trail_offset = trailDist)The key detail is the unit mixing handled correctly. stop= takes an absolute price, so we build it from strategy.position_avg_price (the real average fill) and subtract hardStop * syminfo.mintick to convert the points input into a price distance. Meanwhile trail_points and trail_offset stay in raw points. Every risk input declared at the top is wired into the exit — no dead inputs — so there is no gap between what the settings menu implies and what the engine actually does.
Behaviourally: early in the trade, only the hard stop is live at entry − 400 points. Once price climbs 200 points into profit, the trailing stop activates and begins riding 100 points behind the high. From that point the effective stop is whichever of the two is nearer to price — normally the trail, since it has ratcheted up above the original hard stop. For a short position you mirror the arithmetic: hard stop above entry (position_avg_price + hardStop * syminfo.mintick) and the trail rides above the lowest price seen. A quick self-audit of that mirrored maths, or ForexCodes' Audit and Fixer tooling, catches the inverted-stop slips that quietly corrupt short-side results.
It is worth being blunt about the trade-offs, because a trailing stop is often oversold as a free upgrade. It is not.
First, a trailing stop locks in less than the peak on strong runs. By construction it exits some distance behind the best price, so on a clean, extended trend you always leave the last trail_offset worth of move on the table. Widen the offset to keep more of a big run and you also widen how much any single reversal gives back. There is no setting that captures the top and survives the noise; the offset is a genuine tension you are choosing a point on, not solving.
Second, a tight trail gets whipsawed out of good trades. In choppy, ranging conditions a small offset means routine bar-to-bar noise clips your stop and closes the position right before it would have resumed. The very tightness that locks in profit efficiently in a trend is what shakes you out prematurely in a range. Matching the offset to the instrument's typical noise — which is exactly what the ATR-based version tries to automate — helps, but never eliminates this.
Third, and most important: a backtest with a trailing stop is still not a forecast. TradingView's Strategy Tester fills your trailing exit at idealised prices, but live markets have slippage, spreads, weekend gaps, and fast moves that can blow straight through your trailing level or fill you worse than the tester assumes. Intrabar, the tester also makes assumptions about the order in which high and low were reached, which can flatter trailing exits specifically. And an offset tuned to look good on past data may simply be fitting noise that will not recur.
So use these mechanics to make your risk explicit and bounded, validate the logic — confirm nothing repaints, that shorts mirror longs, that your points-versus-price units are right — and then hold the performance numbers loosely. None of this is financial advice. Trading carries a real risk of loss, a passing backtest is not a live edge, and a trailing stop is a risk-shaping tool, not a profit engine.
Educational only — not financial advice. Trading involves substantial risk of loss.