Donchian Hurst Exponent
The Donchian Hurst Exponent strategy is built around that trades based on Donchian Channel breakouts with Hurst Exponent filter.
Testing indicates an average annual return of about 91%. It performs best in the stocks market.
Signals trigger when Donchian confirms trend changes on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like DonchianPeriod, HurstPeriod. 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:
DonchianPeriod = 20HurstPeriod = 100HurstThreshold = 0.5mStopLossPercent = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Donchian, Hurst, Exponent
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- 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 that trades based on Donchian Channel breakouts with Hurst Exponent filter.
/// Enters position when price breaks Donchian Channel with Hurst Exponent indicating trend persistence.
/// </summary>
public class DonchianHurstStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<decimal> _hurstThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal _hurstValue;
private decimal? _previousUpper;
private decimal? _previousLower;
private decimal? _previousMiddle;
/// <summary>
/// Strategy parameter: Donchian Channel period.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Hurst Exponent calculation period.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Hurst Exponent threshold for trend persistence.
/// </summary>
public decimal HurstThreshold
{
get => _hurstThreshold.Value;
set => _hurstThreshold.Value = value;
}
/// <summary>
/// Strategy parameter: Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Strategy parameter: Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DonchianHurstStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Period for Donchian Channel indicator", "Indicator Settings");
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Hurst Period", "Period for Hurst Exponent calculation", "Indicator Settings");
_hurstThreshold = Param(nameof(HurstThreshold), 0.45m)
.SetRange(0, 1)
.SetDisplay("Hurst Threshold", "Minimum Hurst Exponent value for trend persistence (>0.5 is trending)", "Indicator Settings");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop Loss percentage from entry price", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).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();
_hurstValue = 0;
_previousUpper = null;
_previousLower = null;
_previousMiddle = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create Donchian Channel indicator
var donchian = new DonchianChannels
{
Length = DonchianPeriod
};
// Create FractalDimension indicator for Hurst calculation
// We use 1 - FractalDimension to get Hurst Exponent (H = 2 - D)
var fractalDimension = new FractalDimension
{
Length = HurstPeriod
};
// Create subscription for candles
var subscription = SubscribeCandles(CandleType);
// Bind indicators to subscription and start
subscription
.BindEx(donchian, fractalDimension, ProcessIndicators)
.Start();
// Add chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
}
// Start position protection with percentage-based stop-loss
StartProtection(
takeProfit: new Unit(0), // No take profit, using Donchian Channel for exit
stopLoss: new Unit(StopLossPercent, UnitTypes.Percent)
);
}
private void ProcessIndicators(ICandleMessage candle, IIndicatorValue donchianValue, IIndicatorValue fractalDimensionValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// --- FractalDimension logic (was ProcessFractalDimension) ---
decimal fractalDimension = fractalDimensionValue.ToDecimal();
_hurstValue = 2m - fractalDimension;
// Log Hurst Exponent value periodically
if (candle.OpenTime.Second == 0 && candle.OpenTime.Minute % 15 == 0)
{
LogInfo($"Current Hurst Exponent: {_hurstValue} (>{HurstThreshold} indicates trend persistence)");
}
// --- Donchian logic (was ProcessDonchianChannel) ---
if (!IsFormedAndOnlineAndAllowTrading())
return;
var donchianTyped = (DonchianChannelsValue)donchianValue;
// Convert indicator values to decimal
if (donchianTyped.UpperBand is not decimal upper ||
donchianTyped.LowerBand is not decimal lower ||
donchianTyped.Middle is not decimal middle)
{
return;
}
if (!_previousUpper.HasValue || !_previousLower.HasValue || !_previousMiddle.HasValue)
{
_previousUpper = upper;
_previousLower = lower;
_previousMiddle = middle;
return;
}
if (_hurstValue > HurstThreshold)
{
if (candle.ClosePrice > _previousUpper.Value && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (candle.ClosePrice < _previousLower.Value && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
if (Position > 0 && candle.ClosePrice < _previousMiddle.Value)
SellMarket(Position);
else if (Position < 0 && candle.ClosePrice > _previousMiddle.Value)
BuyMarket(-Position);
_previousUpper = upper;
_previousLower = lower;
_previousMiddle = middle;
}
}
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 DonchianChannels, FractalDimension
from StockSharp.Algo.Strategies import Strategy
class donchian_hurst_strategy(Strategy):
"""
Strategy that trades based on Donchian Channel breakouts with Hurst Exponent filter.
"""
def __init__(self):
super(donchian_hurst_strategy, self).__init__()
self._donchian_period = self.Param("DonchianPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Donchian Period", "Period for Donchian Channel indicator", "Indicator Settings")
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetGreaterThanZero() \
.SetDisplay("Hurst Period", "Period for Hurst Exponent calculation", "Indicator Settings")
self._hurst_threshold = self.Param("HurstThreshold", 0.45) \
.SetDisplay("Hurst Threshold", "Minimum Hurst Exponent value for trend persistence", "Indicator Settings")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop Loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._hurst_value = 0.0
self._previous_upper = None
self._previous_lower = None
self._previous_middle = None
@property
def DonchianPeriod(self):
return self._donchian_period.Value
@DonchianPeriod.setter
def DonchianPeriod(self, value):
self._donchian_period.Value = value
@property
def HurstPeriod(self):
return self._hurst_period.Value
@HurstPeriod.setter
def HurstPeriod(self, value):
self._hurst_period.Value = value
@property
def HurstThreshold(self):
return self._hurst_threshold.Value
@HurstThreshold.setter
def HurstThreshold(self, value):
self._hurst_threshold.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
@property
def CandleType(self):
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(donchian_hurst_strategy, self).OnReseted()
self._hurst_value = 0.0
self._previous_upper = None
self._previous_lower = None
self._previous_middle = None
def OnStarted2(self, time):
super(donchian_hurst_strategy, self).OnStarted2(time)
donchian = DonchianChannels()
donchian.Length = self.DonchianPeriod
fractal_dimension = FractalDimension()
fractal_dimension.Length = self.HurstPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(donchian, fractal_dimension, self.ProcessIndicators).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(0, UnitTypes.Absolute),
stopLoss=Unit(self.StopLossPercent, UnitTypes.Percent)
)
def ProcessIndicators(self, candle, donchian_value, fractal_dimension_value):
if candle.State != CandleStates.Finished:
return
fractal_dim = float(fractal_dimension_value)
self._hurst_value = 2.0 - fractal_dim
if not self.IsFormedAndOnlineAndAllowTrading():
return
if donchian_value.UpperBand is None or donchian_value.LowerBand is None or donchian_value.Middle is None:
return
upper = float(donchian_value.UpperBand)
lower = float(donchian_value.LowerBand)
middle = float(donchian_value.Middle)
if self._previous_upper is None or self._previous_lower is None or self._previous_middle is None:
self._previous_upper = upper
self._previous_lower = lower
self._previous_middle = middle
return
close_price = float(candle.ClosePrice)
if self._hurst_value > self.HurstThreshold:
if close_price > self._previous_upper and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close_price < self._previous_lower and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
if self.Position > 0 and close_price < self._previous_middle:
self.SellMarket(self.Position)
elif self.Position < 0 and close_price > self._previous_middle:
self.BuyMarket(abs(self.Position))
self._previous_upper = upper
self._previous_lower = lower
self._previous_middle = middle
def CreateClone(self):
return donchian_hurst_strategy()