This strategy is a StockSharp port of the MetaTrader 4 expert advisor RUBBERBANDS_3. It maintains two running price extremes, opens additional positions whenever price expands by a configurable distance, and liquidates the entire sequence once a counter-move of a given size occurs. After a retracement the strategy optionally flips to the opposite direction while monitoring a session-level profit and loss target.
Note: StockSharp operates on netted positions. The original MT4 script can keep long and short orders simultaneously, but the port closes the active sequence before flipping direction. The general behaviour of scaling into trends and unwinding on pullbacks is preserved.
Trading Logic
Record the current close price as both the running maximum and minimum (or reuse saved values when restarting).
When the price rises by PipStep points above the current maximum, submit a market buy order of size OrderVolume and update the maximum to the new price.
When the price falls by PipStep points below the current minimum, submit a market sell order of size OrderVolume and update the minimum.
If the market pulls back by BackStep points against the active direction, close all positions in that direction and set up a reversal. The opposite side is opened once the previous sequence is fully liquidated.
Monitor the cumulative session result. If the realised plus open profit reaches SessionTakeProfit × OrderVolume, close the session. When the drawdown while reversing exceeds SessionStopLoss × OrderVolume, close everything as well.
The QuiesceNow toggle prevents new trades when the strategy is flat. The StopNow flag pauses all logic, and CloseNow requests an immediate flattening of the portfolio.
Orders are generated from finished candles of the configured CandleType. The default timeframe is one minute, matching the timing of the original EA which triggered checks at the beginning of each minute.
Parameters
Parameter
Description
Default
OrderVolume
Base size of each market order.
0.02
MaxOrders
Maximum number of concurrent positions in a single direction. Additional entries are blocked when the limit is reached.
10
PipStep
Expansion distance in points that adds a new trade.
100
BackStep
Counter-move in points that forces an exit and prepares a reversal.
20
QuiesceNow
When true, the strategy stays idle while no positions are open.
false
DoNow
Opens the very first long sequence immediately after the strategy starts.
false
StopNow
Hard stop flag that prevents any further processing. Existing positions remain untouched.
false
CloseNow
Requests an immediate flat position, triggering sequential closures.
false
UseSessionTakeProfit
Enables the cumulative session take-profit.
true
SessionTakeProfit
Target profit in account currency per lot used to close the session.
2000
UseSessionStopLoss
Enables the cumulative session stop-loss.
true
SessionStopLoss
Maximum tolerated loss per lot while reversing before the session is closed.
4000
UseInitialValues
When restarting, reuse the manually supplied InitialMax and InitialMin instead of the latest close price.
false
InitialMax
Stored upper extreme reused when UseInitialValues is enabled.
0
InitialMin
Stored lower extreme reused when UseInitialValues is enabled.
0
CandleType
Candle series processed by the strategy. Defaults to one-minute candles.
TimeFrame(1m)
Session Management
Profit aggregation: realised profits are accumulated after every full closure, while unrealised gains are recomputed from the weighted average entry prices of all open positions.
Session take-profit: once SessionTakeProfit is reached, the strategy closes all trades and resets the stored extremes.
Session stop-loss: during a reversal sequence (BackStep triggered) the strategy tracks the floating loss. If the drawdown exceeds SessionStopLoss, all positions are liquidated and the session restarts with cleared statistics.
Usage Notes
The price-step used to convert points into prices is taken from Security.PriceStep. Configure the instrument metadata accordingly; otherwise a fallback of 0.0001 is applied.
Because orders are netted, the strategy executes closing trades before opening the opposite direction. When migrating legacy data, be aware that the order history may differ from hedged platforms.
The DoNow flag only opens the very first long position. Additional entries follow the regular breakout conditions.
Use QuiesceNow when you want to leave the strategy loaded but inactive after it flattens the book.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rubberbands 3: Grid expansion strategy using SMA+ATR bands.
/// Enters at band extremes, adds on continuation, exits at mean.
/// </summary>
public class Rubberbands3Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _entryPrice;
private int _gridCount;
public Rubberbands3Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 20)
.SetDisplay("SMA Length", "SMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_gridCount = 0;
var sma = new SimpleMovingAverage { Length = SmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0)
return;
var close = candle.ClosePrice;
var upper = smaVal + atrVal * 1.5m;
var lower = smaVal - atrVal * 1.5m;
if (Position > 0)
{
if (close >= smaVal)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (close <= _entryPrice - atrVal * 5m)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (_gridCount < 4 && close <= _entryPrice - atrVal * 0.8m)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
BuyMarket();
}
}
else if (Position < 0)
{
if (close <= smaVal)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (close >= _entryPrice + atrVal * 5m)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
}
else if (_gridCount < 4 && close >= _entryPrice + atrVal * 0.8m)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
SellMarket();
}
}
if (Position == 0)
{
if (close <= lower)
{
_entryPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (close >= upper)
{
_entryPrice = close;
_gridCount = 0;
SellMarket();
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class rubberbands_3_strategy(Strategy):
def __init__(self):
super(rubberbands_3_strategy, self).__init__()
self._sma_length = self.Param("SmaLength", 20).SetDisplay("SMA Length", "SMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetDisplay("ATR Length", "ATR period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(rubberbands_3_strategy, self).OnReseted()
self._entry_price = 0
self._grid_count = 0
def OnStarted2(self, time):
super(rubberbands_3_strategy, self).OnStarted2(time)
self._entry_price = 0
self._grid_count = 0
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(sma, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
if atr_val <= 0:
return
close = float(candle.ClosePrice)
upper = sma_val + atr_val * 1.5
lower = sma_val - atr_val * 1.5
if self.Position > 0:
if close >= sma_val:
self.SellMarket()
self._entry_price = 0
self._grid_count = 0
elif close <= self._entry_price - atr_val * 5.0:
self.SellMarket()
self._entry_price = 0
self._grid_count = 0
elif self._grid_count < 4 and close <= self._entry_price - atr_val * 0.8:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.BuyMarket()
elif self.Position < 0:
if close <= sma_val:
self.BuyMarket()
self._entry_price = 0
self._grid_count = 0
elif close >= self._entry_price + atr_val * 5.0:
self.BuyMarket()
self._entry_price = 0
self._grid_count = 0
elif self._grid_count < 4 and close >= self._entry_price + atr_val * 0.8:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.SellMarket()
if self.Position == 0:
if close <= lower:
self._entry_price = close
self._grid_count = 0
self.BuyMarket()
elif close >= upper:
self._entry_price = close
self._grid_count = 0
self.SellMarket()
def CreateClone(self):
return rubberbands_3_strategy()