Стратегия возврата по показателю Херста
Подход использует показатель Херста, чтобы определить, когда рынок ведёт себя как среднеобращающийся. Значения ниже 0.5 подразумевают склонность цены возвращаться к среднему, что создаёт возможности для контртрендовых сделок.
Тестирование показывает среднегодичную доходность около 121%. Стратегию лучше запускать на крипторынке.
Длинная позиция открывается, когда значение Херста ниже HurstThreshold и цена закрывается ниже скользящей средней. Короткая позиция — когда Херст ниже HurstThreshold и закрытие выше средней. Позиции закрываются, когда цена возвращается к средней линии или показатель Херста поднимается выше порога.
Стратегия подходит трейдерам, предпочитающим статистические закономерности сильным трендам. Защитный стоп‑лосс оберегает от затяжных движений без возврата.
Подробности
- Условия входа:
- Long: Hurst < HurstThreshold && закрытие < MA
- Short: Hurst < HurstThreshold && закрытие > MA
- Long/Short: обе стороны.
- Условия выхода:
- Long: выход при закрытии >= MA или Hurst > HurstThreshold
- Short: выход при закрытии <= MA или Hurst > HurstThreshold
- Стопы: да, процентный стоп‑лосс.
- Параметры по умолчанию:
HurstPeriod = 100
AveragePeriod = 20
HurstThreshold = 0.7
StopLossPercent = 2
В правилах указано 0.5 — теоретическая граница возврата к среднему, но R/S-оценка за 100 пятиминутных свечей ни разу не опускалась ниже примерно 0.65 на истории BTC и TON, на которой тестируются примеры, так что правило не могло торговать; HurstThreshold делает уровень настройкой со значением по умолчанию 0.7. MA — простая скользящая средняя за AveragePeriod. Стоп — фиксированный StopLossPercent от цены входа, он отслеживается и между свечами; 0 отключает его. Сигнал на вход против открытой позиции разворачивает её.
CandleType = TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Возврат к среднему
- Направление: Обе стороны
- Индикаторы: Показатель Херста, MA
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hurst Exponent Reversion strategy.
/// A HurstPeriod Hurst exponent below HurstThreshold marks a mean-reverting market. Then a close below the AveragePeriod simple moving average
/// goes long and a close above it goes short, reversing an opposite position. A long closes once the close is back at or above the average
/// or the exponent rises above the threshold, a short mirrors it, and a percent stop limits the loss.
/// </summary>
public class HurstExponentReversionStrategy : Strategy
{
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _hurstThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Period of the Hurst exponent.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Period of the simple moving average.
/// </summary>
public int AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Hurst exponent level below which the market reverts.
/// </summary>
public decimal HurstThreshold
{
get => _hurstThreshold.Value;
set => _hurstThreshold.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HurstExponentReversionStrategy()
{
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Hurst Period", "Period of the Hurst exponent", "Indicators");
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period of the simple moving average", "Indicators");
_hurstThreshold = Param(nameof(HurstThreshold), 0.7m)
.SetDisplay("Hurst Threshold", "Hurst exponent level below which the market reverts", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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 OnStarted2(DateTime time)
{
base.OnStarted2(time);
var hurst = new HurstExponent { Length = HurstPeriod };
var sma = new SimpleMovingAverage { Length = AveragePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(hurst, sma, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, hurst);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hurstValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!hurstValue.IsFormed || !smaValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var hurst = hurstValue.GetValue<decimal>();
var sma = smaValue.GetValue<decimal>();
var close = candle.ClosePrice;
var reverting = hurst < HurstThreshold;
if (reverting && close < sma && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (reverting && close > sma && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && (close >= sma || hurst > HurstThreshold))
SellMarket(Position);
else if (Position < 0 && (close <= sma || hurst > HurstThreshold))
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import HurstExponent, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class hurst_exponent_reversion_strategy(Strategy):
"""
Hurst Exponent Reversion strategy.
A HurstPeriod Hurst exponent below HurstThreshold marks a mean-reverting market. Then a close below the AveragePeriod simple moving average
goes long and a close above it goes short, reversing an opposite position. A long closes once the close is back at or above the average
or the exponent rises above the threshold, a short mirrors it, and a percent stop limits the loss.
"""
def __init__(self):
super(hurst_exponent_reversion_strategy, self).__init__()
self._hurst_period = self.Param("HurstPeriod", 100).SetGreaterThanZero().SetDisplay("Hurst Period", "Period of the Hurst exponent", "Indicators")
self._average_period = self.Param("AveragePeriod", 20).SetGreaterThanZero().SetDisplay("Average Period", "Period of the simple moving average", "Indicators")
self._hurst_threshold = self.Param("HurstThreshold", 0.7).SetDisplay("Hurst Threshold", "Hurst exponent level below which the market reverts", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(hurst_exponent_reversion_strategy, self).OnStarted2(time)
hurst = HurstExponent()
hurst.Length = self._hurst_period.Value
sma = SimpleMovingAverage()
sma.Length = self._average_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(hurst, sma, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, hurst)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, hurst_value, sma_value):
if candle.State != CandleStates.Finished:
return
if not hurst_value.IsFormed or not sma_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
hurst = hurst_value.GetValue[Decimal](None)
sma = sma_value.GetValue[Decimal](None)
close = candle.ClosePrice
threshold = Decimal(self._hurst_threshold.Value)
reverting = hurst < threshold
if reverting and close < sma and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif reverting and close > sma and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and (close >= sma or hurst > threshold):
self.SellMarket(self.Position)
elif self.Position < 0 and (close <= sma or hurst > threshold):
self.BuyMarket(-self.Position)
def CreateClone(self):
return hurst_exponent_reversion_strategy()