Hull MA Trend
Strategy based on Hull Moving Average trend
Testing indicates an average annual return of about 61%. It performs best in the crypto market.
The Hull MA Trend strategy monitors the slope of the Hull Moving Average. Rising slopes prompt longs and falling slopes prompt shorts, with an ATR trailing stop protecting each trade.
Its responsive calculation reduces lag compared with traditional moving averages, allowing the system to react quickly to new momentum. The ATR stop helps avoid large drawdowns if the slope changes abruptly.
Details
- Entry Criteria: Signals based on MA, ATR.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or stop.
- Stops: Yes.
- Default Values:
HmaPeriod= 9AtrPeriod= 14AtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MA, ATR
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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 trend.
/// It enters long position when Hull MA is rising and short position when Hull MA is falling.
/// </summary>
public class HullMaTrendStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
// Current state
private decimal _prevHmaValue;
/// <summary>
/// Period for Hull Moving Average.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// Period for ATR calculation (stop-loss).
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Multiplier for ATR to determine stop-loss distance.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the Hull MA Trend strategy.
/// </summary>
public HullMaTrendStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 500)
.SetDisplay("HMA Period", "Period for Hull Moving Average", "Indicators")
.SetOptimize(5, 15, 2);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Period for Average True Range (stop-loss)", "Risk parameters")
.SetOptimize(10, 20, 2);
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetDisplay("ATR Multiplier", "Multiplier for ATR to determine stop-loss distance", "Risk parameters")
.SetOptimize(1, 3, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var hma = new HullMovingAverage { Length = HmaPeriod };
var 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);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal hmaValue, decimal atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Skip the first received value for proper trend determination
if (_prevHmaValue == 0)
{
_prevHmaValue = hmaValue;
return;
}
// Determine HMA direction with minimum slope threshold
var slopeThreshold = _prevHmaValue * 0.0002m; // 0.02% minimum change
var isHmaRising = hmaValue - _prevHmaValue > slopeThreshold;
var isHmaFalling = _prevHmaValue - hmaValue > slopeThreshold;
// Trading logic - only on significant direction changes
if (isHmaRising && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
}
else if (isHmaFalling && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
}
// Update previous HMA value
_prevHmaValue = hmaValue;
}
}
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
from StockSharp.Algo.Indicators import HullMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_trend_strategy(Strategy):
def __init__(self):
super(hull_ma_trend_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 500) \
.SetDisplay("HMA Period", "Period for Hull Moving Average", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for Average True Range (stop-loss)", "Risk parameters")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "Multiplier for ATR to determine stop-loss distance", "Risk parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_hma_value = 0.0
@property
def HmaPeriod(self):
return self._hma_period.Value
@HmaPeriod.setter
def HmaPeriod(self, value):
self._hma_period.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
@AtrMultiplier.setter
def AtrMultiplier(self, value):
self._atr_multiplier.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(hull_ma_trend_strategy, self).OnStarted2(time)
self._prev_hma_value = 0.0
hma = HullMovingAverage()
hma.Length = self.HmaPeriod
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
self.SubscribeCandles(self.CandleType) \
.Bind(hma, atr, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, hma_value, atr_value):
if candle.State != CandleStates.Finished:
return
hma_f = float(hma_value)
if self._prev_hma_value == 0:
self._prev_hma_value = hma_f
return
slope_threshold = self._prev_hma_value * 0.0002
is_hma_rising = hma_f - self._prev_hma_value > slope_threshold
is_hma_falling = self._prev_hma_value - hma_f > slope_threshold
if is_hma_rising and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif is_hma_falling and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
self._prev_hma_value = hma_f
def OnReseted(self):
super(hull_ma_trend_strategy, self).OnReseted()
self._prev_hma_value = 0.0
def CreateClone(self):
return hull_ma_trend_strategy()