Ichimoku Hurst Exponent
The Ichimoku Hurst Exponent strategy is built around Ichimoku Kinko Hyo indicator with Hurst exponent filter.
Testing indicates an average annual return of about 64%. It performs best in the forex market.
Signals trigger when Hurst confirms trend changes on intraday (15m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like TenkanPeriod, KijunPeriod. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
TenkanPeriod = 9KijunPeriod = 26SenkouSpanBPeriod = 52HurstPeriod = 100HurstThreshold = 0.5mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Hurst, Exponent
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (15m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
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 Kinko Hyo indicator with Hurst exponent filter.
/// </summary>
public class IchimokuHurstStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<decimal> _hurstThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private Ichimoku _ichimoku;
// Data for Hurst exponent calculations
private readonly List<decimal> _prices = [];
private decimal _hurstExponent;
private decimal? _prevTenkan;
private decimal? _prevKijun;
private int _cooldownRemaining;
/// <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 (leading span B) period.
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Hurst exponent calculation period.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Hurst exponent threshold for trend strength.
/// </summary>
public decimal HurstThreshold
{
get => _hurstThreshold.Value;
set => _hurstThreshold.Value = value;
}
/// <summary>
/// Candle type to use for the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bars to wait between position changes.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="IchimokuHurstStrategy"/>.
/// </summary>
public IchimokuHurstStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetDisplay("Tenkan Period", "Tenkan-sen (conversion line) period", "Ichimoku")
.SetOptimize(5, 15, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetDisplay("Kijun Period", "Kijun-sen (base line) period", "Ichimoku")
.SetOptimize(20, 40, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetDisplay("Senkou Span B Period", "Senkou Span B (leading span B) period", "Ichimoku")
.SetOptimize(40, 70, 5);
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetDisplay("Hurst Period", "Hurst exponent calculation period", "Hurst Exponent")
.SetOptimize(50, 200, 10);
_hurstThreshold = Param(nameof(HurstThreshold), 0.5m)
.SetDisplay("Hurst Threshold", "Hurst exponent threshold for trend strength", "Hurst Exponent")
.SetOptimize(0.45m, 0.6m, 0.05m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_hurstExponent = 0.5m; // Default Hurst exponent value
_prevTenkan = null;
_prevKijun = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create Ichimoku indicator
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
// Create subscription and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_ichimoku, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ichimoku);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Store Ichimoku values
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;
// Update price data for Hurst exponent calculation
_prices.Add(candle.ClosePrice);
// Keep only the number of prices needed for Hurst calculation
while (_prices.Count > HurstPeriod)
_prices.RemoveAt(0);
// Calculate Hurst exponent when we have enough data
if (_prices.Count >= HurstPeriod)
CalculateHurstExponent();
// Continue with position checks
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
// Check if price is above/below Kumo (cloud)
var isPriceAboveKumo = candle.ClosePrice > Math.Max(senkouA, senkouB);
var isPriceBelowKumo = candle.ClosePrice < Math.Min(senkouA, senkouB);
if (_prevTenkan is decimal previousTenkan && _prevKijun is decimal previousKijun)
{
var crossUp = previousTenkan <= previousKijun && tenkan > kijun;
var crossDown = previousTenkan >= previousKijun && tenkan < kijun;
var longExit = Position > 0 && (isPriceBelowKumo || crossDown);
var shortExit = Position < 0 && (isPriceAboveKumo || crossUp);
if (longExit)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (shortExit)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && isPriceAboveKumo && crossUp && _hurstExponent > HurstThreshold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && isPriceBelowKumo && crossDown && _hurstExponent > HurstThreshold && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
}
_prevTenkan = tenkan;
_prevKijun = kijun;
}
private void CalculateHurstExponent()
{
// This is a simplified Hurst exponent calculation using R/S analysis
// Note: A full implementation would use multiple time scales
// Calculate log returns
List<decimal> logReturns = [];
for (int i = 1; i < _prices.Count; i++)
{
if (_prices[i-1] != 0)
logReturns.Add((decimal)Math.Log((double)(_prices[i] / _prices[i-1])));
}
if (logReturns.Count < 10)
return;
// Calculate mean
decimal mean = logReturns.Sum() / logReturns.Count;
// Calculate cumulative deviation series
List<decimal> cumulativeDeviation = [];
decimal sum = 0;
foreach (var logReturn in logReturns)
{
sum += (logReturn - mean);
cumulativeDeviation.Add(sum);
}
// Calculate range (max - min of cumulative deviation)
decimal range = cumulativeDeviation.Max() - cumulativeDeviation.Min();
// Calculate standard deviation
decimal sumSquares = logReturns.Sum(x => (x - mean) * (x - mean));
decimal stdDev = (decimal)Math.Sqrt((double)(sumSquares / logReturns.Count));
if (stdDev == 0)
return;
// Calculate R/S statistic
decimal rs = range / stdDev;
// Hurst = log(R/S) / log(N)
decimal logN = (decimal)Math.Log((double)logReturns.Count);
if (logN != 0)
_hurstExponent = (decimal)Math.Log((double)rs) / logN;
}
}
import clr
import math
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
from StockSharp.Algo.Strategies import Strategy
class ichimoku_hurst_exponent_strategy(Strategy):
"""
Strategy based on Ichimoku Kinko Hyo indicator with Hurst exponent filter.
"""
def __init__(self):
super(ichimoku_hurst_exponent_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetDisplay("Tenkan Period", "Tenkan-sen (conversion line) period", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetDisplay("Kijun Period", "Kijun-sen (base line) period", "Ichimoku")
self._senkou_spanb_period = self.Param("SenkouSpanBPeriod", 52) \
.SetDisplay("Senkou Span B Period", "Senkou Span B (leading span B) period", "Ichimoku")
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetDisplay("Hurst Period", "Hurst exponent calculation period", "Hurst Exponent")
self._hurst_threshold = self.Param("HurstThreshold", 0.5) \
.SetDisplay("Hurst Threshold", "Hurst exponent threshold for trend strength", "Hurst Exponent")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 6) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading")
self._prices = []
self._hurst_exponent = 0.5
self._prev_tenkan = None
self._prev_kijun = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ichimoku_hurst_exponent_strategy, self).OnReseted()
self._prices = []
self._hurst_exponent = 0.5
self._prev_tenkan = None
self._prev_kijun = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ichimoku_hurst_exponent_strategy, self).OnStarted2(time)
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = int(self._tenkan_period.Value)
ichimoku.Kijun.Length = int(self._kijun_period.Value)
ichimoku.SenkouB.Length = int(self._senkou_spanb_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ichimoku, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
tenkan_val = ichimoku_value.Tenkan
kijun_val = ichimoku_value.Kijun
senkou_a_val = ichimoku_value.SenkouA
senkou_b_val = ichimoku_value.SenkouB
if tenkan_val is None or kijun_val is None or senkou_a_val is None or senkou_b_val is None:
return
tenkan = float(tenkan_val)
kijun = float(kijun_val)
senkou_a = float(senkou_a_val)
senkou_b = float(senkou_b_val)
hurst_period = int(self._hurst_period.Value)
self._prices.append(float(candle.ClosePrice))
while len(self._prices) > hurst_period:
self._prices.pop(0)
if len(self._prices) >= hurst_period:
self._calculate_hurst_exponent()
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
close_price = float(candle.ClosePrice)
is_price_above_kumo = close_price > max(senkou_a, senkou_b)
is_price_below_kumo = close_price < min(senkou_a, senkou_b)
cd = int(self._signal_cooldown_bars.Value)
ht = float(self._hurst_threshold.Value)
if self._prev_tenkan is not None and self._prev_kijun is not None:
cross_up = self._prev_tenkan <= self._prev_kijun and tenkan > kijun
cross_down = self._prev_tenkan >= self._prev_kijun and tenkan < kijun
long_exit = self.Position > 0 and (is_price_below_kumo or cross_down)
short_exit = self.Position < 0 and (is_price_above_kumo or cross_up)
if long_exit:
self.SellMarket(self.Position)
self._cooldown_remaining = cd
elif short_exit:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and is_price_above_kumo and cross_up and self._hurst_exponent > ht and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and is_price_below_kumo and cross_down and self._hurst_exponent > ht and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
self._cooldown_remaining = cd
self._prev_tenkan = tenkan
self._prev_kijun = kijun
def _calculate_hurst_exponent(self):
log_returns = []
for i in range(1, len(self._prices)):
if self._prices[i - 1] != 0:
log_returns.append(math.log(self._prices[i] / self._prices[i - 1]))
if len(log_returns) < 10:
return
mean = sum(log_returns) / len(log_returns)
cumulative_deviation = []
sum_val = 0.0
for lr in log_returns:
sum_val += (lr - mean)
cumulative_deviation.append(sum_val)
range_val = max(cumulative_deviation) - min(cumulative_deviation)
sum_squares = sum((x - mean) ** 2 for x in log_returns)
std_dev = math.sqrt(sum_squares / len(log_returns))
if std_dev == 0:
return
rs = range_val / std_dev
log_n = math.log(len(log_returns))
if log_n != 0:
self._hurst_exponent = math.log(rs) / log_n
def CreateClone(self):
return ichimoku_hurst_exponent_strategy()