Открыть на GitHub

Стратегия RSI MA on RSI Dual

Общее описание

Стратегия RSI MA on RSI Dual переносит эксперта MetaTrader "RSI_MAonRSI_Dual" в экосистему StockSharp. Используются две линии RSI с разными периодами, для каждой из которых рассчитывается простое скользящее среднее. Торговые решения принимаются, когда сглаженные линии RSI пересекаются, находясь при этом по одну сторону от заданного нейтрального уровня.

Реализация сохраняет ключевую логику оригинального робота и добавляет параметры для ограничения времени торговли, выбора направления и инверсии сигналов.

Индикаторы

  • Быстрый RSI — RSI с настраиваемым периодом.
  • Медленный RSI — RSI с собственным периодом.
  • Скользящее среднее по RSI — простое скользящее среднее, вычисляемое поверх каждой серии значений RSI.

Все индикаторы используют один и тот же тип цены (по умолчанию закрытие). Сглаженные линии RSI выводятся на отдельную область графика.

Правила входа

  1. Дождитесь формирования обеих сглаженных линий RSI на завершившейся свече.
  2. Покупка
    • Быстрая линия пересекает медленную снизу вверх (предыдущее значение ниже, текущее выше).
    • Обе сглаженные линии находятся ниже нейтрального уровня (по умолчанию 50).
  3. Продажа
    • Быстрая линия пересекает медленную сверху вниз (предыдущее значение выше, текущее ниже).
    • Обе сглаженные линии находятся выше нейтрального уровня.
  4. Параметр ReverseSignals позволяет поменять направления сигналов местами.
  5. На одной свече открывается не более одной позиции — повторные сигналы игнорируются.

Управление позицией

  • AllowLong и AllowShort включают или отключают торговлю в соответствующем направлении.
  • CloseOpposite закрывает противоположную позицию перед разворотом.
  • OnlyOnePosition запрещает открывать новую сделку при наличии любой позиции.
  • Рыночные заявки отправляются с объёмом, указанным в параметре Volume стратегии.

Временной фильтр

Параметр UseTimeFilter включает фильтрацию по торговой сессии. При включенном фильтре сделки допускаются только между SessionStart и SessionEnd. Поддерживаются интервалы, пересекающие полуночь. Время берётся из таймстемпов свечей в часовом поясе торговой площадки.

Параметры

Параметр Описание
CandleType Тип свечей, используемых в анализе.
FastRsiPeriod Период быстрого RSI.
SlowRsiPeriod Период медленного RSI.
MaPeriod Длина сглаживающего скользящего среднего.
AppliedPrice Тип цены, подаваемой на расчёт RSI.
NeutralLevel Нейтральный уровень, разделяющий бычью и медвежью зоны.
AllowLong / AllowShort Разрешение торговли в длинную или короткую сторону.
ReverseSignals Инверсия направлений сигналов.
CloseOpposite Закрывать противоположную позицию перед входом.
OnlyOnePosition Ограничение на одну открытую позицию.
UseTimeFilter Включение фильтра по времени.
SessionStart / SessionEnd Границы торгового окна.

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

  • Блоки управления капиталом, стоп-лоссы и трейлинг-стопы из версии MQL5 не перенесены. В StockSharp стратегия выставляет рыночные приказы фиксированным объёмом.
  • Удалены выводы в журнал и всплывающие сообщения — для диагностики следует использовать стандартные механизмы StockSharp.
  • Отслеживание сделок реализовано через событийную модель заявок StockSharp.

Несмотря на перечисленные отличия, логика генерации сигналов и фильтрация направлений полностью соответствуют исходному эксперту.

using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Dual moving averages calculated on top of RSI values.
/// Fast RSI MA crossing slow RSI MA generates entry signals.
/// </summary>
public class RsiMaOnRsiDualStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastRsiPeriod;
	private readonly StrategyParam<int> _slowRsiPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private RelativeStrengthIndex _fastRsi;
	private RelativeStrengthIndex _slowRsi;
	private readonly Queue<decimal> _fastRsiHistory = new();
	private readonly Queue<decimal> _slowRsiHistory = new();

	private decimal? _previousFastMa;
	private decimal? _previousSlowMa;

	public RsiMaOnRsiDualStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle type", "Candles processed by the strategy.", "General");

		_fastRsiPeriod = Param(nameof(FastRsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Fast RSI period", "Length of the fast RSI smoothing window.", "Indicators");

		_slowRsiPeriod = Param(nameof(SlowRsiPeriod), 28)
			.SetGreaterThanZero()
			.SetDisplay("Slow RSI period", "Length of the slow RSI smoothing window.", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("MA period", "Number of RSI values averaged by the smoothing moving average.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastRsiPeriod
	{
		get => _fastRsiPeriod.Value;
		set => _fastRsiPeriod.Value = value;
	}

	public int SlowRsiPeriod
	{
		get => _slowRsiPeriod.Value;
		set => _slowRsiPeriod.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousFastMa = null;
		_previousSlowMa = null;
		_fastRsiHistory.Clear();
		_slowRsiHistory.Clear();
		_fastRsi = null!;
		_slowRsi = null!;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_previousFastMa = null;
		_previousSlowMa = null;
		_fastRsiHistory.Clear();
		_slowRsiHistory.Clear();

		_fastRsi = new RelativeStrengthIndex { Length = FastRsiPeriod };
		_slowRsi = new RelativeStrengthIndex { Length = SlowRsiPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_fastRsi, _slowRsi, ProcessCandle)
			.Start();

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, subscription);
			DrawOwnTrades(priceArea);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastRsiValue, decimal slowRsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_fastRsiHistory.Enqueue(fastRsiValue);
		_slowRsiHistory.Enqueue(slowRsiValue);
		while (_fastRsiHistory.Count > MaPeriod)
			_fastRsiHistory.Dequeue();
		while (_slowRsiHistory.Count > MaPeriod)
			_slowRsiHistory.Dequeue();

		if (!_fastRsi.IsFormed || !_slowRsi.IsFormed)
			return;

		if (_fastRsiHistory.Count < MaPeriod || _slowRsiHistory.Count < MaPeriod)
			return;

		// Calculate SMA of each RSI
		var fastSum = 0m;
		var fastHistory = _fastRsiHistory.ToArray();
		foreach (var v in fastHistory)
			fastSum += v;
		var fastMa = fastSum / MaPeriod;

		var slowSum = 0m;
		var slowHistory = _slowRsiHistory.ToArray();
		foreach (var v in slowHistory)
			slowSum += v;
		var slowMa = slowSum / MaPeriod;

		if (_previousFastMa is null || _previousSlowMa is null)
		{
			_previousFastMa = fastMa;
			_previousSlowMa = slowMa;
			return;
		}

		var crossUp = _previousFastMa < _previousSlowMa && fastMa > slowMa;
		var crossDown = _previousFastMa > _previousSlowMa && fastMa < slowMa;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(volume);
		}

		_previousFastMa = fastMa;
		_previousSlowMa = slowMa;
	}
}