Стратегия Hull Ma Stochastic
Стратегия основана на скользящей средней Халла и осцилляторе Стохастик. Вход выполняется при смене направления HMA, когда Стохастик подтверждает перепроданность или перекупленность.
Тестирование показывает среднегодичную доходность около 94%. Стратегию лучше запускать на фондовом рынке.
Hull MA быстро показывает направление тренда. Стохастик ждёт откат или всплеск внутри этого тренда, чтобы открыть сделку.
Гибкий подход для тех, кто предпочитает плавные сигналы. Стопы по ATR ограничивают возможные потери.
Подробности
- Условия входа:
- Длинная:
HullMA turning up && StochK < StochOversold - Короткая:
HullMA turning down && StochK > StochOverbought
- Длинная:
- Long/Short: Оба
- Условия выхода:
- изменение направления Hull MA
- Стопы: основаны на ATR через
StopLossAtr - Параметры по умолчанию:
HmaPeriod= 9StochPeriod= 14StochK= 3StochOversold= 20StochOverbought= 80CandleType= TimeSpan.FromMinutes(5).TimeFrame()StopLossAtr= 2AtrPeriod= 14 HMA разворачивается вверх, когда растёт после снижения, и вниз, когда снижается после роста; лонг закрывается при её снижении, шорт — при росте. %K — стохастик за StochPeriod свечей, сглаженный за StochK свечей, линия %D не участвует. Стоп стоит в StopLossAtr ATR (AtrPeriod) от цены закрытия при входе и проверяется по закрытиям свечей. Сигнал на вход против открытой позиции разворачивает её.
- Фильтры:
- Категория: Mean reversion
- Направление: Оба
- Индикаторы: Hull MA, Moving Average, Stochastic Oscillator
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Hull MA Stochastic strategy.
/// The Hull average turns up when it rises after falling and turns down when it falls after rising. A turn up with %K below StochOversold
/// goes long and a turn down with %K above StochOverbought goes short, reversing an opposite position. A long closes when the Hull average
/// falls and a short when it rises. %K is the stochastic over StochPeriod candles
/// smoothed over StochK candles. The stop lies StopLossAtr ATR from the entry close and is checked on candle closes.
/// </summary>
public class HullMaStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<decimal> _stopLossAtr;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevHull;
private decimal? _prevPrevHull;
private decimal _stopPrice;
/// <summary>
/// Period of the Hull moving average.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// Lookback period of the raw stochastic.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Smoothing period of %K.
/// </summary>
public int StochK
{
get => _stochK.Value;
set => _stochK.Value = value;
}
/// <summary>
/// %K level for longs.
/// </summary>
public decimal StochOversold
{
get => _stochOversold.Value;
set => _stochOversold.Value = value;
}
/// <summary>
/// %K level for shorts.
/// </summary>
public decimal StochOverbought
{
get => _stochOverbought.Value;
set => _stochOverbought.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal StopLossAtr
{
get => _stopLossAtr.Value;
set => _stopLossAtr.Value = value;
}
/// <summary>
/// Period of the stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HullMaStochasticStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("HMA Period", "Period of the Hull moving average", "Indicators");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Lookback period of the raw stochastic", "Stochastic");
_stochK = Param(nameof(StochK), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "Smoothing period of %K", "Stochastic");
_stochOversold = Param(nameof(StochOversold), 20m)
.SetDisplay("Stochastic Oversold", "%K level for longs", "Stochastic");
_stochOverbought = Param(nameof(StochOverbought), 80m)
.SetDisplay("Stochastic Overbought", "%K level for shorts", "Stochastic");
_stopLossAtr = Param(nameof(StopLossAtr), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the stop ATR", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHull = null;
_prevPrevHull = null;
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHull = null;
_prevPrevHull = null;
_stopPrice = default;
var hull = new HullMovingAverage { Length = HmaPeriod };
// The D line of the core oscillator is the smoothed %K.
var stochastic = new StochasticOscillator
{
K = { Length = StochPeriod },
D = { Length = StochK },
};
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(hull, stochastic, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hull);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, stochastic);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hullValue, IIndicatorValue stochasticValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!hullValue.IsFormed || !stochasticValue.IsFormed || !atrValue.IsFormed)
return;
if (stochasticValue is not IStochasticOscillatorValue { D: decimal k })
return;
var hull = hullValue.GetValue<decimal>();
var prevHull = _prevHull;
var prevPrevHull = _prevPrevHull;
_prevPrevHull = prevHull;
_prevHull = hull;
if (prevHull is not decimal last || prevPrevHull is not decimal beforeLast)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
var rising = hull > last;
var falling = hull < last;
var turnsUp = rising && last < beforeLast;
var turnsDown = falling && last > beforeLast;
if (turnsUp && k < StochOversold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - StopLossAtr * atr;
}
else if (turnsDown && k > StochOverbought && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + StopLossAtr * atr;
}
else if (Position > 0 && (falling || (StopLossAtr > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (rising || (StopLossAtr > 0 && close >= _stopPrice)))
{
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import HullMovingAverage, StochasticOscillator, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_stochastic_strategy(Strategy):
"""
Hull MA Stochastic strategy.
The Hull average turns up when it rises after falling and turns down when it falls after rising. A turn up with %K below StochOversold
goes long and a turn down with %K above StochOverbought goes short, reversing an opposite position. A long closes when the Hull average
falls and a short when it rises. %K is the stochastic over StochPeriod candles
smoothed over StochK candles. The stop lies StopLossAtr ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(hull_ma_stochastic_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9).SetGreaterThanZero().SetDisplay("HMA Period", "Period of the Hull moving average", "Indicators")
self._stoch_period = self.Param("StochPeriod", 14).SetGreaterThanZero().SetDisplay("Stochastic Period", "Lookback period of the raw stochastic", "Stochastic")
self._stoch_k = self.Param("StochK", 3).SetGreaterThanZero().SetDisplay("Stochastic %K", "Smoothing period of %K", "Stochastic")
self._stoch_oversold = self.Param("StochOversold", 20.0).SetDisplay("Stochastic Oversold", "%K level for longs", "Stochastic")
self._stoch_overbought = self.Param("StochOverbought", 80.0).SetDisplay("Stochastic Overbought", "%K level for shorts", "Stochastic")
self._stop_loss_atr = self.Param("StopLossAtr", 2.0).SetNotNegative().SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "Risk")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the stop ATR", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_hull = None
self._prev_prev_hull = None
self._stop_price = Decimal(0)
def OnReseted(self):
super(hull_ma_stochastic_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(hull_ma_stochastic_strategy, self).OnStarted2(time)
self._reset_state()
hull = HullMovingAverage()
hull.Length = self._hma_period.Value
# The D line of the core oscillator is the smoothed %K.
stochastic = StochasticOscillator()
stochastic.K.Length = self._stoch_period.Value
stochastic.D.Length = self._stoch_k.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(hull, stochastic, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hull)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, stochastic)
def _process_candle(self, candle, hull_value, stochastic_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not hull_value.IsFormed or not stochastic_value.IsFormed or not atr_value.IsFormed or stochastic_value.D is None:
return
hull = hull_value.GetValue[Decimal](None)
prev_hull = self._prev_hull
prev_prev_hull = self._prev_prev_hull
self._prev_prev_hull = prev_hull
self._prev_hull = hull
if prev_hull is None or prev_prev_hull is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
k = stochastic_value.D
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
rising = hull > prev_hull
falling = hull < prev_hull
turns_up = rising and prev_hull < prev_prev_hull
turns_down = falling and prev_hull > prev_prev_hull
stop_atr = Decimal(self._stop_loss_atr.Value)
if turns_up and k < Decimal(self._stoch_oversold.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif turns_down and k > Decimal(self._stoch_overbought.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (falling or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (rising or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return hull_ma_stochastic_strategy()