Ichimoku Adx Strategy
Strategy based on Ichimoku Cloud and ADX indicators. Entry criteria: Long: Price > Kumo (cloud) && Tenkan > Kijun && ADX > 25 (uptrend with strong movement) Short: Price < Kumo (cloud) && Tenkan < Kijun && ADX > 25 (downtrend with strong movement) Exit criteria: Long: Price < Kumo (price falls below cloud) Short: Price > Kumo (price rises above cloud)
Testing indicates an average annual return of about 187%. It performs best in the stocks market.
This strategy blends Ichimoku Cloud signals with ADX to filter for powerful trends. Trades occur when price breaks above or below the cloud with ADX confirming.
It favors traders who prefer structured trend setups. ATR-defined stops defend against adverse swings.
Details
- Entry Criteria:
- Long:
Price > Cloud && Tenkan > Kijun && ADX > AdxThreshold - Short:
Price < Cloud && Tenkan < Kijun && ADX > AdxThreshold
- Long:
- Long/Short: Both
- Exit Criteria:
- Price crosses the cloud opposite
- Stops: Uses Ichimoku cloud for trailing stop
- Default Values:
TenkanPeriod= 9KijunPeriod= 26SenkouSpanBPeriod= 52AdxPeriod= 14AdxThreshold= 25mCandleType= TimeSpan.FromMinutes(15).TimeFrame()
- Filters:
- Category: Trend
- Direction: Both
- Indicators: Ichimoku Cloud, ADX
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Strategy based on Ichimoku Cloud and ADX indicators.
///
/// Entry criteria:
/// Long: Price > Kumo (cloud) && Tenkan > Kijun && ADX > 25 (uptrend with strong movement)
/// Short: Price < Kumo (cloud) && Tenkan < Kijun && ADX > 25 (downtrend with strong movement)
///
/// Exit criteria:
/// Long: Price < Kumo (price falls below cloud)
/// Short: Price > Kumo (price rises above cloud)
/// </summary>
public class IchimokuAdxStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
// Previous state tracking
private bool _isPriceAboveCloud;
private bool _isTenkanAboveKijun;
private decimal _lastAdxValue;
/// <summary>
/// Period for Tenkan-sen calculation (conversion line).
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Period for Kijun-sen calculation (base line).
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Period for Senkou Span B calculation (second cloud component).
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Period for ADX calculation.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Threshold for ADX to confirm trend strength.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public IchimokuAdxStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Period for Tenkan-sen (conversion line)", "Ichimoku")
.SetOptimize(7, 13, 2);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Period for Kijun-sen (base line)", "Ichimoku")
.SetOptimize(20, 32, 3);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (second cloud component)", "Ichimoku")
.SetOptimize(40, 60, 5);
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
.SetOptimize(10, 20, 5);
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "Minimum ADX value to confirm trend strength", "Indicators")
.SetOptimize(20m, 30m, 5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_isPriceAboveCloud = default;
_isTenkanAboveKijun = default;
_lastAdxValue = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
// Create subscription
var subscription = SubscribeCandles(CandleType);
// We'll need to manually bind Ichimoku and ADX separately as they have different output values
subscription
.BindEx(ichimoku, adx, ProcessIndicators)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ichimoku);
// Create separate area for ADX
var adxArea = CreateChartArea();
if (adxArea != null)
{
DrawIndicator(adxArea, adx);
}
DrawOwnTrades(area);
}
}
// Process Ichimoku indicator data
private void ProcessIndicators(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue adxValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
var typedAdx = (AverageDirectionalIndexValue)adxValue;
if (typedAdx.MovingAverage is not decimal adx)
return;
_lastAdxValue = adx;
// Get Ichimoku values
// The component values must be extracted based on the Ichimoku implementation
var ichimokuTyped = (IchimokuValue)ichimokuValue;
if (ichimokuTyped.Tenkan is not decimal tenkan)
return;
if (ichimokuTyped.Kijun is not decimal kijun)
return;
if (ichimokuTyped.SenkouA is not decimal senkouA)
return;
if (ichimokuTyped.SenkouB is not decimal senkouB)
return;
// Determine cloud boundaries
var cloudTop = Math.Max(senkouA, senkouB);
var cloudBottom = Math.Min(senkouA, senkouB);
// Update state
var isPriceAboveCloud = candle.ClosePrice > cloudTop;
var isPriceBelowCloud = candle.ClosePrice < cloudBottom;
var isTenkanAboveKijun = tenkan > kijun;
// Log current state
LogInfo($"Close: {candle.ClosePrice}, Tenkan: {tenkan:N2}, Kijun: {kijun:N2}, " +
$"Cloud Top: {cloudTop:N2}, Cloud Bottom: {cloudBottom:N2}, ADX: {_lastAdxValue:N2}");
var isPriceRelativeToCloudChanged = _isPriceAboveCloud != isPriceAboveCloud;
// Only make trading decisions if both Ichimoku and ADX have been calculated
if (_lastAdxValue <= 0)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
var isStrongTrend = _lastAdxValue > AdxThreshold;
// Trading logic
if (Position == 0) // No position
{
if (isPriceAboveCloud && isTenkanAboveKijun && isStrongTrend)
{
// Buy signal: price above cloud, Tenkan above Kijun, strong trend
BuyMarket(Volume);
LogInfo($"Buy signal: Price above cloud, Tenkan above Kijun, ADX = {_lastAdxValue}");
}
else if (isPriceBelowCloud && !isTenkanAboveKijun && isStrongTrend)
{
// Sell signal: price below cloud, Tenkan below Kijun, strong trend
SellMarket(Volume);
LogInfo($"Sell signal: Price below cloud, Tenkan below Kijun, ADX = {_lastAdxValue}");
}
}
else if (isPriceRelativeToCloudChanged) // Exit on cloud crossing
{
if (Position > 0 && !isPriceAboveCloud)
{
// Exit long position: price fell below cloud
SellMarket(Math.Abs(Position));
LogInfo($"Exit long position: Price fell into/below cloud");
}
else if (Position < 0 && !isPriceBelowCloud)
{
// Exit short position: price rose above cloud
BuyMarket(Math.Abs(Position));
LogInfo($"Exit short position: Price rose into/above cloud");
}
}
// Update tracking variables
_isPriceAboveCloud = isPriceAboveCloud;
_isTenkanAboveKijun = isTenkanAboveKijun;
}
}
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
from StockSharp.Algo.Indicators import Ichimoku, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class ichimoku_adx_strategy(Strategy):
"""
Strategy based on Ichimoku Cloud and ADX indicators.
Entry criteria:
Long: Price > Kumo (cloud) && Tenkan > Kijun && ADX > 25 (uptrend with strong movement)
Short: Price < Kumo (cloud) && Tenkan < Kijun && ADX > 25 (downtrend with strong movement)
Exit criteria:
Long: Price < Kumo (price falls below cloud)
Short: Price > Kumo (price rises above cloud)
"""
def __init__(self):
super(ichimoku_adx_strategy, self).__init__()
# Period for Tenkan-sen calculation (conversion line).
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan Period", "Period for Tenkan-sen (conversion line)", "Ichimoku") \
.SetCanOptimize(True) \
.SetOptimize(7, 13, 2)
# Period for Kijun-sen calculation (base line).
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun Period", "Period for Kijun-sen (base line)", "Ichimoku") \
.SetCanOptimize(True) \
.SetOptimize(20, 32, 3)
# Period for Senkou Span B calculation (second cloud component).
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (second cloud component)", "Ichimoku") \
.SetCanOptimize(True) \
.SetOptimize(40, 60, 5)
# Period for ADX calculation.
self._adx_period = self.Param("AdxPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(10, 20, 5)
# Threshold for ADX to confirm trend strength.
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetGreaterThanZero() \
.SetDisplay("ADX Threshold", "Minimum ADX value to confirm trend strength", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(20.0, 30.0, 5.0)
# Type of candles to use.
self._candle_type = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
# Previous state tracking
self._is_price_above_cloud = False
self._is_tenkan_above_kijun = False
self._last_adx_value = 0.0
@property
def TenkanPeriod(self):
"""Period for Tenkan-sen calculation (conversion line)."""
return self._tenkan_period.Value
@TenkanPeriod.setter
def TenkanPeriod(self, value):
self._tenkan_period.Value = value
@property
def KijunPeriod(self):
"""Period for Kijun-sen calculation (base line)."""
return self._kijun_period.Value
@KijunPeriod.setter
def KijunPeriod(self, value):
self._kijun_period.Value = value
@property
def SenkouSpanBPeriod(self):
"""Period for Senkou Span B calculation (second cloud component)."""
return self._senkou_span_b_period.Value
@SenkouSpanBPeriod.setter
def SenkouSpanBPeriod(self, value):
self._senkou_span_b_period.Value = value
@property
def AdxPeriod(self):
"""Period for ADX calculation."""
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def AdxThreshold(self):
"""Threshold for ADX to confirm trend strength."""
return self._adx_threshold.Value
@AdxThreshold.setter
def AdxThreshold(self, value):
self._adx_threshold.Value = value
@property
def CandleType(self):
"""Type of candles to use."""
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(ichimoku_adx_strategy, self).OnReseted()
self._is_price_above_cloud = False
self._is_tenkan_above_kijun = False
self._last_adx_value = 0.0
def OnStarted2(self, time):
super(ichimoku_adx_strategy, self).OnStarted2(time)
# Create indicators
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self.TenkanPeriod
ichimoku.Kijun.Length = self.KijunPeriod
ichimoku.SenkouB.Length = self.SenkouSpanBPeriod
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
# Create subscription
subscription = self.SubscribeCandles(self.CandleType)
# We'll need to manually bind Ichimoku and ADX separately as they have different output values
subscription.BindEx(ichimoku, adx, self.ProcessIndicators).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
# Create separate area for ADX
adx_area = self.CreateChartArea()
if adx_area is not None:
self.DrawIndicator(adx_area, adx)
self.DrawOwnTrades(area)
# Process Ichimoku indicator data
def ProcessIndicators(self, candle, ichimoku_value, adx_value):
# Skip unfinished candles
if candle.State != CandleStates.Finished:
return
if adx_value.MovingAverage is None:
return
adx = float(adx_value.MovingAverage)
self._last_adx_value = adx
# Get Ichimoku values
if ichimoku_value.Tenkan is None:
return
tenkan = float(ichimoku_value.Tenkan)
if ichimoku_value.Kijun is None:
return
kijun = float(ichimoku_value.Kijun)
if ichimoku_value.SenkouA is None:
return
senkou_a = float(ichimoku_value.SenkouA)
if ichimoku_value.SenkouB is None:
return
senkou_b = float(ichimoku_value.SenkouB)
# Determine cloud boundaries
cloud_top = Math.Max(senkou_a, senkou_b)
cloud_bottom = Math.Min(senkou_a, senkou_b)
# Update state
is_price_above_cloud = candle.ClosePrice > cloud_top
is_price_below_cloud = candle.ClosePrice < cloud_bottom
is_tenkan_above_kijun = tenkan > kijun
# Log current state
self.LogInfo(
"Close: {0}, Tenkan: {1:.2f}, Kijun: {2:.2f}, Cloud Top: {3:.2f}, Cloud Bottom: {4:.2f}, ADX: {5:.2f}".format(
candle.ClosePrice, tenkan, kijun, cloud_top, cloud_bottom, self._last_adx_value))
is_price_relative_to_cloud_changed = self._is_price_above_cloud != is_price_above_cloud
# Only make trading decisions if both Ichimoku and ADX have been calculated
if self._last_adx_value <= 0:
return
# Check if strategy is ready to trade
is_strong_trend = self._last_adx_value > self.AdxThreshold
# Trading logic
if self.Position == 0: # No position
if is_price_above_cloud and is_tenkan_above_kijun and is_strong_trend:
# Buy signal: price above cloud, Tenkan above Kijun, strong trend
self.BuyMarket(self.Volume)
self.LogInfo("Buy signal: Price above cloud, Tenkan above Kijun, ADX = {0}".format(self._last_adx_value))
elif is_price_below_cloud and not is_tenkan_above_kijun and is_strong_trend:
# Sell signal: price below cloud, Tenkan below Kijun, strong trend
self.SellMarket(self.Volume)
self.LogInfo("Sell signal: Price below cloud, Tenkan below Kijun, ADX = {0}".format(self._last_adx_value))
elif is_price_relative_to_cloud_changed: # Exit on cloud crossing
if self.Position > 0 and not is_price_above_cloud:
# Exit long position: price fell below cloud
self.SellMarket(Math.Abs(self.Position))
self.LogInfo("Exit long position: Price fell into/below cloud")
elif self.Position < 0 and not is_price_below_cloud:
# Exit short position: price rose above cloud
self.BuyMarket(Math.Abs(self.Position))
self.LogInfo("Exit short position: Price rose into/above cloud")
# Update tracking variables
self._is_price_above_cloud = is_price_above_cloud
self._is_tenkan_above_kijun = is_tenkan_above_kijun
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return ichimoku_adx_strategy()