Hull MA CCI Strategy
This strategy uses Hull MA CCI indicators to generate signals. Long entry occurs when HMA(t) > HMA(t-1) && CCI < CciOversold (HMA rising with oversold conditions). Short entry occurs when HMA(t) < HMA(t-1) && CCI > CciOverbought (HMA falling with overbought conditions). It is suitable for traders seeking opportunities in mixed markets.
Testing indicates an average annual return of about 52%. It performs best in the crypto market.
Details
- Entry Criteria:
- Long: HMA(t) > HMA(t-1) && CCI < CciOversold (HMA rising with oversold conditions)
- Short: HMA(t) < HMA(t-1) && CCI > CciOverbought (HMA falling with overbought conditions)
- Long/Short: Both sides.
- Exit Criteria:
- Long: Exit long position when HMA starts falling
- Short: Exit short position when HMA starts rising
- Stops: Yes.
- Default Values:
HullPeriod= 9CciPeriod= 20CciOversold= -100CciOverbought= 100AtrPeriod= 14AtrMultiplier= 2 -100 and 100 are the defaults of the CCI levels the rules quote. The stop lies AtrMultiplier times the AtrPeriod ATR from the entry close and is checked on candle closes; 0 disables it. An entry signal against an open position reverses it.CandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Mixed
- Direction: Both
- Indicators: Hull MA CCI
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk Level: Medium
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 CCI strategy.
/// A rising HullPeriod Hull moving average with CCI below CciOversold goes long and a falling one with CCI above CciOverbought goes short,
/// reversing an opposite position. A long closes once the Hull MA starts falling and a short once it starts rising. The stop lies
/// AtrMultiplier ATR from the entry close and is checked on candle closes.
/// </summary>
public class HullMaCciStrategy : Strategy
{
private readonly StrategyParam<int> _hullPeriod;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _cciOversold;
private readonly StrategyParam<decimal> _cciOverbought;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevHull;
private decimal _stopPrice;
/// <summary>
/// Period of the Hull moving average.
/// </summary>
public int HullPeriod
{
get => _hullPeriod.Value;
set => _hullPeriod.Value = value;
}
/// <summary>
/// Period of CCI.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// CCI level for longs.
/// </summary>
public decimal CciOversold
{
get => _cciOversold.Value;
set => _cciOversold.Value = value;
}
/// <summary>
/// CCI level for shorts.
/// </summary>
public decimal CciOverbought
{
get => _cciOverbought.Value;
set => _cciOverbought.Value = value;
}
/// <summary>
/// Period of the stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HullMaCciStrategy()
{
_hullPeriod = Param(nameof(HullPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Hull Period", "Period of the Hull moving average", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Period of CCI", "Indicators");
_cciOversold = Param(nameof(CciOversold), -100m)
.SetDisplay("CCI Oversold", "CCI level for longs", "Indicators");
_cciOverbought = Param(nameof(CciOverbought), 100m)
.SetDisplay("CCI Overbought", "CCI level for shorts", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the stop ATR", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "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;
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHull = null;
_stopPrice = default;
var hull = new HullMovingAverage { Length = HullPeriod };
var cci = new CommodityChannelIndex { Length = CciPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(hull, cci, 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, cci);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hullValue, IIndicatorValue cciValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!hullValue.IsFormed)
return;
var hull = hullValue.GetValue<decimal>();
var prevHull = _prevHull;
_prevHull = hull;
if (prevHull is not decimal previous || !cciValue.IsFormed || !atrValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var cci = cciValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var rising = hull > previous;
var falling = hull < previous;
var close = candle.ClosePrice;
if (rising && cci < CciOversold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - AtrMultiplier * atr;
}
else if (falling && cci > CciOverbought && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + AtrMultiplier * atr;
}
else if (Position > 0 && (falling || (AtrMultiplier > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (rising || (AtrMultiplier > 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, CommodityChannelIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_cci_strategy(Strategy):
"""
Hull MA CCI strategy.
A rising HullPeriod Hull moving average with CCI below CciOversold goes long and a falling one with CCI above CciOverbought goes short,
reversing an opposite position. A long closes once the Hull MA starts falling and a short once it starts rising. The stop lies
AtrMultiplier ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(hull_ma_cci_strategy, self).__init__()
self._hull_period = self.Param("HullPeriod", 9).SetGreaterThanZero().SetDisplay("Hull Period", "Period of the Hull moving average", "Indicators")
self._cci_period = self.Param("CciPeriod", 20).SetGreaterThanZero().SetDisplay("CCI Period", "Period of CCI", "Indicators")
self._cci_oversold = self.Param("CciOversold", -100.0).SetDisplay("CCI Oversold", "CCI level for longs", "Indicators")
self._cci_overbought = self.Param("CciOverbought", 100.0).SetDisplay("CCI Overbought", "CCI level for shorts", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the stop ATR", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "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._stop_price = Decimal(0)
def OnReseted(self):
super(hull_ma_cci_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(hull_ma_cci_strategy, self).OnStarted2(time)
self._reset_state()
hull = HullMovingAverage()
hull.Length = self._hull_period.Value
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(hull, cci, 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, cci)
def _process_candle(self, candle, hull_value, cci_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not hull_value.IsFormed:
return
hull = hull_value.GetValue[Decimal](None)
previous = self._prev_hull
self._prev_hull = hull
if previous is None or not cci_value.IsFormed or not atr_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
cci = cci_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
rising = hull > previous
falling = hull < previous
close = candle.ClosePrice
stop_atr = Decimal(self._atr_multiplier.Value)
if rising and cci < Decimal(self._cci_oversold.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif falling and cci > Decimal(self._cci_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_cci_strategy()