GitHub で見る

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/Short: Both
  • Exit Criteria: Hull MA reversal
  • Stops: ATR-based using AtrMultiplier
  • Default Values:
    • HmaPeriod = 9
    • AdxPeriod = 14
    • AtrMultiplier = 2m
    • CandleType = 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;
	}
}