Открыть на GitHub

Стратегия Eliot Wave (порт MQL4 «Eliot Wave I»)

Обзор

Eliot Wave Strategy — это перенос эксперта MetaTrader 4 «Eliot Wave I» на StockSharp API. Стратегия объединяет пересечение двух линейно-взвешенных средних (LWMA), подтверждение импульса по моментуму на старшем таймфрейме и долгосрочный фильтр MACD. Такой набор условий позволяет ловить импульсные движения по тренду и одновременно удерживать риск под контролем.

Основные индикаторы

  • Быстрая LWMA (по умолчанию 6) — оценивает краткосрочное направление по типичной цене (High + Low + Close) / 3.
  • Медленная LWMA (по умолчанию 85) — определяет общий тренд на том же таймфрейме.
  • Momentum (период 14) — рассчитывается на старшем таймфрейме, преобразуется в отклонение от уровня 100 и проверяет силу импульса.
  • MACD (12, 26, 9) — строится на очень медленном таймфрейме (по умолчанию ~месячные свечи) и служит фильтром глобального тренда. Покупки разрешены только при значении MACD выше сигнальной линии, продажи — при значении ниже.

Параметры

Название Описание Значение по умолчанию
Base Candle Базовый таймфрейм для расчёта LWMA. 15-минутные свечи
Momentum Candle Старший таймфрейм для моментума. Часовые свечи
MACD Candle Таймфрейм для долгосрочного MACD. Свечи по 30 дней
Fast LWMA Период быстрой LWMA. 6
Slow LWMA Период медленной LWMA. 85
Momentum Period Период моментума на старшем таймфрейме. 14
Momentum Buy Threshold Минимальное отклонение от 100 для подтверждения покупки. 0.3
Momentum Sell Threshold Минимальное отклонение от 100 для подтверждения продажи. 0.3
Stop Loss (pts) Размер стоп-лосса в пунктах инструмента. 20
Take Profit (pts) Размер тейк-профита в пунктах инструмента. 50
Trade Volume Объём одной сделки. 1 лот
Max Position Максимальный совокупный объём позиции (аналог MQL-параметра Max_Trades). 10 лотов

Все параметры реализованы через StrategyParam<T>, поэтому их можно оптимизировать в Designer или Runner.

Торговые правила

  1. Фильтр тренда и структуры
    • Для покупок быстрая LWMA должна быть выше медленной.
    • Для продаж — ниже медленной.
    • Требуется перекрытие двух последних закрытых свечей: Low[2] < High[1] для лонга и Low[1] < High[2] для шорта. Это условие повторяет консервацию из оригинального советника.
  2. Подтверждение моментума
    • Отклонение моментума вычисляется как abs(momentum - 100).
    • Если любое из трёх последних значений превышает порог, импульс считается достаточным.
  3. Фильтр глобального тренда
    • Для входа в лонг линия MACD должна быть выше сигнальной.
    • Для шорта — ниже сигнальной.
  4. Исполнение
    • При выполнении всех условий отправляется рыночный ордер, который сначала переворачивает текущую позицию и добавляет указанный объём.
    • Переворот позиции разрешён, что соответствует поведению оригинального эксперта.

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

  • Метод StartProtection автоматически задаёт стоп-лосс и тейк-профит в пунктах.
  • Дополнительная логика закрывает лонг, если быстрая LWMA пересекает медленную сверху вниз или MACD становится медвежьим, и аналогично для шорта.
  • Параметр Max Position ограничивает суммарную позицию, что повторяет работу Max_Trades в MQL4.

Отличия от оригинала

  • Проверки пользовательских трендовых линий и отправка уведомлений удалены — в StockSharp нет прямых аналогов этих функций.
  • Многоступенчатые трейлинг-стопы и переход в безубыток заменены на базовый StartProtection. При необходимости их можно добавить вручную.
  • Защита по капиталу в деньгах не реализована; управление риском выполняется через стопы и лимит позиции.

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

  1. Убедитесь, что доступны все три потока свечей (базовый, моментумный и для MACD).
  2. Настройте объём, стопы и пороги в соответствии с волатильностью инструмента.
  3. При необходимости оптимизируйте отдельные пороги для покупок и продаж.
  4. Используйте встроенную визуализацию (свечи, LWMA, сделки) для контроля работы стратегии.

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

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;

public class EliotWaveStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EliotWaveStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}