Estratégia de Hull MA Volatility Contraction
A estratégia Hull MA Volatility Contraction é construída em torno da Média Móvel Hull com filtro de contração de volatilidade.
Os testes indicam um retorno anual médio de aproximadamente 76%. Tem melhor desempenho no mercado de câmbio.
Os sinais são ativados quando os indicadores confirmam padrões de contração de volatilidade em dados intradiários (15m). Este método é adequado para traders ativos.
Os stops dependem de múltiplos do ATR e fatores como HmaPeriod, AtrPeriod. Ajuste esses valores padrão para equilibrar risco e retorno.
Detalhes
- Critérios de entrada: ver implementação para condições de indicadores.
- Comprado/Vendido: Ambos os lados.
- Critérios de saída: sinal oposto ou lógica de stop.
- Stops: Sim, usando cálculos baseados em indicadores.
- Valores padrão:
HmaPeriod = 9AtrPeriod = 14VolatilityContractionFactor = 2.0mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Filtros:
- Categoria: Seguidor de tendência
- Direção: Ambos
- Indicadores: múltiplos indicadores
- Stops: Sim
- Complexidade: Intermediário
- Período: Intradiário (15m)
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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()