Открыть на GitHub

Стратегия Bollinger RSI MA

Обзор

Стратегия Bollinger RSI MA переносит эксперта MetaTrader BolRSIMAs на высокоуровневый API StockSharp. Алгоритм сочетает пробой полос Боллинджера, фильтр RSI и экспоненциальную скользящую среднюю (EMA) старшего таймфрейма, чтобы находить точки входа по тренду после отката. Реализовано автолотовое управление, повторяющее оригинал: при включении стратегия рассчитывает объём сделки из доли капитала, текущей цены, расстояния до стоп-лосса и размера контракта инструмента.

Логика торговли

  1. Подписаться на основной поток свечей (по умолчанию часовые) и рассчитать по нему полосы Боллинджера и RSI.
  2. Подписаться на дневные свечи и передавать их закрытия в EMA с периодом 200, как это делал исходный советник.
  3. Формировать сигнал покупки, когда закрытие свечи ниже нижней полосы, значение RSI ниже уровня перепроданности, а цена остаётся выше дневной EMA. Сигнал продажи возникает при закрытии выше верхней полосы, RSI выше уровня перекупленности и цене ниже дневной EMA.
  4. Открывать позицию только при отсутствии действующих сделок. После входа фиксировать уровни защиты по значениям полос: для лонга стоп равен нижняя полоса - StopLossOffset, цель — средняя полоса; для шорта стоп верхняя полоса + StopLossOffset, цель также средняя полоса.
  5. На закрытии каждой свечи проверять её минимум и максимум. Если цена достигает стоп-лосса или тейк-профита, позиция закрывается, что имитирует заявки, выставлявшиеся советником MetaTrader.

Параметры

Параметр Значение по умолчанию Описание
CandleType Часовые свечи Таймфрейм для расчёта полос Боллинджера и RSI.
DailyCandleType Дневные свечи Старший таймфрейм для EMA.
BollingerPeriod 20 Период полос Боллинджера.
BollingerDeviation 2 Коэффициент ширины полос.
RsiPeriod 13 Период RSI.
RsiUpperLevel 70 Уровень перекупленности для шортов.
RsiLowerLevel 30 Уровень перепроданности для лонгов.
MaPeriod 200 Период EMA старшего таймфрейма.
StopLossOffset 0.0238 Дополнительное расстояние от полосы до стоп-лосса.
UseAutoLot true Включает автолотовое управление объёмом.
RiskPerTrade 0.05 Доля капитала, рискуемая в одной сделке при автолоте.
FixedVolume 0.1 Фиксированный объём, если автолот отключён.

Управление капиталом

  • При включённом UseAutoLot объём рассчитывается по формуле (equity * RiskPerTrade) / (StopLossOffset * price * contractSize) с учётом биржевых ограничений по минимальному, максимальному объёмам и шагу. Это повторяет логику autolot в оригинальном советнике.
  • Если данные о капитале или цене недоступны, стратегия использует FixedVolume, но всё равно приводит объём к лимитам инструмента.

Отличия от MetaTrader-версии

  • Стоп- и тейк-профиты моделируются проверкой экстремумов свечи, а не реальными заявками, что позволяет добиться идентичного результата без зависимостей от торгового сервера.
  • EMA рассчитывается через подписку на свечи StockSharp, поэтому нет обращений к функциям MetaTrader CopyBuffer/iMA.
  • Расчёт объёма учитывает свойства инструмента MinVolume, MaxVolume, VolumeStep, предотвращая отказы биржи из-за неверного объёма.

Рекомендации по использованию

  • Подбирайте StopLossOffset под масштаб цены инструмента, чтобы сохранить буфер около 2.38% за пределами полосы Боллинджера.
  • Для рынков с иным «дневным» таймфреймом (криптовалюты, круглосуточные фьючерсы) измените DailyCandleType, чтобы EMA отражала нужный период тренда.
  • При необходимости продолжать сопровождение сделки после достижения средней полосы используйте внешние трейлинг-стопы.
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;
		}
	}
}