Skip to content

Custom Exits

Beyond basic SL/TP, replaybt supports breakeven stops, trailing stops, partial take profit, and strategy-driven exits.

Breakeven Stop

Move the stop loss to lock in a small profit once the position reaches a threshold:

MarketOrder(
    side=Side.LONG,
    take_profit_pct=0.06,
    stop_loss_pct=0.03,
    breakeven_trigger_pct=0.02,   # activate at +2%
    breakeven_lock_pct=0.005,     # move SL to +0.5%
)

When price reaches +1.5% from entry, the stop loss moves to entry + 0.5%. If price reverses, you exit with a small profit instead of a full loss.

Trailing Stop

A stop that follows price as it moves in your favor:

MarketOrder(
    side=Side.LONG,
    trailing_stop_pct=0.02,              # trail 2% below peak
    trailing_stop_activation_pct=0.03,   # activate after +3% profit
)

The trailing stop activates once the position is +3% in profit. From that point, it trails 2% below the highest price seen. If price drops 2% from its peak, the position closes.

You can use a trailing stop with or without a fixed TP/SL:

# Trailing stop only (no fixed TP)
MarketOrder(
    side=Side.LONG,
    stop_loss_pct=0.05,           # safety SL
    trailing_stop_pct=0.02,
    trailing_stop_activation_pct=0.01,
)

Partial Take Profit

Close a fraction of the position at the first TP, then set a new TP for the remainder:

MarketOrder(
    side=Side.LONG,
    take_profit_pct=0.05,
    stop_loss_pct=0.03,
    partial_tp_pct=0.5,           # close 50% at TP
    partial_tp_new_tp_pct=0.10,   # new TP at +10% for remainder
)

When the first TP is hit: 1. 50% of the position closes at the TP level 2. The remaining 50% gets a new TP at +10% from entry 3. The stop loss stays the same

Strategy-Driven Exits (check_exits)

For exits based on indicators, time, or custom logic, implement check_exits():

class RSIExitStrategy(Strategy):
    def check_exits(self, bar, positions):
        exits = []
        for i, pos in enumerate(positions):
            # Exit long when RSI > 70
            rsi = self._current_rsi  # stored from on_bar
            if pos.is_long and rsi and rsi > 70:
                exits.append((i, bar.close, "RSI_OVERBOUGHT"))
        return exits

    def on_bar(self, bar, indicators, positions):
        self._current_rsi = indicators.get("rsi")
        # ... entry logic ...

Partial Close via check_exits

Return a 4-tuple to close a fraction:

def check_exits(self, bar, positions):
    exits = []
    for i, pos in enumerate(positions):
        pnl_pct = (bar.close - pos.entry_price) / pos.entry_price
        if pos.is_long and pnl_pct > 0.03:
            # Close 50% at current price
            exits.append((i, bar.close, "PARTIAL_TAKE", 0.5))
    return exits

Post-Exit Re-Entry (on_exit)

Use on_exit() to immediately enter a new position after an exit:

class FlipStrategy(Strategy):
    def on_exit(self, fill, trade):
        # After TP, flip direction if conditions are met
        if trade.reason == "TAKE_PROFIT":
            opposite = Side.SHORT if trade.side == Side.LONG else Side.LONG
            return MarketOrder(
                side=opposite,
                take_profit_pct=0.05,
                stop_loss_pct=0.03,
            )
        return None

Canceling Pending Orders on Exit

When a position closes, any pending limit orders (e.g., scale-in) may still be active. Cancel them with CancelPendingLimitsOrder:

from replaybt import CancelPendingLimitsOrder

class CleanupStrategy(Strategy):
    def on_exit(self, fill, trade):
        # Cancel pending scale-in limit orders
        return CancelPendingLimitsOrder()

Combining Exit Types

All exit types can be combined on a single order:

MarketOrder(
    side=Side.LONG,
    take_profit_pct=0.06,             # fixed TP
    stop_loss_pct=0.03,              # fixed SL
    breakeven_trigger_pct=0.02,      # breakeven at +2%
    breakeven_lock_pct=0.005,        # lock at +0.5%
    trailing_stop_pct=0.02,           # trail 2% below peak
    trailing_stop_activation_pct=0.04, # activate at +4%
    partial_tp_pct=0.5,               # close 50% at TP
    partial_tp_new_tp_pct=0.12,       # remainder TP at +12%
)

The engine evaluates exit conditions in priority order: gap check, SL, TP, breakeven activation, trailing stop update and check, partial TP.