StockSharp port of the RUBBERBANDS 1.6 MetaTrader expert advisor. The original system keeps a hedged pair of buy and sell tickets, reinjects the closed side after each profit, and activates a safety net when the running loss exceeds predefined cash thresholds. The conversion keeps the alternating cycle but adapts the mechanics to StockSharp's net-position model by averaging in the direction of the current exposure instead of holding independent hedge orders.
Trading Logic
Cycle start: At the top of each minute, or when Enter Now is toggled, the strategy opens a market position using BaseVolume. The next cycle alternates direction (buy then sell, then buy again, etc.).
Base profit target: The running unrealized PnL is compared with TargetProfitPerLot * BaseVolume. When reached, the position is liquidated and the next cycle flips direction.
Session control:UseSessionTakeProfit and UseSessionStopLoss watch the cumulative realized plus unrealized profit measured in cash per base lot. Hitting either threshold triggers a full liquidation and resets the counters.
Safety mode: When enabled and the unrealized loss exceeds SafetyStartPerLot * BaseVolume, the algorithm enters safety mode and starts averaging in the current direction by sending additional orders of size SafetyVolume. Every extra SafetyStepPerLot loss per safety lot schedules another averaging order.
Safety exits: While in safety mode the position is flattened once the unrealized gain reaches SafetyProfitPerLot * |Position| or when the session level metric crosses SafetyModeTakeProfitPerLot * BaseVolume.
Entry Conditions
Long entries
No open exposure and either the minute just rolled over or Enter Now is true.
The strategy currently expects to open a long (cycles alternate).
Manual stop switch is disabled.
Short entries
Same as the long conditions but the next cycle direction is short.
Exit Management
Base target hit: Close the entire position and flip the cycle direction.
Session TP/SL: Close the position, clear realized profit counters and stay flat until the next minute trigger.
Safety profit: Close the position when the net PnL target is met while safety mode is active.
Safety averaging: Additional safety orders are appended when the unrealized loss grows in increments of SafetyStepPerLot.
Manual close: Setting Close Now closes the position on the next candle and resets the realized profit accumulator.
Parameters
Parameter
Description
BaseVolume
Market order size for the primary leg.
TargetProfitPerLot
Profit target (cash per lot) for the base trade.
UseSessionTakeProfit / SessionTakeProfitPerLot
Enable and configure the session-wide take profit.
UseSessionStopLoss / SessionStopLossPerLot
Enable and configure the session-wide stop loss.
UseSafetyMode
Toggle the safety averaging logic.
SafetyStartPerLot
Loss per base lot that activates safety mode.
SafetyVolume
Volume of each safety averaging order.
SafetyStepPerLot
Additional loss per safety lot needed to queue another safety order.
SafetyProfitPerLot
Profit target applied while in safety mode.
SafetyModeTakeProfitPerLot
Session-level profit target while safety mode is active.
Manual switches mirroring the original EA extern variables.
CandleType
Time frame of the candle series that drives the loop (default 1 minute).
Practical Notes
StockSharp keeps a single net position per instrument. Instead of holding simultaneous buy and sell tickets, the conversion averages into the existing position when safety mode is active. This preserves the cash-based thresholds while conforming to the netting model.
The profit and loss thresholds are expressed in account currency per lot, mirroring the MetaTrader extern inputs. Adjust them to match the instrument's tick value.
Manual switches (Stop Trading, Close Now, Enter Now, Quiesce) can be changed on the fly from the user interface to control the strategy without editing the code.
StartProtection() is invoked on start to reuse the standard StockSharp protection framework for risk controls.
Ensure the instrument metadata (VolumeStep, VolumeMin, VolumeMax) is configured so that the requested volumes pass exchange validation; the helper automatically aligns them to the nearest valid step.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rubberbands Safety Net: Mean reversion with Bollinger-style bands.
/// Buys at lower band, sells at upper band with ATR stops.
/// </summary>
public class RubberbandsSafetyNetStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _bandMult;
private decimal _entryPrice;
public RubberbandsSafetyNetStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_smaLength = Param(nameof(SmaLength), 20)
.SetDisplay("SMA Length", "SMA center band period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for bands and stops.", "Indicators");
_bandMult = Param(nameof(BandMult), 2.0m)
.SetDisplay("Band Mult", "ATR multiplier for bands.", "Signals");
}
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;
}
public decimal BandMult
{
get => _bandMult.Value;
set => _bandMult.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 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 * BandMult;
var lower = smaVal - atrVal * BandMult;
if (Position > 0)
{
if (close >= smaVal || close <= _entryPrice - atrVal * 3m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= smaVal || close >= _entryPrice + atrVal * 3m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close <= lower)
{
_entryPrice = close;
BuyMarket();
}
else if (close >= upper)
{
_entryPrice = close;
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.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
class rubberbands_safety_net_strategy(Strategy):
def __init__(self):
super(rubberbands_safety_net_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._sma_length = self.Param("SmaLength", 20) \
.SetDisplay("SMA Length", "SMA center band period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for bands and stops.", "Indicators")
self._band_mult = self.Param("BandMult", 2.0) \
.SetDisplay("Band Mult", "ATR multiplier for bands.", "Signals")
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmaLength(self):
return self._sma_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def BandMult(self):
return self._band_mult.Value
def OnStarted2(self, time):
super(rubberbands_safety_net_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._sma = SimpleMovingAverage()
self._sma.Length = self.SmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._sma, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
sv = float(sma_val)
av = float(atr_val)
if av <= 0:
return
close = float(candle.ClosePrice)
bm = float(self.BandMult)
upper = sv + av * bm
lower = sv - av * bm
if self.Position > 0:
if close >= sv or close <= self._entry_price - av * 3.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= sv or close >= self._entry_price + av * 3.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if close <= lower:
self._entry_price = close
self.BuyMarket()
elif close >= upper:
self._entry_price = close
self.SellMarket()
def OnReseted(self):
super(rubberbands_safety_net_strategy, self).OnReseted()
self._entry_price = 0.0
def CreateClone(self):
return rubberbands_safety_net_strategy()