Дончан и показатель Херста
Стратегия Donchian Hurst Exponent торгует по пробоям канала Дончиана с фильтром по экспоненте Херста. Сигналы формируются, когда Дончиан подтверждает смену тренда на внутридневных данных (5м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров DonchianPeriod, HurstPeriod. Эти значения можно изменять для баланса риска и прибыли.
Тестирование показывает среднегодичную доходность около 91%. Стратегию лучше запускать на фондовом рынке.
Подробности
- Условия входа: см. реализацию для условий по индикаторам.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: обратный сигнал или логика стопов.
- Стопы: да, вычисляются на основе индикаторов.
- Значения по умолчанию:
DonchianPeriod = 20HurstPeriod = 100HurstThreshold = 0.5mStopLossPercent = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: Donchian, Hurst, Exponent
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (5m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()