Adaptive HMA Plus
Adaptive Hull Moving Average strategy that adjusts its period based on volatility or volume. It opens long or short positions when the HMA slope points in the trend direction during active market conditions.
Details
- Entry Criteria: Signals based on adaptive HMA, ATR or volume.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal.
- Stops: No.
- Default Values:
MinPeriod= 172MaxPeriod= 233AdaptPercent= 0.031mFlatThreshold= 0mUseVolume= falseCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: MA, ATR, Volume
- Stops: No
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy based on Hull Moving Average slope with ATR volatility filter.
/// Enters long when HMA slope is positive and volatility is expanding.
/// Enters short when HMA slope is negative and volatility is expanding.
/// </summary>
public class AdaptiveHmaPlusStrategy : Strategy
{
private readonly StrategyParam<int> _hmaLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private HullMovingAverage _hma;
private AverageTrueRange _atrShort;
private AverageTrueRange _atrLong;
private decimal _prevHma;
private int _cooldownRemaining;
public int HmaLength { get => _hmaLength.Value; set => _hmaLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public AdaptiveHmaPlusStrategy()
{
_hmaLength = Param(nameof(HmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("HMA Length", "Hull Moving Average period", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_hma = null;
_atrShort = null;
_atrLong = null;
_prevHma = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hma = new HullMovingAverage { Length = HmaLength };
_atrShort = new AverageTrueRange { Length = 14 };
_atrLong = new AverageTrueRange { Length = 46 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_hma, _atrShort, _atrLong, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _hma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal hmaValue, decimal atrShortValue, decimal atrLongValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevHma = hmaValue;
return;
}
if (_prevHma == 0)
{
_prevHma = hmaValue;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevHma = hmaValue;
return;
}
var slope = hmaValue - _prevHma;
var volExpanding = atrShortValue > atrLongValue;
// Buy: HMA slope positive + volatility expanding
if (slope > 0 && volExpanding && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: HMA slope negative + volatility expanding
else if (slope < 0 && volExpanding && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: slope turns negative
else if (Position > 0 && slope <= 0)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: slope turns positive
else if (Position < 0 && slope >= 0)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevHma = 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 adaptive_hma_plus_strategy(Strategy):
"""Adaptive HMA Plus Strategy."""
def __init__(self):
super(adaptive_hma_plus_strategy, self).__init__()
self._hma_length = self.Param("HmaLength", 20) \
.SetDisplay("HMA Length", "Hull Moving Average period", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prev_hma = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adaptive_hma_plus_strategy, self).OnReseted()
self._prev_hma = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adaptive_hma_plus_strategy, self).OnStarted2(time)
hma = HullMovingAverage()
hma.Length = int(self._hma_length.Value)
atr_short = AverageTrueRange()
atr_short.Length = 14
atr_long = AverageTrueRange()
atr_long.Length = 46
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(hma, atr_short, atr_long, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawOwnTrades(area)
def _on_process(self, candle, hma_value, atr_short_value, atr_long_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_hma = float(hma_value)
return
hma_v = float(hma_value)
if self._prev_hma == 0:
self._prev_hma = hma_v
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_hma = hma_v
return
slope = hma_v - self._prev_hma
vol_expanding = float(atr_short_value) > float(atr_long_value)
cooldown = int(self._cooldown_bars.Value)
if slope > 0 and vol_expanding and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif slope < 0 and vol_expanding and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and slope <= 0:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and slope >= 0:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_hma = hma_v
def CreateClone(self):
return adaptive_hma_plus_strategy()