Skip to content
GetProfitable
Search

Stops, targets and strategy.exit

Lesson 17 · about 12 min

strategy.exit() is how a strategy places the protective orders that a real trade would have: a stop loss, a profit target, or a trailing stop, attached to a specific entry. It is more capable and more confusing than strategy.close, and most backtests that "can't be right" have a strategy.exit argument in the wrong units.

The call

strategy.exit(id, from_entry, qty, qty_percent, profit, limit, loss, stop, trail_price, trail_points, trail_offset, comment, alert_message)
  • id: the exit's own label.
  • from_entry: the entry ID this exit protects. Omit it to attach to every open entry.
  • stop and limit: absolute prices for the stop loss and the take profit.
  • loss and profit: the same levels expressed as a distance in ticks from the entry price.
  • trail_points and trail_offset: trailing stop activation and distance, in ticks.
  • trail_price: activation as a price.
  • qty or qty_percent: exit part of the position.

Prices versus ticks is the trap. loss=50 on a stock that trades in cents is a 50-cent stop; on an index future with a 0.25 tick it is a 12.5-point stop. Use stop= and limit= with prices you have computed, and you will always know what you meant.

Attaching exits to an entry

//@version=6
strategy("Breakout with ATR stop", overlay=true, initial_capital=10000,
     default_qty_type=strategy.percent_of_equity, default_qty_value=10, pyramiding=0)
len     = input.int(20, "Breakout length", minval=2)
atrMult = input.float(2.0, "ATR stop multiple", minval=0.5, step=0.25)
rr      = input.float(2.0, "Reward to risk", minval=0.5, step=0.25)

atr = ta.atr(14)
breakout = close > ta.highest(high, len)[1]
var float stopPrice = na
var float targetPrice = na

if breakout and strategy.position_size == 0
    stopPrice := math.round_to_mintick(close - atr * atrMult)
    targetPrice := math.round_to_mintick(close + (close - stopPrice) * rr)
    strategy.entry("Long", strategy.long)
    strategy.exit("Exit", "Long", stop=stopPrice, limit=targetPrice)

plot(strategy.position_size > 0 ? stopPrice : na, "Stop", color=color.red, style=plot.style_linebr)
plot(strategy.position_size > 0 ? targetPrice : na, "Target", color=color.green, style=plot.style_linebr)

Calling strategy.exit in the same block as the entry is correct. The exit is tied to the entry ID and becomes active once the entry fills at the next open; until then it waits. The stop and target are computed from the signal bar's close, but the fill is at the next open, so the actual risk per trade differs slightly from the plan. That is realistic; you would face the same gap trading it by hand.

Rounding with math.round_to_mintick matters because the emulator rejects prices that are not multiples of the tick.

Exit orders persist

Once placed, an exit stays active until it fills or you cancel it (strategy.cancel("Exit"), or the position closes some other way). You do not have to call strategy.exit on every bar. Calling it every bar with a changing price is how you build a manual trailing stop:

var float trail = na
if strategy.position_size > 0
    trail := na(trail) ? stopPrice : math.max(trail, close - atr * atrMult)
    strategy.exit("Exit", "Long", stop=trail, limit=targetPrice)
else
    trail := na

Each call replaces the previous exit with the same ID, so the stop ratchets up and never down.

The built-in alternative is trail_points (how far in profit, in ticks, before the trail activates) with trail_offset (how far behind price it follows). It is convenient and hard to reason about across symbols; the manual version above is explicit and works anywhere.

Both levels in one bar

If a bar's range covers both the stop and the target, the emulator has to guess which was hit first. Its rule: if the bar's high is closer to the open than its low is, price is assumed to have gone open, high, low, close; otherwise open, low, high, close. That is a coin toss dressed as a rule, and on volatile bars it decides wins versus losses. The bar magnifier option in module 7 replaces the guess with lower-timeframe data. Until then, treat strategies with tight stops and targets that both fit inside typical bars as unreliable.

Pyramiding

pyramiding=3 allows three entries in the same direction. Each strategy.entry with the same ID adds to the position; different IDs let you exit legs separately. A strategy.exit without from_entry covers all of them. Adding to winners is a legitimate technique; adding to losers ("averaging down") in a strategy is a fast way to build a backtest that survives everything except the one drawdown that ends it. The tester will not stop you.

Time stops

Exiting after N bars needs no special function:

if strategy.position_size > 0 and bar_index - strategy.opentrades.entry_bar_index(0) >= 10
    strategy.close("Long", comment="Time stop")

strategy.opentrades.entry_bar_index(0) is the bar index where the oldest open trade entered.

Key idea: strategy.exit("Exit", "Long", stop=price, limit=price) attaches a stop and a target to an entry, in prices, and stays active until filled; calling it again with the same ID replaces it, which is how trailing stops are built.

Try it: Add the breakout strategy to a daily chart and open the trade list. Find a trade that exited at the stop and check that the exit price equals the plotted stop line (or the next open if it gapped). Then change stop=stopPrice to loss=50 and see what happens to every stop.

Recap

  • stop and limit take prices; loss and profit take ticks. Prefer prices you computed and rounded with math.round_to_mintick.
  • Place strategy.exit in the same block as the entry; it activates when the entry fills.
  • Exits persist; re-calling with the same ID replaces the order, which is how manual trailing stops work.
  • When one bar contains both stop and target, the emulator guesses the order of touches unless the bar magnifier is on.
  • pyramiding allows multiple same-direction entries; time stops use strategy.opentrades.entry_bar_index.

See it drawn

Original diagrams for the ideas on this page. Illustrative, not real market data.

Risk and reward on one tradeA price scale showing an entry with a stop two points below and a target six points above, so the reward band is three times the risk band.PRICETARGET 106.00ENTRY 100.00STOP 98.00REWARDRISK6.00 pointsthree times the risk2.00 pointsthe most you loserisk : reward = 1 : 3
Risk and reward on one trade. One trade on a price scale: the entry sits 2.00 points above the stop and 6.00 points below the target, so the shaded reward band is three times the risk band. The ratio compares what is lost if the stop is hit with what is gained if the target is reached.
A trailing stop held two ATRs under a rising priceA rising price line with a stepped line below it that climbs whenever price climbs and holds its level whenever price falls, until price drops onto it.PRICE AND A TRAILING ATR STOP2 × ATRstop hittrailing stoppriceIllustrative prices. The stop follows price up and never moves back down.
A trailing stop set by ATR. Average true range measures how far a market typically travels in a session, so a stop placed a multiple of ATR under price leaves room for ordinary swings. The step line only ever ratchets up, and the circle marks where price falls onto it.
Breakout and retestPrice stalls under one level, pushes above it, comes back to touch it from above, then continues higher.pricetimeold resistancenow support1price keeps stalling2breaks above3pulls back and retests it4and carries on
Breakout and retest. Price stalls under the same level several times, pushes above it, then drops back to touch it from above before carrying on. That touch is the retest, where the old ceiling is tried as a floor. A break that falls straight back under it is a false breakout.