Открыть на GitHub

Стратегия пересечения EMA RSI Volatility Adaptive

Стратегия является прямым портом советника MetaTrader EA_MARSI_1-02. Торговля ведётся по пересечениям двух копий пользовательского индикатора EMA_RSI_VA от Integer — это волатильно-адаптивная EMA, управляемая RSI. При каждом пересечении медленной и быстрой линий стратегия немедленно разворачивает позицию, полностью воспроизводя логику исходного советника и при этом соблюдая подходы StockSharp к управлению заявками.

Как устроен индикатор

В оригинальном пакете MQL присутствует индикатор EMA_RSI_VA. Он вычисляет EMA по цене, но её эффективный период меняется в зависимости от удалённости RSI от нейтрального уровня. В порте StockSharp используется класс EmaRsiVolatilityAdaptiveIndicator, реализующий исходную формулу:

  1. Рассчитать RSI по выбранному типу цены (AppliedPrice) с периодом RSIPeriod.
  2. Измерить расстояние RSI от 50 (|RSI - 50| + 1) — это прокси волатильности.
  3. Получить адаптивный множитель multi = (5 + 100 / RSIPeriod) / (0.06 + 0.92 * dist + 0.02 * dist^2).
  4. Умножить базовый период EMA на данный множитель и получить динамический период pdsx.
  5. Применить стандартную рекурсию EMA с коэффициентом сглаживания 2 / (pdsx + 1) и ценой из выбранного режима.

Чем дальше RSI уходит от 50, тем быстрее реагирует кривая; когда RSI «плоский», EMA удлиняется и сильнее фильтрует шум. Обе линии поддерживают весь набор режимов StockSharp.Messages.AppliedPrice.

Правила торговли

  • Сигналы
    • Продажа/шорт: ранее медленная < быстрой и сейчас медленная ≥ быстрой.
    • Покупка/лонг: ранее медленная > быстрой и сейчас медленная ≤ быстрой.
  • Исполнение
    • Анализируются только завершённые свечи выбранного типа.
    • При появлении сигнала отправляется рыночная заявка таким объёмом, чтобы закрыть текущую позицию и открыть новую в нужном направлении.
    • Соблюдаются ограничения биржи через Security.MinVolume, Security.VolumeStep и Security.MaxVolume.
  • Развороты
    • Объём рассчитывается так, чтобы одна команда SellMarket или BuyMarket перевела позицию через ноль — это соответствует поведению MQL-советника, где встречный сигнал сразу переворачивает сделку.

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

  • TakeProfitPoints и StopLossPoints повторяют поля TP/SL из советника (в пунктах). Если один из параметров больше нуля, стратегия запускает встроенный менеджер защит StockSharp с абсолютными отступами и useMarketOrders = true, что имитирует цикл модификации стопов/тейк-профитов через OrderSend.
  • UseBalanceMultiplier реализует переключатель use_Multpl. При его включении фактический объём становится равен Volume * Equity / MaxDrawdown, после чего нормализуется под ограничения инструмента.
  • Дополнительно вызывается StartProtection(), чтобы внешние модули могли подключать трейлинг или безубыток.

Параметры

Параметр Значение по умолчанию Описание
Volume 0.1 Базовый объём рыночной заявки до применения мультипликатора баланса.
TakeProfitPoints 0 Отступ тейк-профита в пунктах инструмента; 0 отключает тейк.
StopLossPoints 0 Отступ стоп-лосса в пунктах инструмента; 0 отключает защитный стоп.
UseBalanceMultiplier false Включает пропорциональное балансом управление объёмом (аналог use_Multpl).
MaxDrawdown 10000 Делитель для мультипликатора баланса; соответствует Max_drawdown в советнике.
SlowRsiPeriod 310 Период RSI для медленной линии EMA_RSI_VA.
SlowEmaPeriod 40 Базовый период EMA для медленной линии до адаптации по RSI.
SlowAppliedPrice Close Тип цены, который поступает в медленный индикатор.
FastRsiPeriod 200 Период RSI для быстрой линии EMA_RSI_VA.
FastEmaPeriod 50 Базовый период EMA для быстрой линии.
FastAppliedPrice Close Тип цены для быстрой линии.
CandleType TimeFrame(1m) Свечная серия, используемая в расчётах.

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

  • Использован высокоуровневый API StockSharp (SubscribeCandles().Bind(...)), поэтому нет ручных циклов обработки буферов.
  • Обрабатываются только завершённые свечи, что соответствует вызовам CopyBuffer(..., 1, 2, ...) в исходном коде.
  • Нормализация объёма использует Security.MinVolume, Security.VolumeStep и Security.MaxVolume, что предотвращает некорректные заявки на реальной бирже.
  • По запросу не создаётся Python-версия — директория содержит только реализацию на C# и документацию.

Полученная стратегия полностью повторяет поведение исходного советника, но предоставляет параметры и защитные механизмы, привычные для экосистемы StockSharp (Designer, Runner и т.д.).

using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA RSI VA Crossover: Fast/slow EMA crossover with RSI volatility filter.
/// </summary>
public class EmaRsiVaCrossStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;

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

	public EmaRsiVaCrossStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastEmaLength = Param(nameof(FastEmaLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowEmaLength = Param(nameof(SlowEmaLength), 40)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI 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;
	}

	/// <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 = 14 };

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

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

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}