Открыть на GitHub

Стратегия Hull Ma Stochastic

Стратегия основана на скользящей средней Халла и осцилляторе Стохастик. Вход выполняется при смене направления HMA, когда Стохастик подтверждает перепроданность или перекупленность.

Тестирование показывает среднегодичную доходность около 94%. Стратегию лучше запускать на фондовом рынке.

Hull MA быстро показывает направление тренда. Стохастик ждёт откат или всплеск внутри этого тренда, чтобы открыть сделку.

Гибкий подход для тех, кто предпочитает плавные сигналы. Стопы по ATR ограничивают возможные потери.

Подробности

  • Условия входа:
    • Длинная: HullMA turning up && StochK < 20
    • Короткая: HullMA turning down && StochK > 80
  • Long/Short: Оба
  • Условия выхода:
    • изменение направления Hull MA
  • Стопы: основаны на ATR через StopLossAtr
  • Параметры по умолчанию:
    • HmaPeriod = 9
    • StochPeriod = 14
    • StochK = 3
    • StochD = 3
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
    • StopLossAtr = 2m
  • Фильтры:
    • Категория: Mean reversion
    • Направление: Оба
    • Индикаторы: Hull MA, Moving Average, Stochastic Oscillator
    • Стопы: Да
    • Сложность: Средняя
    • Таймфрейм: Среднесрочный
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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;
	}
}