Открыть на GitHub

Стратегия Eliot Waves

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

Стратегия Eliot Waves — это перенос одноимённого советника MetaTrader в экосистему StockSharp. Алгоритм использует высокоуровневый API SubscribeCandles().BindEx(...), чтобы работать только с завершёнными свечами выбранного таймфрейма, и воссоздаёт основные блоки оригинала: поиск тренда по двум LWMA, подтверждение импульса через Momentum, а также выходы по полосам Боллинджера и MACD.

Логика входа

  1. Фильтр тренда. Быстрая LWMA (период 6) должна находиться выше медленной LWMA (период 85) для открытия длинной позиции. Для коротких сигналов требуется противоположное соотношение.
  2. Подтверждение импульса. Показания индикатора Momentum (период 14) за три последних закрытых бара анализируются на отклонение от значения 100. Если хотя бы одно из них превышает заданный порог (по умолчанию 0.3), то фильтр пропускает сигнал.
  3. Структура свечей. Для покупок минимум свечи два бара назад должен быть ниже максимума предыдущей свечи. Для продаж минимум предыдущей свечи должен располагаться ниже максимума свечи два бара назад. Тем самым реализуется фильтр дивергенции, присутствовавший в MQL-версии.
  4. Наращивание позиции. Каждый сигнал добавляет фиксированный объём (по умолчанию 0.1) до достижения лимита шагов (MaxPositions, по умолчанию 10). Перед открытием новой позиции стратегия закрывает противоположное плечо.

Управление рисками и выходами

  • Стоп-лосс и тейк-профит. Задаются в пунктах и пересчитываются после каждого изменения позиции относительно средней цены входа.
  • Трейлинг-стоп. При включённом параметре EnableTrailing стоп подтягивается вслед за ценой, как только плавающая прибыль превышает дистанцию TrailingStopPips.
  • Перевод в безубыток. После достижения BreakEvenTriggerPips стоп переносится на цену входа плюс/минус отступ BreakEvenOffsetPips в зависимости от направления сделки.
  • Выход по полосам Боллинджера. Длинные позиции закрываются при касании нижней полосы (период 20, ширина 2), короткие — при касании верхней. Этот блок повторяет защитный механизм исходного советника.
  • Контроль MACD. Дополнительно позиции закрываются при пересечении линий MACD (12, 26, 9) против направления сделки, что заменяет месячный MACD-фильтр из MQL.
  • Принудительное закрытие. Параметр EnableExitStrategy позволяет мгновенно зафиксировать прибыль/убыток по всем открытым позициям.

Параметры

Имя Описание Значение по умолчанию
TradeVolume Объём одной ступени наращивания позиции. 0.1
CandleType Таймфрейм свечей, используемых в расчётах. Свечи 15 минут
FastMaPeriod / SlowMaPeriod Периоды быстрых и медленных LWMA. 6 / 85
MomentumPeriod Глубина индикатора Momentum. 14
MomentumThreshold Минимальное отклонение Momentum от 100 для допуска сделки. 0.3
StopLossPips / TakeProfitPips Размеры стоп-лосса и тейк-профита в пунктах. 20 / 50
EnableTrailing / TrailingStopPips Переключатель и дистанция трейлинг-стопа. true / 40
EnableBreakEven, BreakEvenTriggerPips, BreakEvenOffsetPips Настройки перевода в безубыток. true, 30, 30
MaxPositions Максимальное количество ступеней позиции. 10
EnableExitStrategy Включение принудительного выхода. false

Особенности конвертации

  • Все индикаторы обновляются внутри одного пайплайна BindEx, поэтому стратегия работает только по завершённым свечам и не обращается к индикаторам напрямую.
  • Конвертация пунктов в цену использует PriceStep инструмента. Если брокер не предоставляет точное значение, стратегия выдаёт предупреждение и использует шаг цены как приближение, аналогично поведению исходного кода.
  • Логика уведомлений (email, push, Alert) из MQL опущена и заменена стандартными журналами StockSharp.
  • Управление стопами выполняется внутри стратегии: заявки выставляются рыночными ордерами, что делает поведение повторяемым в тестах.

Рекомендации по применению

  1. Подбирайте значения TradeVolume и MaxPositions в соответствии с размером счёта и рисковым профилем.
  2. Для инструментов с иной волатильностью откалибруйте MomentumThreshold, StopLossPips и TrailingStopPips перед запуском на реальных данных.
  3. Убедитесь, что у выбранного инструмента корректно задан PriceStep, чтобы пункты переводились в цену без искажений.
  4. При появлении предупреждения о невозможности определить размер пункта стоит настроить инструмент в поставщике данных или вручную скорректировать параметры стопов.
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 EliotWavesStrategy : 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 EliotWavesStrategy()
	{
		_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;
	}
}