Hurst-Exponent-Volatilitätsfilter-Strategie
Die Hurst-Exponent-Volatilitätsfilter-Strategie verwendet den Hurst Exponent zusammen mit Volatilitätsfiltern. Sie tritt nur in Trades ein, wenn bestimmte Bedingungen übereinstimmen.
Tests zeigen eine durchschnittliche Jahresrendite von etwa 163%. Sie funktioniert am besten am Aktienmarkt.
Signale erfordern, dass der Indikator einen Schwellenwert überschreitet, während die Volatilität vordefinierte Kriterien erfüllt. Positionen können Long oder Short sein und haben eingebaute Stops.
Für Trader entwickelt, die Risikokontrolle schätzen; die Strategie steigt aus, sobald der Indikator zur Mitte zurückkehrt oder sich die Volatilität verschiebt. Anfangseinstellung HurstPeriod = 100.
Details
- Einstiegskriterien: Der Indikator kreuzt zurück in Richtung Mittelwert.
- Long/Short: Beide.
- Ausstiegskriterien: Der Indikator kehrt zum Durchschnitt zurück.
- Stops: Ja.
- Standardwerte:
HurstPeriod= 100MAPeriod= 20ATRPeriod= 14StopLoss= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filter:
- Kategorie: Mean Reversion
- Richtung: Beide
- Indikatoren: Hurst
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Kurzfristig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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 mean reversion with a volatility filter.
/// Trades back toward the moving average only while the Hurst exponent is below 0.5 (anti-persistent prices)
/// and ATR is below its own average. Exits when price returns to the moving average or volatility expands.
/// </summary>
public class HurstVolatilityFilterStrategy : Strategy
{
private const decimal _randomWalkHurst = 0.5m;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _atrAverage;
private HurstExponent _hurst;
private decimal? _prevClose;
/// <summary>
/// Period for the Hurst exponent.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Period for the moving average.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Period for ATR and its average.
/// </summary>
public int ATRPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="HurstVolatilityFilterStrategy"/>.
/// </summary>
public HurstVolatilityFilterStrategy()
{
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Hurst Period", "Period for the Hurst exponent", "Indicators");
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for the moving average", "Indicators");
_atrPeriod = Param(nameof(ATRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR and its average", "Indicators");
_stopLoss = Param(nameof(StopLoss), 2.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management");
_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 OnReseted()
{
base.OnReseted();
_atrAverage = null;
_hurst = null;
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = MAPeriod };
var atr = new AverageTrueRange { Length = ATRPeriod };
_hurst = new HurstExponent { Length = HurstPeriod };
_atrAverage = new SimpleMovingAverage { Length = ATRPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, atr, ProcessCandle)
.Start();
StartProtection(
takeProfit: null,
stopLoss: StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Percent) : null
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var atrAverage = _atrAverage.Process(atrValue, candle.ServerTime, true).ToDecimal();
var prevClose = _prevClose;
_prevClose = candle.ClosePrice;
if (prevClose is not decimal prev)
return;
// Hurst is measured on bar-to-bar price changes, where 0.5 separates trending from mean-reverting behaviour.
var hurstResult = _hurst.Process(candle.ClosePrice - prev, candle.ServerTime, true);
if (!_atrAverage.IsFormed || !_hurst.IsFormed || hurstResult.IsEmpty)
return;
var hurstValue = hurstResult.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var isQuiet = atrValue < atrAverage;
var isMeanReverting = hurstValue < _randomWalkHurst;
if (Position > 0 && (close >= smaValue || !isQuiet))
{
SellMarket(Position);
return;
}
if (Position < 0 && (close <= smaValue || !isQuiet))
{
BuyMarket(-Position);
return;
}
if (!isMeanReverting || !isQuiet)
return;
if (close < smaValue && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close > smaValue && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange, HurstExponent
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
RANDOM_WALK_HURST = 0.5
class hurst_volatility_filter_strategy(Strategy):
"""
Hurst exponent mean reversion with a volatility filter.
Trades back toward the moving average only while the Hurst exponent is below 0.5 (anti-persistent prices)
and ATR is below its own average. Exits when price returns to the moving average or volatility expands.
"""
def __init__(self):
super(hurst_volatility_filter_strategy, self).__init__()
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetGreaterThanZero() \
.SetDisplay("Hurst Period", "Period for the Hurst exponent", "Indicators")
self._ma_period = self.Param("MAPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("MA Period", "Period for the moving average", "Indicators")
self._atr_period = self.Param("ATRPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period for ATR and its average", "Indicators")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._atr_average = None
self._hurst = None
self._prev_close = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(hurst_volatility_filter_strategy, self).OnReseted()
self._atr_average = None
self._hurst = None
self._prev_close = None
def OnStarted2(self, time):
super(hurst_volatility_filter_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
self._hurst = HurstExponent()
self._hurst.Length = self._hurst_period.Value
self._atr_average = SimpleMovingAverage()
self._atr_average.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(sma, atr, self._process_candle).Start()
stop = float(self._stop_loss.Value)
self.StartProtection(None, Unit(stop, UnitTypes.Percent) if stop > 0 else None)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_value, atr_value):
if candle.State != CandleStates.Finished:
return
sma_value = float(sma_value)
atr_value = float(atr_value)
close = float(candle.ClosePrice)
atr_average = float(process_float(self._atr_average, atr_value, candle.ServerTime, True))
prev_close = self._prev_close
self._prev_close = close
if prev_close is None:
return
# Hurst is measured on bar-to-bar price changes, where 0.5 separates trending from mean-reverting behaviour.
hurst_result = process_float(self._hurst, close - prev_close, candle.ServerTime, True)
if not self._atr_average.IsFormed or not self._hurst.IsFormed or hurst_result.IsEmpty:
return
hurst_value = float(hurst_result)
if not self.IsFormedAndOnlineAndAllowTrading():
return
is_quiet = atr_value < atr_average
is_mean_reverting = hurst_value < RANDOM_WALK_HURST
if self.Position > 0 and (close >= sma_value or not is_quiet):
self.SellMarket(self.Position)
return
if self.Position < 0 and (close <= sma_value or not is_quiet):
self.BuyMarket(-self.Position)
return
if not is_mean_reverting or not is_quiet:
return
if close < sma_value and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close > sma_value and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return hurst_volatility_filter_strategy()