Стратегия Elli переносит советник MetaTrader 4 «Elli» на высокоуровневый API StockSharp. В оригинале робот сочетал структуру Ichimoku Kinko Hyo на часовом таймфрейме и фильтр ADX на младшем периоде, дополняя их жёстким управлением рисками. При конверсии сохранены те же правила направленного отбора, ручные модификации ордеров заменены на StartProtection, а все настройки вынесены в StrategyParam<T>, чтобы параметры можно было оптимизировать и менять на лету.
Логика торговли
Трендовая структура Ichimoku
Подписка на свечи таймфрейма CandleType (по умолчанию H1) и расчёт линий Tenkan, Kijun и Senkou по исходным периодам 19/60/120.
Сигнал на покупку требует соблюдения цепочки Tenkan > Kijun > Senkou Span A > Senkou Span B и закрытия свечи выше Kijun. Для продаж условия зеркальные.
Абсолютное расстояние между Tenkan и Kijun должно быть больше TenkanKijunGapPips пунктов, что отсекает плоские облака.
Подтверждение Directional Movement
Вторая подписка запускает индикатор Average Directional Index на таймфрейме AdxCandleType (по умолчанию M1).
Лонг разрешён только если предыдущий +DI меньше ConvertLow, а текущий +DI превышает ConvertHigh. Для шорта аналогично проверяется −DI. Тем самым воспроизводится ускорение, проверяемое через iADX в МQL.
Исполнение входов
При выполнении условий отправляется рыночная заявка объёмом OrderVolume + |Position|, что сначала закрывает обратную позицию и затем открывает новую.
В любой момент времени допускается только одно направленное удержание, как и в оригинале с проверкой OrdersTotal() < 1.
Управление риском
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;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class elli_strategy(Strategy):
def __init__(self):
super(elli_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_length = self.Param("FastLength", 19) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_length = self.Param("SlowLength", 60) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for momentum", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_atr = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastLength(self):
return self._fast_length.Value
@property
def SlowLength(self):
return self._slow_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(elli_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_atr = 0.0
self._entry_price = 0.0
self._fast = ExponentialMovingAverage()
self._fast.Length = self.FastLength
self._slow = ExponentialMovingAverage()
self._slow.Length = self.SlowLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast, self._slow, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
self._prev_atr = av
return
close = float(candle.ClosePrice)
# Exit: stop or take based on ATR
if self.Position > 0:
if close <= self._entry_price - av * 2.0 or close >= self._entry_price + av * 3.0:
self.SellMarket()
self._entry_price = 0.0
elif fv < sv:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close >= self._entry_price + av * 2.0 or close <= self._entry_price - av * 3.0:
self.BuyMarket()
self._entry_price = 0.0
elif fv > sv:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_fast = fv
self._prev_slow = sv
self._prev_atr = av
return
# Entry: EMA crossover with ATR expansion
if self.Position == 0:
atr_rising = av > self._prev_atr
if self._prev_fast <= self._prev_slow and fv > sv and atr_rising:
self._entry_price = close
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fv < sv and atr_rising:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
self._prev_atr = av
def OnReseted(self):
super(elli_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._prev_atr = 0.0
self._entry_price = 0.0
def CreateClone(self):
return elli_strategy()