Elli Ichimoku ADX Strategy
Overview
The strategy is a C# port of the MetaTrader 5 expert "Elli" (barabashkakvn's edition). It combines Ichimoku Kinko Hyo structure with an Average Directional Index (+DI) breakout filter. Trades are opened only when a strong directional impulse is confirmed simultaneously by Ichimoku line alignment and a sudden surge in the positive directional index.
The StockSharp implementation keeps the original behaviour of working with two candle streams: Ichimoku analysis is performed on a higher timeframe (default 1 hour) while ADX is evaluated on a faster series (default 1 minute). Orders are entered with a fixed protective stop and target measured in price steps, identical to the original expert advisor.
Indicators and data
- Ichimoku (Tenkan 19, Kijun 60, Senkou Span B 120 by default).
- Average Directional Index (ADX), only the +DI line is used as in the source code.
- Optional chart areas display the candle series, Ichimoku cloud and the ADX line.
Two independent candle subscriptions are created:
IchimokuCandleType (default 1 hour) – drives Ichimoku calculations and generates trading decisions.
AdxCandleType (default 1 minute) – feeds the ADX indicator and supplies current/previous +DI values.
Parameters
| Parameter |
Default |
Description |
TakeProfitPoints |
60 |
Take profit distance in price steps. Set to 0 to disable. |
StopLossPoints |
30 |
Stop loss distance in price steps. Set to 0 to disable. |
TenkanPeriod |
19 |
Length of the Ichimoku Tenkan-sen (conversion line). |
KijunPeriod |
60 |
Length of the Ichimoku Kijun-sen (base line). |
SenkouSpanBPeriod |
120 |
Length of the Ichimoku Senkou Span B line. |
AdxPeriod |
10 |
Period for the ADX indicator. |
PlusDiHighThreshold |
13 |
Threshold that the current +DI value must exceed. |
PlusDiLowThreshold |
6 |
Threshold that the previous +DI value must stay below. |
BaselineDistanceThreshold |
20 |
Minimum Tenkan/Kijun spread (in price steps) required to confirm momentum. |
IchimokuCandleType |
1 hour candles |
Candle series used for Ichimoku evaluation. |
AdxCandleType |
1 minute candles |
Candle series used for ADX calculation. |
Trading logic
- Wait for one finished Ichimoku candle.
- Ensure ADX has at least two finished values and the last reading produced a +DI breakout (
previous +DI < PlusDiLowThreshold and current +DI > PlusDiHighThreshold).
- Convert the Tenkan/Kijun spread into price steps and verify it exceeds
BaselineDistanceThreshold.
- All orders are blocked if an open position already exists.
- Buy when:
- Tenkan > Kijun.
- Kijun > Senkou Span A.
- Senkou Span A > Senkou Span B (bullish cloud).
- Closing price > Kijun.
- Sell when the reverse alignment is observed (Tenkan < Kijun < Senkou Span A < Senkou Span B and the close is below Kijun).
- Position exits rely on the protective stop and target configured via
StartProtection. No discretionary exit is triggered; this mirrors the original EA that waited for stops/targets or manual intervention.
Risk management
StartProtection is called once on start. If either stop or target is zero the respective protection is omitted. Orders are sent with market execution (BuyMarket/SellMarket), matching the MQL implementation that used market orders with attached SL/TP.
Implementation notes
- Only the positive directional index is used for both long and short signals, replicating the logic of the MQL5 code (the original author commented out the -DI branch).
- The strategy does not track the Chikou span explicitly; instead, cloud alignment is validated by comparing Senkou Span A and B.
- Internal fields store the last two +DI values without calling
GetValue, in accordance with the high-level API guidelines.
- If both candle parameters are identical, a single subscription is reused for Ichimoku and ADX to reduce overhead.
Usage tips
- Keep
AdxCandleType faster than IchimokuCandleType to emulate the MT5 version (e.g., M1 ADX vs. H1 Ichimoku).
- Raise
BaselineDistanceThreshold on high-volatility instruments to demand wider Tenkan/Kijun separation.
- Because the expert opens only one position at a time, combine the strategy with portfolio-level risk controls when trading multiple symbols.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the MQL5 strategy "Elli" combining Ichimoku and ADX filters.
/// Focuses on impulsive moves confirmed by +DI acceleration and Ichimoku line alignment.
/// </summary>
public class ElliIchimokuAdxStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _plusDiHighThreshold;
private readonly StrategyParam<decimal> _plusDiLowThreshold;
private readonly StrategyParam<decimal> _baselineDistanceThreshold;
private readonly StrategyParam<DataType> _ichimokuCandleType;
private readonly StrategyParam<DataType> _adxCandleType;
private Ichimoku _ichimoku;
private decimal? _previousPlusDi;
private decimal? _currentPlusDi;
private bool _isAdxReady;
private decimal? _previousAdxHigh;
private decimal? _previousAdxLow;
private decimal? _previousAdxClose;
private decimal _smoothedTrueRange;
private decimal _smoothedPlusDm;
private int _adxSamples;
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Tenkan-sen (conversion line) period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen (base line) period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B period.
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// ADX calculation period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Upper threshold for +DI breakout confirmation.
/// </summary>
public decimal PlusDiHighThreshold
{
get => _plusDiHighThreshold.Value;
set => _plusDiHighThreshold.Value = value;
}
/// <summary>
/// Lower threshold that previous +DI must stay below before breakout.
/// </summary>
public decimal PlusDiLowThreshold
{
get => _plusDiLowThreshold.Value;
set => _plusDiLowThreshold.Value = value;
}
/// <summary>
/// Required Tenkan/Kijun separation measured in price steps.
/// </summary>
public decimal BaselineDistanceThreshold
{
get => _baselineDistanceThreshold.Value;
set => _baselineDistanceThreshold.Value = value;
}
/// <summary>
/// Candle type used for Ichimoku evaluation and trading decisions.
/// </summary>
public DataType IchimokuCandleType
{
get => _ichimokuCandleType.Value;
set => _ichimokuCandleType.Value = value;
}
/// <summary>
/// Candle type used for ADX calculation.
/// </summary>
public DataType AdxCandleType
{
get => _adxCandleType.Value;
set => _adxCandleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ElliIchimokuAdxStrategy"/>.
/// </summary>
public ElliIchimokuAdxStrategy()
{
_takeProfitPoints = Param(nameof(TakeProfitPoints), 60m)
.SetDisplay("Take Profit", "Take profit distance in price steps", "Risk Management")
.SetNotNegative();
_stopLossPoints = Param(nameof(StopLossPoints), 30m)
.SetDisplay("Stop Loss", "Stop loss distance in price steps", "Risk Management")
.SetNotNegative();
_tenkanPeriod = Param(nameof(TenkanPeriod), 19)
.SetDisplay("Tenkan Period", "Tenkan-sen (conversion line) length", "Ichimoku")
.SetGreaterThanZero();
_kijunPeriod = Param(nameof(KijunPeriod), 60)
.SetDisplay("Kijun Period", "Kijun-sen (base line) length", "Ichimoku")
.SetGreaterThanZero();
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 120)
.SetDisplay("Senkou Span B Period", "Senkou Span B length", "Ichimoku")
.SetGreaterThanZero();
_adxPeriod = Param(nameof(AdxPeriod), 10)
.SetDisplay("ADX Period", "Average Directional Index period", "ADX")
.SetGreaterThanZero();
_plusDiHighThreshold = Param(nameof(PlusDiHighThreshold), 10m)
.SetDisplay("+DI High Threshold", "Level current +DI must exceed", "ADX")
.SetGreaterThanZero();
_plusDiLowThreshold = Param(nameof(PlusDiLowThreshold), 8m)
.SetDisplay("+DI Low Threshold", "Level previous +DI must stay below", "ADX")
.SetNotNegative();
_baselineDistanceThreshold = Param(nameof(BaselineDistanceThreshold), 5m)
.SetDisplay("Baseline Distance", "Minimum Tenkan/Kijun spread in steps", "Ichimoku")
.SetNotNegative();
_ichimokuCandleType = Param(nameof(IchimokuCandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Ichimoku Candle", "Candle series for Ichimoku", "General");
_adxCandleType = Param(nameof(AdxCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("ADX Candle", "Candle series for ADX", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, IchimokuCandleType);
if (AdxCandleType != IchimokuCandleType)
yield return (Security, AdxCandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousPlusDi = null;
_currentPlusDi = null;
_isAdxReady = false;
_previousAdxHigh = null;
_previousAdxLow = null;
_previousAdxClose = null;
_smoothedTrueRange = 0m;
_smoothedPlusDm = 0m;
_adxSamples = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
var ichimokuSubscription = SubscribeCandles(IchimokuCandleType);
ichimokuSubscription.BindEx(_ichimoku, ProcessIchimoku);
if (AdxCandleType == IchimokuCandleType)
{
ichimokuSubscription.Bind(ProcessAdxCandle);
ichimokuSubscription.Start();
}
else
{
ichimokuSubscription.Start();
var adxSubscription = SubscribeCandles(AdxCandleType);
adxSubscription.Bind(ProcessAdxCandle).Start();
}
if (TakeProfitPoints > 0m || StopLossPoints > 0m)
{
StartProtection(
StopLossPoints > 0m ? new Unit(StopLossPoints, UnitTypes.Absolute) : null,
TakeProfitPoints > 0m ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null);
}
var priceArea = CreateChartArea();
if (priceArea != null)
{
DrawCandles(priceArea, ichimokuSubscription);
DrawIndicator(priceArea, _ichimoku);
DrawOwnTrades(priceArea);
}
}
private void ProcessAdxCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_previousAdxHigh is not decimal previousHigh ||
_previousAdxLow is not decimal previousLow ||
_previousAdxClose is not decimal previousClose)
{
_previousAdxHigh = candle.HighPrice;
_previousAdxLow = candle.LowPrice;
_previousAdxClose = candle.ClosePrice;
return;
}
var upMove = candle.HighPrice - previousHigh;
var downMove = previousLow - candle.LowPrice;
var plusDm = upMove > downMove && upMove > 0m ? upMove : 0m;
var trueRange = Math.Max(
candle.HighPrice - candle.LowPrice,
Math.Max(
Math.Abs(candle.HighPrice - previousClose),
Math.Abs(candle.LowPrice - previousClose)));
if (_adxSamples < AdxPeriod)
{
_smoothedPlusDm += plusDm;
_smoothedTrueRange += trueRange;
_adxSamples++;
}
else
{
_smoothedPlusDm = _smoothedPlusDm - (_smoothedPlusDm / AdxPeriod) + plusDm;
_smoothedTrueRange = _smoothedTrueRange - (_smoothedTrueRange / AdxPeriod) + trueRange;
}
if (_adxSamples >= AdxPeriod && _smoothedTrueRange > 0m)
{
_previousPlusDi = _currentPlusDi;
_currentPlusDi = 100m * _smoothedPlusDm / _smoothedTrueRange;
_isAdxReady = _previousPlusDi.HasValue;
}
_previousAdxHigh = candle.HighPrice;
_previousAdxLow = candle.LowPrice;
_previousAdxClose = candle.ClosePrice;
}
private void ProcessIchimoku(ICandleMessage candle, IIndicatorValue ichimokuValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_currentPlusDi is not decimal currentPlus || _previousPlusDi is not decimal previousPlus)
return;
if (!_isAdxReady)
return;
var ich = (IchimokuValue)ichimokuValue;
if (ich.Tenkan is not decimal tenkan ||
ich.Kijun is not decimal kijun ||
ich.SenkouA is not decimal senkouA ||
ich.SenkouB is not decimal senkouB)
{
return;
}
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0m)
priceStep = 1m;
var baselineDistance = Math.Abs(tenkan - kijun) / priceStep;
var hasPlusDiBreakout = currentPlus > PlusDiHighThreshold && previousPlus >= PlusDiLowThreshold && currentPlus >= previousPlus;
if (!hasPlusDiBreakout)
return;
if (baselineDistance < BaselineDistanceThreshold)
return;
if (Position != 0)
return;
var priceAboveCloud = senkouA > senkouB && kijun > senkouA && tenkan > kijun && candle.ClosePrice > kijun;
var priceBelowCloud = senkouA < senkouB && kijun < senkouA && tenkan < kijun && candle.ClosePrice < kijun;
if (priceAboveCloud)
{
this.LogInfo($"Bullish signal: Tenkan {tenkan:F2} > Kijun {kijun:F2}, cloud rising, +DI from {previousPlus:F2} to {currentPlus:F2}.");
BuyMarket();
}
else if (priceBelowCloud)
{
this.LogInfo($"Bearish signal: Tenkan {tenkan:F2} < Kijun {kijun:F2}, cloud falling, +DI from {previousPlus:F2} to {currentPlus:F2}.");
SellMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Ichimoku
from StockSharp.Algo.Strategies import Strategy
class elli_ichimoku_adx_strategy(Strategy):
def __init__(self):
super(elli_ichimoku_adx_strategy, self).__init__()
self._take_profit_points = self.Param("TakeProfitPoints", 60.0)
self._stop_loss_points = self.Param("StopLossPoints", 30.0)
self._tenkan_period = self.Param("TenkanPeriod", 19)
self._kijun_period = self.Param("KijunPeriod", 60)
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 120)
self._adx_period = self.Param("AdxPeriod", 10)
self._plus_di_high_threshold = self.Param("PlusDiHighThreshold", 10.0)
self._plus_di_low_threshold = self.Param("PlusDiLowThreshold", 8.0)
self._baseline_distance_threshold = self.Param("BaselineDistanceThreshold", 5.0)
self._ichimoku_candle_type = self.Param("IchimokuCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._adx_candle_type = self.Param("AdxCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._previous_plus_di = None
self._current_plus_di = None
self._is_adx_ready = False
self._previous_adx_high = None
self._previous_adx_low = None
self._previous_adx_close = None
self._smoothed_true_range = 0.0
self._smoothed_plus_dm = 0.0
self._adx_samples = 0
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TenkanPeriod(self):
return self._tenkan_period.Value
@TenkanPeriod.setter
def TenkanPeriod(self, value):
self._tenkan_period.Value = value
@property
def KijunPeriod(self):
return self._kijun_period.Value
@KijunPeriod.setter
def KijunPeriod(self, value):
self._kijun_period.Value = value
@property
def SenkouSpanBPeriod(self):
return self._senkou_span_b_period.Value
@SenkouSpanBPeriod.setter
def SenkouSpanBPeriod(self, value):
self._senkou_span_b_period.Value = value
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def PlusDiHighThreshold(self):
return self._plus_di_high_threshold.Value
@PlusDiHighThreshold.setter
def PlusDiHighThreshold(self, value):
self._plus_di_high_threshold.Value = value
@property
def PlusDiLowThreshold(self):
return self._plus_di_low_threshold.Value
@PlusDiLowThreshold.setter
def PlusDiLowThreshold(self, value):
self._plus_di_low_threshold.Value = value
@property
def BaselineDistanceThreshold(self):
return self._baseline_distance_threshold.Value
@BaselineDistanceThreshold.setter
def BaselineDistanceThreshold(self, value):
self._baseline_distance_threshold.Value = value
@property
def IchimokuCandleType(self):
return self._ichimoku_candle_type.Value
@IchimokuCandleType.setter
def IchimokuCandleType(self, value):
self._ichimoku_candle_type.Value = value
@property
def AdxCandleType(self):
return self._adx_candle_type.Value
@AdxCandleType.setter
def AdxCandleType(self, value):
self._adx_candle_type.Value = value
def OnStarted2(self, time):
super(elli_ichimoku_adx_strategy, self).OnStarted2(time)
self._ichimoku = Ichimoku()
self._ichimoku.Tenkan.Length = self.TenkanPeriod
self._ichimoku.Kijun.Length = self.KijunPeriod
self._ichimoku.SenkouB.Length = self.SenkouSpanBPeriod
self._previous_plus_di = None
self._current_plus_di = None
self._is_adx_ready = False
self._previous_adx_high = None
self._previous_adx_low = None
self._previous_adx_close = None
self._smoothed_true_range = 0.0
self._smoothed_plus_dm = 0.0
self._adx_samples = 0
ichi_sub = self.SubscribeCandles(self.IchimokuCandleType)
ichi_sub.BindEx(self._ichimoku, self._process_ichimoku)
if str(self.AdxCandleType) == str(self.IchimokuCandleType):
ichi_sub.Bind(self._process_adx_candle)
ichi_sub.Start()
else:
ichi_sub.Start()
adx_sub = self.SubscribeCandles(self.AdxCandleType)
adx_sub.Bind(self._process_adx_candle).Start()
tp = float(self.TakeProfitPoints)
sl = float(self.StopLossPoints)
sl_unit = Unit(sl, UnitTypes.Absolute) if sl > 0.0 else None
tp_unit = Unit(tp, UnitTypes.Absolute) if tp > 0.0 else None
if tp > 0.0 or sl > 0.0:
self.StartProtection(sl_unit, tp_unit)
def _process_adx_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._previous_adx_high is None or self._previous_adx_low is None or self._previous_adx_close is None:
self._previous_adx_high = high
self._previous_adx_low = low
self._previous_adx_close = close
return
up_move = high - self._previous_adx_high
down_move = self._previous_adx_low - low
plus_dm = up_move if (up_move > down_move and up_move > 0.0) else 0.0
true_range = max(high - low, max(abs(high - self._previous_adx_close), abs(low - self._previous_adx_close)))
adx_period = int(self.AdxPeriod)
if self._adx_samples < adx_period:
self._smoothed_plus_dm += plus_dm
self._smoothed_true_range += true_range
self._adx_samples += 1
else:
self._smoothed_plus_dm = self._smoothed_plus_dm - (self._smoothed_plus_dm / adx_period) + plus_dm
self._smoothed_true_range = self._smoothed_true_range - (self._smoothed_true_range / adx_period) + true_range
if self._adx_samples >= adx_period and self._smoothed_true_range > 0.0:
self._previous_plus_di = self._current_plus_di
self._current_plus_di = 100.0 * self._smoothed_plus_dm / self._smoothed_true_range
self._is_adx_ready = self._previous_plus_di is not None
self._previous_adx_high = high
self._previous_adx_low = low
self._previous_adx_close = close
def _process_ichimoku(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
if self._current_plus_di is None or self._previous_plus_di is None:
return
if not self._is_adx_ready:
return
tenkan = ichimoku_value.Tenkan
kijun = ichimoku_value.Kijun
senkou_a = ichimoku_value.SenkouA
senkou_b = ichimoku_value.SenkouB
if tenkan is None or kijun is None or senkou_a is None or senkou_b is None:
return
tenkan_val = float(tenkan)
kijun_val = float(kijun)
senkou_a_val = float(senkou_a)
senkou_b_val = float(senkou_b)
close = float(candle.ClosePrice)
price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if price_step <= 0.0:
price_step = 1.0
baseline_distance = abs(tenkan_val - kijun_val) / price_step
di_high = float(self.PlusDiHighThreshold)
di_low = float(self.PlusDiLowThreshold)
has_plus_di_breakout = (self._current_plus_di > di_high and
self._previous_plus_di >= di_low and
self._current_plus_di >= self._previous_plus_di)
if not has_plus_di_breakout:
return
if baseline_distance < float(self.BaselineDistanceThreshold):
return
if self.Position != 0:
return
price_above_cloud = (senkou_a_val > senkou_b_val and kijun_val > senkou_a_val and
tenkan_val > kijun_val and close > kijun_val)
price_below_cloud = (senkou_a_val < senkou_b_val and kijun_val < senkou_a_val and
tenkan_val < kijun_val and close < kijun_val)
if price_above_cloud:
self.BuyMarket()
elif price_below_cloud:
self.SellMarket()
def OnReseted(self):
super(elli_ichimoku_adx_strategy, self).OnReseted()
self._previous_plus_di = None
self._current_plus_di = None
self._is_adx_ready = False
self._previous_adx_high = None
self._previous_adx_low = None
self._previous_adx_close = None
self._smoothed_true_range = 0.0
self._smoothed_plus_dm = 0.0
self._adx_samples = 0
def CreateClone(self):
return elli_ichimoku_adx_strategy()