דלגו לתוכן
פתיחת RaamTrade

Exit Rules

תוכן זה אינו זמין עדיין בשפה שלך.

Exit rules are function calls, not assignments. They register a price-based exit that the platform checks tick-by-tick — not just on bar close.

self.stop_loss(2.0) # exit if drawdown ≥ 2% from entry
self.take_profit(5.0) # exit if gain ≥ 5% from entry
self.break_even(1.0) # once up 1%, a stop at the entry price
self.trailing_stop(1.0, 0.5) # once up 1%, trail 0.5% behind the best price

A bare positional value means percent from entry. The unit is the keyword, so the same rules also take an absolute distance or an absolute level:

self.stop_loss(2.0) # 2% below entry (long)
self.stop_loss(percent=2.0) # the same thing, spelled out
self.stop_loss(points=5) # 5 points below entry
self.stop_loss(price=412.50) # an absolute price level

take_profit takes the same three units. break_even takes a percent or points=, but not price=, because it is measured from the entry.

Break-even

self.break_even(1.0) # once up 1% from entry, a stop at the entry price
self.break_even(percent=1.0) # the same thing, spelled out
self.break_even(points=20) # once up 20 points, a stop at the entry price

break_even takes one number, the trigger. The moment the trade is up by that much, a stop appears at exactly the entry price. This happens mid-bar, on the tick that reaches it.

  • It is its own stop, separate from stop_loss. Both are watched, and whichever price is hit first closes the trade.
  • Once it appears, it stays until the trade closes. Declaring a different trigger on a later bar only matters before it has appeared.
  • A trigger of 0 (or less) is no break-even, the same as stop_loss(0), so an optimizer sweep can include 0 to mean “off”.
  • A trade it closes shows the exit reason BreakEven.

Trailing stop

self.trailing_stop(1.0, 0.5) # start at +1%, trail 0.5% of the peak
self.trailing_stop(activate_percent=1.0, trail_percent=0.5) # the same thing, spelled out
self.trailing_stop(activate_points=20, trail_points=10) # start at +20 points, trail 10 points
self.trailing_stop(0, 1.5) # trail 1.5% from the moment of entry

trailing_stop takes two numbers:

  1. Activation. How far the trade must be up before trailing starts. 0 means it trails from the moment of entry.
  2. Distance. How far behind the best price the stop follows: a percent of the peak (long) or trough (short), or a number of points.

Details:

  • Two positional numbers are both percent.
  • To use points, name the numbers. Each number carries its own unit, so activate_points=20, trail_percent=0.5 is fine.
  • Once trailing has started, it keeps trailing until the trade closes.
  • A distance of 0 is no trailing stop.

The old one-number spelling self.trailing_stop(1.5) is refused on new code with RC2_TRAILING_TWO_NUMBERS. Write self.trailing_stop(0, 1.5), which means exactly the same. Strategies already saved with one number keep running as they always did. points= and price= on trailing_stop raise RC2_EXIT_UNIT. Name the numbers instead.

Assigning instead of calling fails SILENTLY

self.stop_loss = 2.0 # ❌ — no error, and NO STOP IS SET
self.stop_loss(2.0) # ✅

stop_loss is not a reserved name, so the assignment is accepted as an ordinary persistent-state slot called stop_loss holding 2.0. Nothing warns you; the position simply runs without a stop. This is the most expensive typo on this page — exit rules are registered by calling them, never set by assignment.

When they fire

Exit rules are checked tick-by-tick by the platform — not just on bar close. That means your stop fires the moment price touches it, not at the next bar’s open.

