Стратегия Hull Suite by MRS
Следящая за трендом стратегия сравнивает выбранную разновидность скользящей средней Hull со значением два бара назад. Длинная позиция открывается, когда средняя поднимается выше значения двух баров назад, короткая — когда опускается ниже.
Подробности
- Условия входа:
- Long:
MA > MA[2]. - Short:
MA < MA[2].
- Long:
- Направление: Обе стороны.
- Условия выхода: Реверс при противоположном сигнале.
- Стопы: Нет.
- Значения по умолчанию:
Length= 55Mode= Hma
- Фильтры:
- Категория: Следование тренду
- Направление: Обе
- Индикаторы: Hull MA
- Стопы: Нет
- Сложность: Простая
- Таймфрейм: Любой
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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 Suite by MRS strategy.
/// Computes the Hull-type average selected by Mode on the close: HMA = WMA(2*WMA(n/2) - WMA(n), sqrt(n)), EHMA is the same with
/// EMAs, and THMA = WMA(3*WMA(m/3) - WMA(m/2) - WMA(m), m) with m = Length / 2. The strategy goes long when the average is above
/// its value two bars ago and short when it is below, reversing an opposite position.
/// </summary>
public class HullSuiteByMRSStrategy : Strategy
{
/// <summary>
/// Hull average variants.
/// </summary>
public enum HullModes
{
/// <summary>
/// Hull moving average.
/// </summary>
Hma,
/// <summary>
/// Exponential Hull moving average.
/// </summary>
Ehma,
/// <summary>
/// Triple Hull moving average.
/// </summary>
Thma,
}
private readonly StrategyParam<int> _length;
private readonly StrategyParam<HullModes> _mode;
private readonly StrategyParam<DataType> _candleType;
private IIndicator _first;
private IIndicator _second;
private IIndicator _third;
private IIndicator _smooth;
private decimal? _prev1;
private decimal? _prev2;
/// <summary>
/// Hull average length.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Hull average variant.
/// </summary>
public HullModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HullSuiteByMRSStrategy()
{
_length = Param(nameof(Length), 55)
.SetGreaterThanZero()
.SetDisplay("Length", "Hull average length", "Indicators");
_mode = Param(nameof(Mode), HullModes.Hma)
.SetDisplay("Mode", "Hull average variant", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
_prev1 = null;
_prev2 = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prev1 = null;
_prev2 = null;
var length = Length;
switch (Mode)
{
case HullModes.Ehma:
_first = new ExponentialMovingAverage { Length = Math.Max(1, length / 2) };
_second = new ExponentialMovingAverage { Length = length };
_third = null;
_smooth = new ExponentialMovingAverage { Length = Math.Max(1, (int)Math.Round(Math.Sqrt(length))) };
break;
case HullModes.Thma:
{
var half = Math.Max(1, length / 2);
_first = new WeightedMovingAverage { Length = Math.Max(1, half / 3) };
_second = new WeightedMovingAverage { Length = Math.Max(1, half / 2) };
_third = new WeightedMovingAverage { Length = half };
_smooth = new WeightedMovingAverage { Length = half };
break;
}
default:
_first = new WeightedMovingAverage { Length = Math.Max(1, length / 2) };
_second = new WeightedMovingAverage { Length = length };
_third = null;
_smooth = new WeightedMovingAverage { Length = Math.Max(1, (int)Math.Round(Math.Sqrt(length))) };
break;
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _smooth);
DrawOwnTrades(area);
}
}
private static decimal ProcessValue(IIndicator indicator, decimal input, DateTime time)
{
return indicator.Process(new DecimalIndicatorValue(indicator, input, time) { IsFinal = true }).GetValue<decimal>();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var time = candle.OpenTime;
var first = ProcessValue(_first, close, time);
var second = ProcessValue(_second, close, time);
var third = _third != null ? ProcessValue(_third, close, time) : 0m;
if (!_first.IsFormed || !_second.IsFormed || (_third != null && !_third.IsFormed))
return;
var raw = _third != null
? 3m * first - second - third
: 2m * first - second;
var ma = ProcessValue(_smooth, raw, time);
if (!_smooth.IsFormed)
return;
var maTwoBarsAgo = _prev2;
_prev2 = _prev1;
_prev1 = ma;
if (maTwoBarsAgo is not decimal older)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (ma > older && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (ma < older && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import WeightedMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
MODE_HMA = 0
MODE_EHMA = 1
MODE_THMA = 2
class hull_suite_by_mrs_strategy(Strategy):
"""
Hull Suite by MRS strategy.
Computes the Hull-type average selected by Mode on the close: HMA = WMA(2*WMA(n/2) - WMA(n), sqrt(n)), EHMA is the same with
EMAs, and THMA = WMA(3*WMA(m/3) - WMA(m/2) - WMA(m), m) with m = Length / 2. The strategy goes long when the average is above
its value two bars ago and short when it is below, reversing an opposite position.
"""
def __init__(self):
super(hull_suite_by_mrs_strategy, self).__init__()
self._length = self.Param("Length", 55).SetGreaterThanZero().SetDisplay("Length", "Hull average length", "Indicators")
self._mode = self.Param("Mode", MODE_HMA).SetDisplay("Mode", "0 Hma, 1 Ehma, 2 Thma", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._first = None
self._second = None
self._third = None
self._smooth = None
self._prev1 = None
self._prev2 = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hull_suite_by_mrs_strategy, self).OnReseted()
self._prev1 = None
self._prev2 = None
@staticmethod
def _make(cls, length):
indicator = cls()
indicator.Length = max(1, length)
return indicator
def OnStarted2(self, time):
super(hull_suite_by_mrs_strategy, self).OnStarted2(time)
self._prev1 = None
self._prev2 = None
length = int(self._length.Value)
mode = int(self._mode.Value)
sqrt_length = int(round(math.sqrt(length)))
if mode == MODE_EHMA:
self._first = self._make(ExponentialMovingAverage, length // 2)
self._second = self._make(ExponentialMovingAverage, length)
self._third = None
self._smooth = self._make(ExponentialMovingAverage, sqrt_length)
elif mode == MODE_THMA:
half = max(1, length // 2)
self._first = self._make(WeightedMovingAverage, half // 3)
self._second = self._make(WeightedMovingAverage, half // 2)
self._third = self._make(WeightedMovingAverage, half)
self._smooth = self._make(WeightedMovingAverage, half)
else:
self._first = self._make(WeightedMovingAverage, length // 2)
self._second = self._make(WeightedMovingAverage, length)
self._third = None
self._smooth = self._make(WeightedMovingAverage, sqrt_length)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._smooth)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
t = candle.OpenTime
first = float(process_float(self._first, close, t, True))
second = float(process_float(self._second, close, t, True))
third = float(process_float(self._third, close, t, True)) if self._third is not None else 0.0
if not self._first.IsFormed or not self._second.IsFormed or (self._third is not None and not self._third.IsFormed):
return
if self._third is not None:
raw = 3.0 * first - second - third
else:
raw = 2.0 * first - second
ma = float(process_float(self._smooth, Decimal(raw), t, True))
if not self._smooth.IsFormed:
return
ma_two_bars_ago = self._prev2
self._prev2 = self._prev1
self._prev1 = ma
if ma_two_bars_ago is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if ma > ma_two_bars_ago and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif ma < ma_two_bars_ago and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return hull_suite_by_mrs_strategy()