Hull Ma Adx Strategy
Strategy based on Hull Moving Average and ADX. Enters long when HMA increases and ADX > 25 (strong trend). Enters short when HMA decreases and ADX > 25 (strong trend). Exits when ADX < 20 (weakening trend).
Testing indicates an average annual return of about 178%. It performs best in the stocks market.
Hull MA shows the trend, while ADX confirms its intensity. Entries follow the Hull slope when ADX indicates strength.
Effective for traders who focus on smooth trends with confirmation. ATR stops keep losses under control.
Details
- Entry Criteria:
- Long:
HullMA turning up && ADX > 25 - Short:
HullMA turning down && ADX > 25
- Long:
- Long/Short: Both
- Exit Criteria: Hull MA reversal
- Stops: ATR-based using
AtrMultiplier - Default Values:
HmaPeriod= 9AdxPeriod= 14AtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(15).TimeFrame()
- Filters:
- Category: Trend
- Direction: Both
- Indicators: Hull MA, Moving Average, ADX
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- 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;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Hull Moving Average and ADX.
/// Enters long when HMA increases and ADX > 25 (strong trend).
/// Enters short when HMA decreases and ADX > 25 (strong trend).
/// Exits when ADX < 20 (weakening trend).
/// </summary>
public class HullMaAdxStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private HullMovingAverage _hma;
private AverageDirectionalIndex _adx;
private AverageTrueRange _atr;
private decimal _prevHmaValue;
private decimal _prevAdxValue;
private int _cooldown;
private bool _hasPrevSlope;
private bool _prevSlopeUp;
/// <summary>
/// Hull Moving Average period.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// ADX indicator period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// ATR multiplier for stop loss calculation.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="HullMaAdxStrategy"/>.
/// </summary>
public HullMaAdxStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetDisplay("HMA Period", "Period for Hull Moving Average calculation", "Indicators")
.SetOptimize(5, 15, 2);
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetDisplay("ADX Period", "Period for Average Directional Movement Index", "Indicators")
.SetOptimize(10, 20, 2);
_cooldownBars = Param(nameof(CooldownBars), 80)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetDisplay("ATR Multiplier", "ATR multiplier for stop loss calculation", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe of data for strategy", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(0.5m, 2.0m, 0.5m);
}
/// <inheritdoc />
public override IEnumerable<(Security, DataType)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hma?.Reset();
_adx?.Reset();
_atr?.Reset();
_prevHmaValue = 0;
_prevAdxValue = 0;
_cooldown = 0;
_hasPrevSlope = false;
_prevSlopeUp = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_hma = new() { Length = HmaPeriod };
_adx = new() { Length = AdxPeriod };
_atr = new() { Length = 14 };
// Create subscription
var subscription = SubscribeCandles(CandleType);
// Process candles with indicators
subscription
.BindEx(_hma, _adx, _atr, ProcessCandle)
.Start();
// Setup chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _hma);
DrawOwnTrades(area);
// ADX in separate area
var adxArea = CreateChartArea();
if (adxArea != null)
{
DrawIndicator(adxArea, _adx);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hmaValue, IIndicatorValue adxValue, IIndicatorValue atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
var typedAdx = (AverageDirectionalIndexValue)adxValue;
if (typedAdx.MovingAverage is not decimal adx)
return;
var hma = hmaValue.ToDecimal();
// Detect HMA direction
bool hmaIncreasing = hma > _prevHmaValue;
bool hmaDecreasing = hma < _prevHmaValue;
if (!_hasPrevSlope)
{
_hasPrevSlope = true;
_prevSlopeUp = hmaIncreasing;
}
if (_cooldown > 0)
_cooldown--;
var slopeTurnedUp = !_prevSlopeUp && hmaIncreasing;
var slopeTurnedDown = _prevSlopeUp && hmaDecreasing;
// Trading logic
if (_cooldown == 0 && slopeTurnedUp && Position <= 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (_cooldown == 0 && slopeTurnedDown && Position >= 0)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position != 0 && (slopeTurnedUp || slopeTurnedDown))
{
if (Position > 0)
SellMarket();
else
BuyMarket();
_cooldown = CooldownBars;
}
// Store current values for next candle
_prevHmaValue = hma;
_prevAdxValue = adx;
_prevSlopeUp = hmaIncreasing;
}
}
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
from StockSharp.Algo.Indicators import HullMovingAverage, AverageDirectionalIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class hull_ma_adx_strategy(Strategy):
"""
Hull MA + ADX trend strategy. Enters on HMA slope turn with ADX confirmation.
"""
def __init__(self):
super(hull_ma_adx_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9).SetDisplay("HMA Period", "Hull MA period", "Indicators")
self._adx_period = self.Param("AdxPeriod", 14).SetDisplay("ADX Period", "ADX period", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 80).SetDisplay("Cooldown", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", tf(5)).SetDisplay("Candle Type", "Timeframe", "General")
self._prev_hma = 0.0
self._has_prev_slope = False
self._prev_slope_up = False
self._cooldown = 0
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(hull_ma_adx_strategy, self).OnReseted()
self._prev_hma = 0.0
self._has_prev_slope = False
self._prev_slope_up = False
self._cooldown = 0
def OnStarted2(self, time):
super(hull_ma_adx_strategy, self).OnStarted2(time)
self._prev_hma = 0.0
self._has_prev_slope = False
self._prev_slope_up = False
self._cooldown = 0
hma = HullMovingAverage()
hma.Length = self._hma_period.Value
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
atr = AverageTrueRange()
atr.Length = 14
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(hma, adx, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, hma_val, adx_val, atr_val):
if candle.State != CandleStates.Finished:
return
adx_ma = adx_val.MovingAverage
if adx_ma is None:
return
hma = float(hma_val)
hma_increasing = hma > self._prev_hma
hma_decreasing = hma < self._prev_hma
if not self._has_prev_slope:
self._has_prev_slope = True
self._prev_slope_up = hma_increasing
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldown_bars.Value)
slope_turned_up = not self._prev_slope_up and hma_increasing
slope_turned_down = self._prev_slope_up and hma_decreasing
if self._cooldown == 0 and slope_turned_up and self.Position <= 0:
self.BuyMarket()
self._cooldown = cooldown_val
elif self._cooldown == 0 and slope_turned_down and self.Position >= 0:
self.SellMarket()
self._cooldown = cooldown_val
elif self.Position != 0 and (slope_turned_up or slope_turned_down):
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._cooldown = cooldown_val
self._prev_hma = hma
self._prev_slope_up = hma_increasing
def CreateClone(self):
return hull_ma_adx_strategy()