Открыть на GitHub

Стратегия Slope RSI MTF

Обзор

Slope RSI MTF — это порт советника MetaTrader 4 SLOPE_RSI_MTF_LBranjord.mq4 вместе с пользовательским индикатором Slope_Direction_Line_Alert.mq4. Исходная система строила «линию направления наклона» (по сути Hull Moving Average) на нескольких таймфреймах и открывала сделки только тогда, когда все средние указывали в одну сторону и фильтр из четырёх RSI подтверждал импульс. Версия на StockSharp воспроизводит эту многофреймовую схему с помощью высокоуровневых подписок, сохраняет ATR-ориентированные выходы и расширяет набор параметров для удобной настройки.

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

  1. Подписаться на четыре серии свечей по одному инструменту: торговый таймфрейм (BaseTimeframe), часовой (HourTimeframe), четырёхчасовой (FourHourTimeframe) и дневной (DayTimeframe).
  2. Для каждой серии запустить отдельные индикаторы HullMovingAverage и RelativeStrengthIndex. Рабочий таймфрейм использует длину SlopeTriggerLength (по умолчанию 60), подтверждающие таймфреймы — SlopeTrendLength (по умолчанию 200).
  3. Хранить две последние величины Hull на каждом таймфрейме: если текущая выше предыдущей — фаза роста, если ниже — фаза снижения.
  4. Одновременно контролировать значения RSI:
    • Для покупки RSI на всех таймфреймах должен быть выше RsiMiddleLevel (50) и ниже RsiUpperBound (90).
    • Для продажи RSI должен быть ниже RsiMiddleLevel, но выше RsiLowerBound (10).
  5. После закрытия свечи торгового таймфрейма стратегия проверяет согласованность направлений. Если все Hull растут — открывается длинная позиция, если все падают — короткая. До формирования первого полного значения каждого индикатора сигналы игнорируются.
  6. Перед размещением заявки рассчитываются защитные расстояния: часовая серия ATR задаёт стоп-лосс, дневная серия ATR — тейк-профит.
  7. Сделки открываются рыночными ордерами с учётом лимита MaxOrders. В неттинговом режиме StockSharp перед открытием новой сделки закрывает противоположную позицию.
  8. После каждого входа пересчитываются цены стопа и тейка. На последующих свечах базового таймфрейма стратегия сравнивает high/low с целями и, при пробое, закрывает позицию рыночным ордером.

Управление рисками и размер позиции

  • Параметр UseCompounding включает формулу исходного советника: volume = PortfolioValue / BalanceDivider. При выключении используется фиксированный объём BaseVolume.
  • Метод AdjustVolume округляет объём к шагу VolumeStep, соблюдает ограничения MinVolume/MaxVolume и записывает итог в Strategy.Volume для согласованности с ручными действиями.
  • AtrPeriod (по умолчанию 21) применяется для обеих защитных дистанций: часовой ATR — для стоп-лосса, дневной ATR — для тейк-профита.
  • Счётчики _longEntries и _shortEntries гарантируют, что число добавочных входов в каждом направлении не превысит MaxOrders.

Работа с несколькими таймфреймами

  • Все подписки создаются через SubscribeCandles() и обрабатываются методом Bind, поэтому индикаторы получают данные потоково без ручного хранения истории.
  • Вспомогательный класс TimeframeState сохраняет текущие и предыдущие значения Hull и RSI, что позволяет сравнивать наклон без обращения к буферам индикаторов.
  • Значения ATR учитываются только после IsFormed == true, обеспечивая расчёт стопов и тейков по полностью сформированным свечам.

Параметры

Имя Тип Значение по умолчанию Аналог в MetaTrader Описание
SlopeTriggerLength int 60 SDL1_trigger Длина Hull на торговом таймфрейме.
SlopeTrendLength int 200 SDL1_period Длина Hull на подтверждающих таймфреймах.
RsiPeriod int 14 период RSI Окно RSI для всех таймфреймов.
RsiLowerBound decimal 10 нижняя граница RSI Фильтр для коротких сигналов.
RsiMiddleLevel decimal 50 центральный уровень RSI Разделяет бычьи и медвежьи режимы.
RsiUpperBound decimal 90 верхняя граница RSI Фильтр для длинных сигналов.
AtrPeriod int 21 ATR_Period Длина ATR для расчёта стопа и тейка.
MaxOrders int 5 MaxOrders Максимум добавочных входов в одном направлении.
UseCompounding bool true compounding Включает расчёт объёма от капитала.
BaseVolume decimal 0.1 Lots Фиксированный объём при отключённом компаундинге.
BalanceDivider decimal 100000 AccountBalance/100000 Делитель в формуле объёма.
BaseTimeframe DataType 5m рабочий таймфрейм Серия, по которой принимаются решения.
HourTimeframe DataType 1h PERIOD_H1 Первая подтверждающая серия.
FourHourTimeframe DataType 4h PERIOD_H4 Вторая подтверждающая серия.
DayTimeframe DataType 1d PERIOD_D1 Старший подтверждающий таймфрейм.

Отличия от оригинального советника

  • StockSharp работает в неттинговом режиме, поэтому стратегия закрывает встречную позицию перед открытием новой, тогда как MetaTrader 4 позволял держать хеджирующие ордера.
  • Стопы и тейки реализованы через проверку закрытых свечей, а не через модификацию ордеров на стороне брокера, но расстояния по-прежнему соответствуют ATR из оригинала.
  • Используются встроенные индикаторы HullMovingAverage, RelativeStrengthIndex и AverageTrueRange, что соответствует рекомендациям по высокоуровневому API и исключает прямой доступ к буферам.
  • Все параметры сопровождаются метаданными SetDisplay, что облегчает локализацию и оптимизацию.

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

  • Держите подтверждающие таймфреймы не меньше рабочего, иначе теряется идея иерархии трендов.
  • Проверьте заполненность атрибутов инструмента (PriceStep, VolumeStep, MinVolume, MaxVolume), чтобы округление объёмов и цен работало корректно.
  • Так как выходы проверяются на закрытии свечи базового таймфрейма, для более быстрых реакций можно сократить таймфрейм или дополнить стратегию внутридневным контролем.
  • Условие наклона Hull требует, чтобы текущая и предыдущая величины отличались. При «плоском» индикаторе новые сделки не открываются, что соответствует условию SDL > SDL[1] в оригинальном коде.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Slope RSI MTF: EMA crossover with RSI filter and ATR stops.
/// </summary>
public class SlopeRsiMtfStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public SlopeRsiMtfStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 12)
			.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI 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 FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
	public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
			else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
		}
		_prevFast = fastVal; _prevSlow = slowVal;
	}
}