Auf GitHub ansehen

Strategie Hull Ma Stochastic

Hull Moving Average + Stochastic Oscillator Strategie. Die Strategie tritt ein, wenn sich die HMA-Trendrichtung ändert und der Stochastic überverkaufte/überkaufte Bedingungen bestätigt.

Tests zeigen eine durchschnittliche Jahresrendite von etwa 94%. Die Strategie funktioniert am besten auf dem Aktienmarkt.

Der Hull MA zeigt die Trendrichtung schnell auf. Der Stochastic wartet auf einen Rückgang oder eine Erholung innerhalb dieses Trends, um den Trade auszulösen.

Ein flexibler Ansatz für Trader, die glatte Signale bevorzugen. ATR-basierte Stops begrenzen den potenziellen Verlust.

Details

  • Einstiegskriterien:
    • Long: HullMA turning up && StochK < 20
    • Short: HullMA turning down && StochK > 80
  • Long/Short: Beide
  • Ausstiegskriterien:
    • Hull MA Richtungswechsel
  • Stops: ATR-basiert mit StopLossAtr
  • Standardwerte:
    • HmaPeriod = 9
    • StochPeriod = 14
    • StochK = 3
    • StochD = 3
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
    • StopLossAtr = 2m
  • Filter:
    • Kategorie: Mean Reversion
    • Richtung: Beide
    • Indikatoren: Hull MA, Moving Average, Stochastic Oscillator
    • Stops: Ja
    • Komplexität: Mittel
    • Zeitrahmen: Mittelfristig
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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>
/// Hull Moving Average + Stochastic Oscillator strategy.
/// Strategy enters when HMA trend direction changes with Stochastic confirming oversold/overbought conditions.
/// </summary>
public class HullMaStochasticStrategy : Strategy
{
	private readonly StrategyParam<int> _hmaPeriod;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<int> _stochK;
	private readonly StrategyParam<int> _stochD;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;

	// Indicators
	private HullMovingAverage _hma;
	private StochasticOscillator _stochastic;
	private AverageTrueRange _atr;
	private int _cooldown;

	// Previous HMA value for trend detection
	private decimal _prevHmaValue;

	/// <summary>
	/// Hull Moving Average period.
	/// </summary>
	public int HmaPeriod
	{
		get => _hmaPeriod.Value;
		set => _hmaPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic period.
	/// </summary>
	public int StochPeriod
	{
		get => _stochPeriod.Value;
		set => _stochPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K period.
	/// </summary>
	public int StochK
	{
		get => _stochK.Value;
		set => _stochK.Value = value;
	}

	/// <summary>
	/// Stochastic %D period.
	/// </summary>
	public int StochD
	{
		get => _stochD.Value;
		set => _stochD.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Stop-loss percentage.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public HullMaStochasticStrategy()
	{
		_hmaPeriod = Param(nameof(HmaPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("HMA Period", "Hull Moving Average period", "Indicators")
			
			.SetOptimize(4, 30, 2);

		_stochPeriod = Param(nameof(StochPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Period", "Stochastic oscillator period", "Indicators")
			
			.SetOptimize(5, 30, 5);

		_stochK = Param(nameof(StochK), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %K", "Stochastic %K period", "Indicators")
			
			.SetOptimize(1, 10, 1);

		_stochD = Param(nameof(StochD), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %D", "Stochastic %D period", "Indicators")
			
			.SetOptimize(1, 10, 1);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_cooldownBars = Param(nameof(CooldownBars), 90)
			.SetRange(5, 500)
			.SetDisplay("Cooldown Bars", "Bars between trades", "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 sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_hma = null;
		_stochastic = null;
		_atr = null;
		_prevHmaValue = 0;
		_cooldown = 0;
	}

/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
	base.OnStarted2(time);

	// Create indicators
	_hma = new HullMovingAverage { Length = HmaPeriod };

		_stochastic = new StochasticOscillator
		{
			K = { Length = StochK },
			D = { Length = StochD },
		};

		_atr = new AverageTrueRange { Length = 14 };

		// Subscribe to candles and bind indicators
		var subscription = SubscribeCandles(CandleType);
		
		subscription
			.BindEx(_hma, _stochastic, _atr, ProcessCandle)
			.Start();

		// Setup chart
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _hma);
			
			var secondArea = CreateChartArea();
			if (secondArea != null)
			{
				DrawIndicator(secondArea, _stochastic);
			}
			
			DrawOwnTrades(area);
		}

	}

	private void ProcessCandle(
		ICandleMessage candle, 
		IIndicatorValue hmaValue, 
		IIndicatorValue stochValue, 
		IIndicatorValue atrValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;

		// Check if strategy is ready to trade
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Get indicator values
		decimal hma = hmaValue.ToDecimal();
		
		var stochTyped = (StochasticOscillatorValue)stochValue;

		if (stochTyped.K is not decimal stochK)
			return;

		decimal atr = atrValue.ToDecimal();

		// Skip first candle after initialization
		if (_prevHmaValue == 0)
		{
			_prevHmaValue = hma;
			return;
		}

		// Detect HMA trend direction
		bool hmaIncreasing = hma > _prevHmaValue;
		bool hmaDecreasing = hma < _prevHmaValue;

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevHmaValue = hma;
			return;
		}

		// Trading logic:
		// Buy/short by HMA slope with a light stochastic filter.
		if (hmaIncreasing && stochK > 50 && Position == 0)
		{
			BuyMarket();
			_cooldown = CooldownBars;
			LogInfo($"Long entry: Price={candle.ClosePrice}, HMA={hma}, Prev HMA={_prevHmaValue}, Stochastic %K={stochK}");
		}
		// Sell when HMA is decreasing and stochastic confirms bearish momentum.
		else if (hmaDecreasing && stochK < 50 && Position == 0)
		{
			SellMarket();
			_cooldown = CooldownBars;
			LogInfo($"Short entry: Price={candle.ClosePrice}, HMA={hma}, Prev HMA={_prevHmaValue}, Stochastic %K={stochK}");
		}
		// Exit when HMA trend changes direction
		else if (Position > 0 && hmaDecreasing)
		{
			SellMarket();
			_cooldown = CooldownBars;
			LogInfo($"Long exit: Price={candle.ClosePrice}, HMA={hma}, Prev HMA={_prevHmaValue}");
		}
		else if (Position < 0 && hmaIncreasing)
		{
			BuyMarket();
			_cooldown = CooldownBars;
			LogInfo($"Short exit: Price={candle.ClosePrice}, HMA={hma}, Prev HMA={_prevHmaValue}");
		}

		// Save current HMA value for next candle
		_prevHmaValue = hma;
	}
}