Открыть на GitHub

Стратегия Elli

Обзор

Стратегия Elli переносит советник MetaTrader 4 «Elli» на высокоуровневый API StockSharp. В оригинале робот сочетал структуру Ichimoku Kinko Hyo на часовом таймфрейме и фильтр ADX на младшем периоде, дополняя их жёстким управлением рисками. При конверсии сохранены те же правила направленного отбора, ручные модификации ордеров заменены на StartProtection, а все настройки вынесены в StrategyParam<T>, чтобы параметры можно было оптимизировать и менять на лету.

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

  1. Трендовая структура Ichimoku
    • Подписка на свечи таймфрейма CandleType (по умолчанию H1) и расчёт линий Tenkan, Kijun и Senkou по исходным периодам 19/60/120.
    • Сигнал на покупку требует соблюдения цепочки Tenkan > Kijun > Senkou Span A > Senkou Span B и закрытия свечи выше Kijun. Для продаж условия зеркальные.
    • Абсолютное расстояние между Tenkan и Kijun должно быть больше TenkanKijunGapPips пунктов, что отсекает плоские облака.
  2. Подтверждение Directional Movement
    • Вторая подписка запускает индикатор Average Directional Index на таймфрейме AdxCandleType (по умолчанию M1).
    • Лонг разрешён только если предыдущий +DI меньше ConvertLow, а текущий +DI превышает ConvertHigh. Для шорта аналогично проверяется −DI. Тем самым воспроизводится ускорение, проверяемое через iADX в МQL.
  3. Исполнение входов
    • При выполнении условий отправляется рыночная заявка объёмом OrderVolume + |Position|, что сначала закрывает обратную позицию и затем открывает новую.
    • В любой момент времени допускается только одно направленное удержание, как и в оригинале с проверкой OrdersTotal() < 1.
  4. Управление риском
    • StartProtection добавляет симметричные стоп-лосс и тейк-профит, переводя расстояния в пунктах в абсолютные цены с учётом размера пункта инструмента.
    • Дальнейшее сопровождение позиции передано защитным ордерам, что соответствует поведению советника MT4.

Индикаторы и подписки

  • Основные свечи: CandleType (по умолчанию часовой график) для расчёта Ichimoku.
  • Свечи ADX: AdxCandleType (по умолчанию минутный график) для контроля +DI/−DI.
  • Индикаторы: Ichimoku (Tenkan, Kijun, Senkou Span B) и AverageDirectionalIndex (возвращает компоненты +DI/−DI).
  • При наличии области графика стратегия отображает свечи, индикаторы и собственные сделки.

Параметры

Имя Значение по умолчанию Описание
OrderVolume 1 Базовый объём рыночных заявок.
TakeProfitPips 60 Дистанция до тейк-профита в пунктах.
StopLossPips 30 Дистанция до стоп-лосса в пунктах.
TenkanPeriod 19 Период линии Tenkan-sen.
KijunPeriod 60 Период линии Kijun-sen.
SenkouSpanBPeriod 120 Период Senkou Span B для облака Ichimoku.
TenkanKijunGapPips 20 Минимальное расстояние между Tenkan и Kijun в пунктах.
ConvertHigh 13 Порог DI, который должен превысить текущее значение.
ConvertLow 6 Порог DI, ниже которого должно находиться предыдущее значение.
AdxPeriod 10 Период расчёта ADX.
CandleType H1 Таймфрейм для логики Ichimoku.
AdxCandleType M1 Таймфрейм для расчёта ADX и DI.

Все параметры реализованы через StrategyParam<T>, поэтому доступны для оптимизации и изменения в Designer.

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

  • Перевод пунктов учитывает особенности форекс-котировок (0.0001 для пятизнака и 0.01 для трёхзнака), что сохраняет исходные пороги.
  • Значения ADX кэшируются в полях _latestPlusDi, _previousPlusDi, _latestMinusDi, _previousMinusDi, повторяя обращения iADX со сдвигами 0 и 1.
  • Вызов IsFormedAndOnlineAndAllowTrading() гарантирует, что торговля начинается только после прогрева данных и индикаторов.
  • При входе объём вычисляется как Volume + Math.Abs(Position), благодаря чему стратегия автоматически переворачивается и поддерживает единственную позицию.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Elli: EMA crossover with ATR momentum confirmation.
/// Fast EMA above slow EMA = bullish, below = bearish.
/// Entry when ATR expansion confirms trend strength.
/// </summary>
public class ElliStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

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

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

		_fastLength = Param(nameof(FastLength), 19)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 60)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for momentum.", "Indicators");
	}

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

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

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

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevFast = 0;
		_prevSlow = 0;
		_prevAtr = 0;
		_entryPrice = 0;

		var fast = new ExponentialMovingAverage { Length = FastLength };
		var slow = new ExponentialMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fast);
			DrawIndicator(area, slow);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			_prevAtr = atrVal;
			return;
		}

		var close = candle.ClosePrice;

		// Exit: stop or take based on ATR
		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (fastVal < slowVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (fastVal > slowVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: EMA crossover with ATR expansion
		if (Position == 0)
		{
			var atrRising = atrVal > _prevAtr;

			if (_prevFast <= _prevSlow && fastVal > slowVal && atrRising)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevFast >= _prevSlow && fastVal < slowVal && atrRising)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
		_prevAtr = atrVal;
	}
}