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.
Торговые правила
Фильтр тренда и структуры
Для покупок быстрая LWMA должна быть выше медленной.
Для продаж — ниже медленной.
Требуется перекрытие двух последних закрытых свечей: Low[2] < High[1] для лонга и
Low[1] < High[2] для шорта. Это условие повторяет консервацию из оригинального советника.
Подтверждение моментума
Отклонение моментума вычисляется как abs(momentum - 100).
Если любое из трёх последних значений превышает порог, импульс считается достаточным.
Фильтр глобального тренда
Для входа в лонг линия MACD должна быть выше сигнальной.
Для шорта — ниже сигнальной.
Исполнение
При выполнении всех условий отправляется рыночный ордер, который сначала переворачивает
текущую позицию и добавляет указанный объём.
Переворот позиции разрешён, что соответствует поведению оригинального эксперта.
Управление рисками
Метод StartProtection автоматически задаёт стоп-лосс и тейк-профит в пунктах.
Дополнительная логика закрывает лонг, если быстрая LWMA пересекает медленную сверху вниз
или MACD становится медвежьим, и аналогично для шорта.
Параметр Max Position ограничивает суммарную позицию, что повторяет работу Max_Trades в MQL4.
Отличия от оригинала
Проверки пользовательских трендовых линий и отправка уведомлений удалены — в StockSharp нет
прямых аналогов этих функций.
Многоступенчатые трейлинг-стопы и переход в безубыток заменены на базовый StartProtection.
При необходимости их можно добавить вручную.
Защита по капиталу в деньгах не реализована; управление риском выполняется через стопы и лимит
позиции.
Рекомендации по использованию
Убедитесь, что доступны все три потока свечей (базовый, моментумный и для MACD).
Настройте объём, стопы и пороги в соответствии с волатильностью инструмента.
При необходимости оптимизируйте отдельные пороги для покупок и продаж.
Используйте встроенную визуализацию (свечи, 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class eliot_wave_strategy(Strategy):
def __init__(self):
super(eliot_wave_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(eliot_wave_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(eliot_wave_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return eliot_wave_strategy()