Auf GitHub ansehen

Rubberbands 3 Strategy

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

  1. Record the current close price as both the running maximum and minimum (or reuse saved values when restarting).
  2. 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.
  3. When the price falls by PipStep points below the current minimum, submit a market sell order of size OrderVolume and update the minimum.
  4. 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.
  5. 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.
  6. 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();
			}
		}
	}
}