Ver en GitHub

Bollinger RSI MA Strategy

Overview

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

  1. Subscribe to the primary candle series (default: 1 hour) and calculate Bollinger Bands and RSI on the same timeframe.
  2. 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.
  3. 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.
  4. 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.
  5. 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;
		}
	}
}