Rule Triggers when Fills at
stop_loss(p) Long: low ≤ entry × (1 − p/100). Short: high ≥ entry × (1 + p/100). The trigger price.
take_profit(p) Long: high ≥ entry × (1 + p/100). Short: low ≤ entry × (1 − p/100). The trigger price.
break_even(t) It has appeared (the best price reached entry × (1 + t/100) long, or entry × (1 − t/100) short), and price comes back to the entry. The entry price.
trailing_stop(a, d) It has started (the peak reached entry × (1 + a/100) long, or the trough reached entry × (1 − a/100) short), and long: low ≤ peak × (1 − d/100); short: high ≥ trough × (1 + d/100). The trigger price.

With points=, replace the percentage with a distance in points: for example, a long break-even appears at entry + t.

If a bar opens beyond a level (a gap), the rule fills at that bar’s open instead, the way a real stop would.

When an exit rule fires, the position closes at the trigger price. execute() still runs on that bar — your indicators and your state see every bar that happened — but no order fills on it: an order placed on the previous bar rests and gets its chance on the next one, because the exit already used that bar.

Dynamic exits

Exit rules can be re-registered every bar with different values. The most recent call wins.

class Tightening(Strategy):
trade = Symbol()
sizing = FixedCapital()
bars_in_pos = 0
def execute(self):
# Count bars in position with persistent state
if self.trade.is_long:
self.bars_in_pos = self.bars_in_pos + 1
else:
self.bars_in_pos = 0
# Tighten the trailing stop after 20 bars in position
if self.trade.is_long and self.bars_in_pos > 20:
self.trailing_stop(0, 1.0)
elif self.trade.is_long:
self.trailing_stop(0, 2.0)
self.stop_loss(3.0) # always active
if self.trade.is_flat:
self.buy(self.trade)

Exit rules registered on bar T take effect from bar T+1 onward.

Multiple rules

You can register all four at once. The first to trigger wins.

self.stop_loss(2.0)
self.take_profit(5.0)
self.break_even(1.0)
self.trailing_stop(1.0, 0.5)

If two trigger on the same tick (extremely rare; usually requires a gap), the order is: stop_loss → break_even → trailing_stop → take_profit.

A switch Param is how you make a rule optional, and it keeps the strategy on the optimizer’s fast path:

class OptionalExits(Strategy):
enable_trailing_stop = Param(True)
trailing_floor_percent = Param(1.0, min=0.0, max=5.0)
trailing_amount = Param(0.5, min=0.1, max=3.0)
enable_break_even = Param(False)
break_even_floor_percent = Param(1.0, min=0.0, max=5.0)
trade = Symbol()
sizing = FixedCapital()
def execute(self):
self.stop_loss(3.0)
if self.enable_trailing_stop:
self.trailing_stop(self.trailing_floor_percent, self.trailing_amount)
if self.enable_break_even:
self.break_even(self.break_even_floor_percent)
if self.trade.is_flat:
self.buy(self.trade)

Manual exits vs. rule exits

Rule exits (stop_loss, take_profit, break_even, trailing_stop) fire intra-bar at the trigger price. Manual exits (self.sell(), self.buy_to_cover()) fire at the next bar’s open. Use rules for risk management, manual exits for indicator-driven decisions.

class RuleAndManual(Strategy):
trade = Symbol()
sizing = FixedCapital()
def execute(self):
rsi = RSI(14, on=self.trade)
# Risk management — always on
self.stop_loss(3.0)
if rsi.crossed_above(30) and self.trade.is_flat:
self.buy(self.trade)
# Indicator-driven exit
if self.trade.is_long and rsi.crossed_below(70):
self.sell(self.trade, self.trade.position, reason="RSI exit")

Common diagnostics

Code What it means
RC2_EXIT_ARG Exit rule called with the wrong number of positional args, or with no threshold.
RC2_EXIT_UNIT A stale keyword (pct, pts, unit, dollars), two unit sources at once, price= on break_even, or points=/price= on trailing_stop.
RC2_TRAILING_TWO_NUMBERS trailing_stop with one number. Write both: self.trailing_stop(0, 1.5).

There is deliberately no diagnostic for self.stop_loss = 2.0 — see the danger note above. Nothing catches it but reading your own code.