Открыть на GitHub

Стратегия Sidus EMA RSI

Стратегия представляет собой перенос эксперта MetaTrader 4 Exp_Sidus.mq4 на платформу StockSharp. В логике сохранено сочетание пересечения быстрой и медленной EMA с фильтрацией по уровню 50 индикатора RSI. Все расчёты выполняются только по закрытым свечам, и каждая свеча может сгенерировать не более одного сигнала — полностью повторяя поведение исходного советника.

Торговая логика

  • Набор индикаторов
    • Быстрая экспоненциальная скользящая средняя (период 5)
    • Медленная экспоненциальная скользящая средняя (период 12)
    • Индекс относительной силы RSI (период 21)
  • Условия для покупок
    1. На предыдущей сигнальной свече быстрая EMA была ниже или равна медленной.
    2. На текущей сигнальной свече быстрая EMA пересекает медленную снизу вверх.
    3. Значение RSI на этой же свече строго выше 50.
  • Условия для продаж
    1. На предыдущей сигнальной свече быстрая EMA была выше или равна медленной.
    2. На текущей сигнальной свече быстрая EMA пересекает медленную сверху вниз.
    3. Значение RSI на этой же свече строго ниже 50.
  • Сдвиг сигнала — параметр SignalShift (по умолчанию 1) определяет, какая закрытая свеча считается текущей сигнальной. Значение 1 означает последнюю закрытую свечу, 0 — только что закрытую, 2 — свечу двумя барами ранее и т.д. Свеча для сравнения вычисляется автоматически как SignalShift + 1.
  • Защита от повторов — стратегия запоминает время открытия сигнальной свечи и не открывает новую позицию, если по этой свече уже был совершён вход, что воспроизводит проверку LastTime в оригинале.

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

  • Одновременно открыта только одна позиция.
  • При появлении противоположного сигнала активная позиция закрывается вызовом ClosePosition(), и лишь после этого, на следующем проходе обработки, стратегия может открыть сделку в новом направлении — точь-в-точь как в MQL версии.
  • Метод StartProtection прикрепляет стоп-лосс и тейк-профит, заданные в шагах цены. Значения по умолчанию: тейк-профит 80 пунктов, стоп-лосс 20 пунктов.

Параметры

Параметр Описание Значение по умолчанию Примечание
TakeProfitPoints Расстояние до тейк-профита в шагах цены 80 0 отключает цель
StopLossPoints Расстояние до стоп-лосса в шагах цены 20 0 отключает защиту
TradeVolume Объём заявки (лоты/контракты) 0.1 На старте присваивается свойству Volume
FastPeriod Период быстрой EMA 5 Доступна оптимизация
SlowPeriod Период медленной EMA 12 Доступна оптимизация
RsiPeriod Период RSI 21 Доступна оптимизация
SignalShift Количество закрытых свечей для расчёта сигнала 1 Аналог параметра shif в MT4
CandleType Тип используемых свечей Таймфрейм 1 час Можно задать любой DataType

Особенности реализации

  • Подписка на свечи выполняется через SubscribeCandles(CandleType); обработка ведётся только после перехода свечи в состояние CandleStates.Finished.
  • Для доступа к значениям индикаторов на нужных свечах используется компактная очередь, что позволяет соблюдать требование репозитория и не обращаться к GetValue.
  • Если в рынке есть позиция противоположного направления, сначала вызывается ClosePosition(), а уже затем стратегия может отправить приказ BuyMarket или SellMarket после фактического закрытия.
  • Журналирование ведётся на английском языке для удобства анализа.

Примечания по конверсии

  • Расстояния стопов умножаются на PriceStep инструмента, что эквивалентно использованию Point в MetaTrader.
  • Объём 0.1 соответствует значению параметра Lots в исходном советнике.
  • Порог RSI фиксирован на уровне 50, как и в оригинальной реализации.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Sidus EMA + RSI strategy: fast EMA crosses slow EMA confirmed by RSI above/below 50.
/// </summary>
public class SidusEmaRsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;

	private decimal _prevFast;
	private decimal _prevSlow;

	public SidusEmaRsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 12)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetDisplay("RSI Period", "RSI period.", "Indicators");
	}

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
	}

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

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, rsi, 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 fastValue, decimal slowValue, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var bullishCross = _prevFast <= _prevSlow && fastValue > slowValue;
		var bearishCross = _prevFast >= _prevSlow && fastValue < slowValue;

		// Exit existing positions on opposite cross
		if (Position > 0 && bearishCross)
		{
			SellMarket();
		}
		else if (Position < 0 && bullishCross)
		{
			BuyMarket();
		}

		// Entry on crossover confirmed by RSI
		if (Position == 0)
		{
			if (bullishCross && rsiValue > 50)
			{
				BuyMarket();
			}
			else if (bearishCross && rsiValue < 50)
			{
				SellMarket();
			}
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}