Стратегия 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= 9StochPeriod= 14StochK= 3StochD= 3CandleType= 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;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import HullMovingAverage, StochasticOscillator, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_stochastic_strategy(Strategy):
def __init__(self):
super(hull_ma_stochastic_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9) \
.SetDisplay("HMA Period", "Hull Moving Average period", "Indicators")
self._stoch_period = self.Param("StochPeriod", 14) \
.SetDisplay("Stochastic Period", "Stochastic oscillator period", "Indicators")
self._stoch_k = self.Param("StochK", 3) \
.SetDisplay("Stochastic %K", "Stochastic %K period", "Indicators")
self._stoch_d = self.Param("StochD", 3) \
.SetDisplay("Stochastic %D", "Stochastic %D period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 90) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
self._hma = None
self._stochastic = None
self._atr = None
self._prev_hma_value = 0.0
self._cooldown = 0
@property
def HmaPeriod(self):
return self._hma_period.Value
@HmaPeriod.setter
def HmaPeriod(self, value):
self._hma_period.Value = value
@property
def StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_period.Value = value
@property
def StochK(self):
return self._stoch_k.Value
@StochK.setter
def StochK(self, value):
self._stoch_k.Value = value
@property
def StochD(self):
return self._stoch_d.Value
@StochD.setter
def StochD(self, value):
self._stoch_d.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
def OnStarted2(self, time):
super(hull_ma_stochastic_strategy, self).OnStarted2(time)
self._prev_hma_value = 0.0
self._cooldown = 0
self._hma = HullMovingAverage()
self._hma.Length = self.HmaPeriod
self._stochastic = StochasticOscillator()
self._stochastic.K.Length = self.StochK
self._stochastic.D.Length = self.StochD
self._atr = AverageTrueRange()
self._atr.Length = 14
self.SubscribeCandles(self.CandleType) \
.BindEx(self._hma, self._stochastic, self._atr, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, hma_value, stoch_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
hma_f = float(hma_value)
stoch_k = stoch_value.K
if stoch_k is None:
return
stoch_k_f = float(stoch_k)
if self._prev_hma_value == 0:
self._prev_hma_value = hma_f
return
hma_increasing = hma_f > self._prev_hma_value
hma_decreasing = hma_f < self._prev_hma_value
cooldown_bars = int(self.CooldownBars)
if self._cooldown > 0:
self._cooldown -= 1
self._prev_hma_value = hma_f
return
if hma_increasing and stoch_k_f > 50 and self.Position == 0:
self.BuyMarket()
self._cooldown = cooldown_bars
elif hma_decreasing and stoch_k_f < 50 and self.Position == 0:
self.SellMarket()
self._cooldown = cooldown_bars
elif self.Position > 0 and hma_decreasing:
self.SellMarket()
self._cooldown = cooldown_bars
elif self.Position < 0 and hma_increasing:
self.BuyMarket()
self._cooldown = cooldown_bars
self._prev_hma_value = hma_f
def OnReseted(self):
super(hull_ma_stochastic_strategy, self).OnReseted()
self._hma = None
self._stochastic = None
self._atr = None
self._prev_hma_value = 0.0
self._cooldown = 0
def CreateClone(self):
return hull_ma_stochastic_strategy()