Hull MA Volatility Contraction-Strategie
Die Hull MA Volatility Contraction-Strategie basiert auf dem Hull Moving Average mit Volatilitätskontraktionsfilter.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 76%. Sie funktioniert am besten auf dem Forex-Markt.
Signale werden ausgelöst, wenn die Indikatoren Volatilitätskontraktionsmuster auf Intraday-Daten (15m) bestätigen. Diese Methode eignet sich für aktive Trader.
Stops basieren auf ATR-Vielfachen und Faktoren wie HmaPeriod, AtrPeriod. Passen Sie diese Standardwerte an, um Risiko und Ertrag auszubalancieren.
Details
- Einstiegskriterien: siehe Implementierung für Indikatorbedingungen.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: entgegengesetztes Signal oder Stop-Logik.
- Stops: Ja, mit indikatorbasierten Berechnungen.
- Standardwerte:
HmaPeriod = 9AtrPeriod = 14VolatilityContractionFactor = 2.0mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Filter:
- Kategorie: Trendfolge
- Richtung: Beide
- Indikatoren: mehrere Indikatoren
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday (15m)
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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 based on Hull Moving Average with volatility contraction filter.
/// </summary>
public class HullMaVolatilityContractionStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _volatilityContractionFactor;
private readonly StrategyParam<DataType> _candleType;
private HullMovingAverage _hma;
private AverageTrueRange _atr;
// Store values for analysis
private readonly SynchronizedList<decimal> _atrValues = [];
private decimal _prevHmaValue;
private decimal _currentHmaValue;
private bool _isLongPosition;
private bool _isShortPosition;
/// <summary>
/// Hull Moving Average period.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// Average True Range period for volatility calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Volatility contraction factor (standard deviation multiplier).
/// </summary>
public decimal VolatilityContractionFactor
{
get => _volatilityContractionFactor.Value;
set => _volatilityContractionFactor.Value = value;
}
/// <summary>
/// Candle type to use for the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="HullMaVolatilityContractionStrategy"/>.
/// </summary>
public HullMaVolatilityContractionStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetDisplay("Hull MA Period", "Hull Moving Average period", "Hull MA")
.SetOptimize(5, 20, 1);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR volatility calculation", "Volatility")
.SetOptimize(10, 30, 2);
_volatilityContractionFactor = Param(nameof(VolatilityContractionFactor), 2.0m)
.SetDisplay("Volatility Contraction Factor", "Standard deviation multiplier for volatility contraction", "Volatility")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_prevHmaValue = default;
_currentHmaValue = default;
_isLongPosition = false;
_isShortPosition = false;
_atrValues.Clear();
_hma = null;
_atr = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_hma = new HullMovingAverage
{
Length = HmaPeriod
};
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_hma, _atr, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _hma);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
// Setup position protection
StartProtection(
new Unit(2, UnitTypes.Percent),
new Unit(2, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle, decimal hmaValue, decimal atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Save previous HMA value
_prevHmaValue = _currentHmaValue;
// Extract values from indicators
_currentHmaValue = hmaValue;
decimal atr = atrValue;
// Store ATR values for volatility analysis
_atrValues.Add(atr);
// Keep only needed history
while (_atrValues.Count > AtrPeriod * 2)
_atrValues.RemoveAt(0);
// Check for volatility contraction
bool isVolatilityContracted = IsVolatilityContracted();
// Determine HMA trend direction
bool isHmaRising = _currentHmaValue > _prevHmaValue;
bool isHmaFalling = _currentHmaValue < _prevHmaValue;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Log current status
if (_atrValues.Count >= AtrPeriod)
{
decimal avgAtr = _atrValues.Skip(Math.Max(0, _atrValues.Count - AtrPeriod)).Average();
LogInfo($"HMA: {_currentHmaValue:F2} (Prev: {_prevHmaValue:F2}), ATR: {atr:F2}, Avg ATR: {avgAtr:F2}, Volatility Contracted: {isVolatilityContracted}");
}
// Trading logic
// Buy when HMA is rising and volatility is contracted
if (isHmaRising && isVolatilityContracted && Position <= 0)
{
BuyMarket(Volume);
LogInfo($"Buy Signal: HMA Rising ({_prevHmaValue:F2} -> {_currentHmaValue:F2}) with Contracted Volatility");
_isLongPosition = true;
_isShortPosition = false;
}
// Sell when HMA is falling and volatility is contracted
else if (isHmaFalling && isVolatilityContracted && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
LogInfo($"Sell Signal: HMA Falling ({_prevHmaValue:F2} -> {_currentHmaValue:F2}) with Contracted Volatility");
_isLongPosition = false;
_isShortPosition = true;
}
// Exit long position when HMA starts falling
else if (_isLongPosition && isHmaFalling)
{
SellMarket(Position);
LogInfo($"Exit Long: HMA started falling ({_prevHmaValue:F2} -> {_currentHmaValue:F2})");
_isLongPosition = false;
}
// Exit short position when HMA starts rising
else if (_isShortPosition && isHmaRising)
{
BuyMarket(Math.Abs(Position));
LogInfo($"Exit Short: HMA started rising ({_prevHmaValue:F2} -> {_currentHmaValue:F2})");
_isShortPosition = false;
}
}
private bool IsVolatilityContracted()
{
// Need enough ATR values for calculation
if (_atrValues.Count < AtrPeriod)
return false;
// Get recent ATR values for analysis
var recentAtrValues = _atrValues.Skip(Math.Max(0, _atrValues.Count - AtrPeriod)).ToList();
// Calculate mean and standard deviation
decimal mean = recentAtrValues.Average();
decimal sumSquaredDifferences = recentAtrValues.Sum(x => (x - mean) * (x - mean));
decimal standardDeviation = (decimal)Math.Sqrt((double)(sumSquaredDifferences / recentAtrValues.Count));
// Get current ATR (latest)
decimal currentAtr = _atrValues.Last();
// Check if current ATR is less than mean minus standard deviation * factor
bool isContracted = currentAtr < (mean - standardDeviation * VolatilityContractionFactor);
// Log details if contraction is detected
if (isContracted)
{
LogInfo($"Volatility Contraction Detected: Current ATR {currentAtr:F2} < Mean {mean:F2} - (StdDev {standardDeviation:F2} * Factor {VolatilityContractionFactor})");
}
return isContracted;
}
}
import clr
import math
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 HullMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_volatility_contraction_strategy(Strategy):
"""
Hull MA with volatility contraction filter. Enters when HMA trends and ATR is contracted.
"""
def __init__(self):
super(hull_ma_volatility_contraction_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9) \
.SetDisplay("Hull MA Period", "Hull Moving Average period", "Hull MA") \
.SetCanOptimize(True) \
.SetOptimize(5, 20, 1)
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR volatility calculation", "Volatility") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 2)
self._volatility_contraction_factor = self.Param("VolatilityContractionFactor", 2.0) \
.SetDisplay("Volatility Contraction Factor", "Standard deviation multiplier for volatility contraction", "Volatility") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_hma = 0.0
self._cur_hma = 0.0
self._atr_values = []
self._is_long = False
self._is_short = False
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(hull_ma_volatility_contraction_strategy, self).OnReseted()
self._prev_hma = 0.0
self._cur_hma = 0.0
self._atr_values = []
self._is_long = False
self._is_short = False
def OnStarted2(self, time):
super(hull_ma_volatility_contraction_strategy, self).OnStarted2(time)
hma = HullMovingAverage()
hma.Length = int(self._hma_period.Value)
atr = AverageTrueRange()
atr.Length = int(self._atr_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(hma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(2, UnitTypes.Percent)
)
def _is_volatility_contracted(self):
period = int(self._atr_period.Value)
if len(self._atr_values) < period:
return False
recent = self._atr_values[-period:]
mean = sum(recent) / len(recent)
sum_sq = sum((x - mean) ** 2 for x in recent)
std = math.sqrt(sum_sq / len(recent))
current = self._atr_values[-1]
return current < (mean - std * float(self._volatility_contraction_factor.Value))
def _process_candle(self, candle, hma_val, atr_val):
if candle.State != CandleStates.Finished:
return
self._prev_hma = self._cur_hma
self._cur_hma = float(hma_val)
atr = float(atr_val)
self._atr_values.append(atr)
max_buf = int(self._atr_period.Value) * 2
while len(self._atr_values) > max_buf:
self._atr_values.pop(0)
contracted = self._is_volatility_contracted()
rising = self._cur_hma > self._prev_hma
falling = self._cur_hma < self._prev_hma
if not self.IsFormedAndOnlineAndAllowTrading():
return
if rising and contracted and self.Position <= 0:
self.BuyMarket(self.Volume)
self._is_long = True
self._is_short = False
elif falling and contracted and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
self._is_long = False
self._is_short = True
elif self._is_long and falling:
self.SellMarket(self.Position)
self._is_long = False
elif self._is_short and rising:
self.BuyMarket(Math.Abs(self.Position))
self._is_short = False
def CreateClone(self):
return hull_ma_volatility_contraction_strategy()