Hull Suite Strategy – 1% Risk, No SL/TP
The Hull Suite strategy opens long positions when the selected Hull moving average rises compared to two bars ago and opens short positions when it falls. No stop loss or take profit is used.
Details
- Entry Criteria:
- Long: Hull value greater than the value two bars ago.
- Short: Hull value less than the value two bars ago.
- Long/Short: Both sides.
- Exit Criteria: Reverse position on opposite signal.
- Stops: None.
- Default Values:
HullLength= 55Mode= Hma
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: HMA, EHMA, THMA
- Stops: None
- Complexity: Low
- Timeframe: 5m
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Low
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()