Skip to content

A mean-reversion strategy ​

How do you write a strategy that buys a dip and sells the bounce, with a stop? This one buys when the close comes back above the lower Bollinger band, and sells at the middle band or at a stop below its entry. It is a teaching example, not a recommendation.

pine
//@version=6
strategy("Band reversion", overlay = true, initial_capital = 10000,
  default_qty_type = strategy.percent_of_equity, default_qty_value = 100,
  commission_type = strategy.commission.percent, commission_value = 0.1)
int len = input.int(20, "Length", minval = 2)
float mult = input.float(2.0, "Width", minval = 0.5, step = 0.5)
float stopPct = input.float(5.0, "Stop %", minval = 0.5)

[mid, upper, lower] = ta.bb(close, len, mult)
plot(mid, "Middle", color.gray)
plot(upper, "Upper", color.silver)
plot(lower, "Lower", color.silver)

// Enter only when flat, so one dip makes one trade
if ta.crossover(close, lower) and strategy.position_size == 0
    strategy.entry("Long", strategy.long)
// The stop sits under the entry price, or under this close until the
// entry fills, so it is already waiting on the bar the entry fills
float basis = strategy.position_size > 0 ?
  strategy.position_avg_price : close
strategy.exit("Exit", "Long", stop = basis * (1 - stopPct / 100),
  limit = mid)

How it works ​

  • strategy(...) sets the account: 10,000 to start, each entry the whole equity, and 0.1% commission a side.
  • ta.bb(close, len, mult) returns the middle band and the two bands mult standard deviations either side, as a tuple.
  • strategy.entry is a market order. Placed at this bar's close, it fills at the next bar's open, as on TradingView.
  • strategy.exit is placed on every bar, flat or not, so it is already resting when the entry fills, and the bar the entry fills on has its stop. While the entry waits, the stop sits under this bar's close. Once it has filled, strategy.position_avg_price gives the entry price, and the stop sits under that.
  • A stop and a limit rest together. Whichever the price reaches first fills, and the other is cancelled. Moving limit = mid each bar follows the middle band down or up.

Traps ​

  • A stop and a limit in one bar. When a bar's range holds both, the emulator decides which came first from the bar's open, high and low, as TradingView does. A daily backtest can't know the real order inside the day.
  • Without the flat check, a new cross while long would add another entry wherever pyramiding allows one. By default it allows none, so the extra order does nothing.
  • An exit placed only while long leaves the fill bar bare. strategy.position_size reads 0 until the close of the bar the entry fills on, so an exit inside if strategy.position_size > 0 arrives a bar late, and that first bar has no stop.
  • Commission matters. A reversion strategy trades often, so test with a realistic commission_value.

See also ​

AmenoScript is compatible with Pine Script v6. Pine Script is a trademark of TradingView, Inc., which has no part in AmenoScript.