Ver no GitHub

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 = 20
    • HurstPeriod = 100
    • HurstThreshold = 0.5m
    • StopLossPercent = 2m
    • CandleType = 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;
	}
}