The Bollinger RSI MA strategy ports the MetaTrader expert BolRSIMAs to the StockSharp high-level API. The system combines a
Bollinger Band breakout, an RSI filter and a higher timeframe exponential moving average (EMA) to identify pullback trades in
the direction of the dominant trend. Auto-lot sizing is preserved: when enabled the strategy converts the configured risk
fraction of portfolio equity into volume using the current price, the Bollinger stop distance and the instrument contract size.
Trading logic
Subscribe to the primary candle series (default: 1 hour) and calculate Bollinger Bands and RSI on the same timeframe.
Subscribe to daily candles and feed their closing prices into a 200-period EMA to reproduce the higher timeframe filter used
in the original EA.
Generate a long setup when the latest candle closes below the lower band, the RSI value is below the oversold threshold
and the close remains above the daily EMA. A short setup is triggered by a close above the upper band, RSI above the
overbought threshold and price below the daily EMA.
Open positions only when no exposure is active. Every new trade stores stop-loss and take-profit levels derived from the
previous Bollinger values: longs use lowerBand - StopLossOffset and target the middle band; shorts use
upperBand + StopLossOffset and target the middle band as well.
On each finished candle the strategy checks the candle extremums against the protective levels. If the low/high touches the
stop or target, the position is closed immediately, emulating the protective orders placed by the MetaTrader version.
Parameters
Name
Default
Description
CandleType
1-hour candles
Primary timeframe processed by Bollinger Bands and RSI.
DailyCandleType
1-day candles
Higher timeframe that feeds the EMA trend filter.
BollingerPeriod
20
Number of candles used to build Bollinger Bands.
BollingerDeviation
2
Band width multiplier.
RsiPeriod
13
RSI smoothing length.
RsiUpperLevel
70
Overbought threshold required for short trades.
RsiLowerLevel
30
Oversold threshold required for long trades.
MaPeriod
200
Length of the higher timeframe EMA.
StopLossOffset
0.0238
Extra buffer added outside the band before placing the stop-loss.
UseAutoLot
true
Enables risk-based position sizing.
RiskPerTrade
0.05
Fraction of equity allocated to each trade when auto lot is active.
FixedVolume
0.1
Order size when auto lot sizing is disabled.
Money management
When UseAutoLot is true, volume equals (equity * RiskPerTrade) / (StopLossOffset * price * contractSize) rounded to the
exchange limits. This mirrors the MetaTrader autolot routine, which divides the risk amount by the stop distance in cash and
the contract size.
If equity information or price is unavailable, the strategy falls back to FixedVolume while still respecting the
instrument volume constraints.
Differences from the MetaTrader expert
Stop-loss and take-profit orders are simulated through candle highs and lows instead of server-side orders, matching the
outcome of the original EA without relying on synchronous order submission.
The EMA filter uses StockSharp's candle subscriptions; there is no dependency on MetaTrader-specific daily data calls.
Risk sizing honors StockSharp security limits (MinVolume, MaxVolume, VolumeStep) to avoid rejected orders on exchanges.
Usage tips
Adjust StopLossOffset when trading symbols with different price scales so that the distance reflects the original EA's
2.38% buffer beyond the Bollinger Band.
If the instrument uses a different daily timeframe (e.g., crypto exchanges), change DailyCandleType accordingly so the EMA
reflects the intended trend filter.
Combine the strategy with external trailing stops if you prefer dynamic exits once the middle band target is reached.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Bollinger + RSI + MA strategy.
/// Buys when price at lower BB and RSI oversold, sells at upper BB and RSI overbought.
/// </summary>
public class BollingerRsiMaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bandPercent;
private readonly StrategyParam<int> _signalCooldownCandles;
private int _candlesSinceTrade;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
public decimal BandPercent { get => _bandPercent.Value; set => _bandPercent.Value = value; }
public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }
public BollingerRsiMaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_bbPeriod = Param(nameof(BbPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bandPercent = Param(nameof(BandPercent), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Band Percent", "MA percentage band width", "Signals");
_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candlesSinceTrade = SignalCooldownCandles;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ma = new SimpleMovingAverage { Length = BbPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal maValue)
{
if (candle.State != CandleStates.Finished) return;
if (_candlesSinceTrade < SignalCooldownCandles)
_candlesSinceTrade++;
var close = candle.ClosePrice;
var upper = maValue * (1 + BandPercent);
var lower = maValue * (1 - BandPercent);
// Mean reversion: buy at lower band, sell at upper band
if (close < lower && rsiValue < 35 && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (close > upper && rsiValue > 65 && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
}
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 RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bollinger_rsi_ma_strategy(Strategy):
def __init__(self):
super(bollinger_rsi_ma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._rsi_period = self.Param("RsiPeriod", 14)
self._bb_period = self.Param("BbPeriod", 20)
self._band_percent = self.Param("BandPercent", 0.01)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def BbPeriod(self):
return self._bb_period.Value
@BbPeriod.setter
def BbPeriod(self, value):
self._bb_period.Value = value
@property
def BandPercent(self):
return self._band_percent.Value
@BandPercent.setter
def BandPercent(self, value):
self._band_percent.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(bollinger_rsi_ma_strategy, self).OnReseted()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(bollinger_rsi_ma_strategy, self).OnStarted2(time)
self._candles_since_trade = self.SignalCooldownCandles
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
ma = SimpleMovingAverage()
ma.Length = self.BbPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ma, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ma_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
ma_val = float(ma_value)
upper = ma_val * (1.0 + self.BandPercent)
lower = ma_val * (1.0 - self.BandPercent)
if close < lower and rsi_val < 35 and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif close > upper and rsi_val > 65 and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return bollinger_rsi_ma_strategy()