The Elli Strategy ports the MetaTrader 4 expert advisor "Elli" to the StockSharp high level API. The original robot combined the Ichimoku Kinko Hyo structure on the H1 timeframe with a lower timeframe ADX filter and strict risk parameters. The conversion keeps the same directional logic, replaces manual order management with StartProtection, and exposes every tuning knob as an optimisable StrategyParam<T> so that the behaviour can be adapted to different markets.
Trading Logic
Ichimoku trend structure
The strategy subscribes to the timeframe defined by CandleType (H1 by default) and computes Tenkan-sen, Kijun-sen and Senkou spans using the original periods (19, 60, 120).
A bullish setup requires Tenkan > Kijun > Senkou Span A > Senkou Span B with the candle close above Kijun. Bearish setups mirror this condition.
The absolute distance between Tenkan and Kijun must exceed TenkanKijunGapPips pips to avoid flat or ranging clouds.
Directional Movement confirmation
A second candle subscription runs the Average Directional Index on the timeframe specified by AdxCandleType (M1 by default).
Long signals are allowed only when the previous +DI value is below ConvertLow and the current +DI pushes above ConvertHigh. Shorts require the same relationship for the −DI component, replicating the acceleration filter present in the MT4 code.
Entry execution
When all filters align, the strategy issues a market order with volume OrderVolume + |Position|. This automatically closes any opposite exposure before joining the trend.
Only one directional exposure is kept at a time, following the original OrdersTotal() < 1 guard.
Risk management
StartProtection attaches symmetric stop loss and take profit orders converted from pip distances using the instrument’s pip size.
The position is otherwise managed passively, letting the protection orders handle exits just like the MT4 expert advisor.
Indicators and Data Subscriptions
Primary candles: CandleType (default 1-hour candles) for Ichimoku processing.
ADX candles: AdxCandleType (default 1-minute candles) for DI acceleration checks.
Indicators: Ichimoku (Tenkan, Kijun, Senkou Span B) and AverageDirectionalIndex (providing +DI/−DI).
Both subscriptions support chart rendering through DrawCandles, DrawIndicator, and DrawOwnTrades if a chart area is available.
Parameters
Name
Default
Description
OrderVolume
1
Base market order volume.
TakeProfitPips
60
Take-profit distance expressed in pips.
StopLossPips
30
Stop-loss distance expressed in pips.
TenkanPeriod
19
Tenkan-sen period for the Ichimoku indicator.
KijunPeriod
60
Kijun-sen period for the Ichimoku indicator.
SenkouSpanBPeriod
120
Senkou Span B period for the Ichimoku cloud.
TenkanKijunGapPips
20
Minimum Tenkan/Kijun distance (in pips) required before trading.
ConvertHigh
13
DI threshold the current value must exceed to confirm momentum.
ConvertLow
6
DI threshold the previous value must stay below before a new trade.
AdxPeriod
10
Period used for the ADX computation.
CandleType
H1
Timeframe that drives the Ichimoku calculation.
AdxCandleType
M1
Timeframe used for ADX and DI monitoring.
All parameters are implemented with StrategyParam<T> helpers, enabling optimisation and runtime adjustments inside StockSharp Designer.
Implementation Notes
The pip conversion follows the standard forex convention (0.0001 for 5-digit quotes and 0.01 for 3-digit instruments) to preserve the original pip-based thresholds.
ADX values are cached in _latestPlusDi, _previousPlusDi, _latestMinusDi, and _previousMinusDi, ensuring the DI acceleration check matches the MQL iADX calls with shifts 0 and 1.
IsFormedAndOnlineAndAllowTrading() blocks signals until the strategy, indicators, and data feeds are ready, preventing premature trades during warm-up.
Market entries rely on Volume + Math.Abs(Position) so that direction changes instantly flatten existing trades, emulating the single-position behaviour of the MT4 script.
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()