Стратегия Hull Suite – риск 1%, без SL/TP
Стратегия Hull Suite открывает длинные позиции, когда выбранная скользящая средняя Халла растёт по сравнению со значением два бара назад, и открывает короткие позиции, когда она падает. Стоп‑лоссы и тейк‑профиты не используются.
Детали
- Условия входа:
- Лонг: значение Hull больше, чем два бара назад.
- Шорт: значение Hull меньше, чем два бара назад.
- Лонг/Шорт: обе стороны.
- Условия выхода: разворот позиции при противоположном сигнале.
- Стопы: нет.
- Значения по умолчанию:
HullLength= 55Mode= Hma
- Фильтры:
- Категория: следование тренду
- Направление: оба
- Индикаторы: HMA, EHMA, THMA
- Стопы: нет
- Сложность: низкая
- Таймфрейм: 5m
- Сезонность: нет
- Нейросети: нет
- Дивергенции: нет
- Уровень риска: низкий
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 1% risk, no SL/TP strategy. No stop loss or take profit is placed.
/// 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 = HullLength / 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 HullSuite1RiskNoSlTpStrategy : 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> _hullLength;
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 HullLength
{
get => _hullLength.Value;
set => _hullLength.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 HullSuite1RiskNoSlTpStrategy()
{
_hullLength = Param(nameof(HullLength), 55)
.SetGreaterThanZero()
.SetDisplay("Hull Length", "Hull average length", "Indicators");
_mode = Param(nameof(Mode), HullModes.Hma)
.SetDisplay("Mode", "Hull average variant", "Indicators");
_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();
_prev1 = null;
_prev2 = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prev1 = null;
_prev2 = null;
var length = HullLength;
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_1_risk_no_sl_tp_strategy(Strategy):
"""
Hull Suite 1% risk, no SL/TP strategy. No stop loss or take profit is placed.
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 = HullLength / 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_1_risk_no_sl_tp_strategy, self).__init__()
self._hull_length = self.Param("HullLength", 55).SetGreaterThanZero().SetDisplay("Hull 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(5))).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_1_risk_no_sl_tp_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_1_risk_no_sl_tp_strategy, self).OnStarted2(time)
self._prev1 = None
self._prev2 = None
length = int(self._hull_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_1_risk_no_sl_tp_strategy()