Double CCI Confirmed Hull MA Reversal 策略
当价格向上穿越Hull移动平均线并且快慢CCI均大于0时,该策略做多。ATR触发后使用EMA进行移动止盈。
测试显示该策略具有中等年化收益,在混合市场表现最佳。
细节
- 入场条件:
- 多头: 价格向上穿越HMA,收盘价高于HMA,快CCI > 0,慢CCI > 0
- 多空方向: 仅多头
- 离场条件:
- 多头: 向下跌破触发后的EMA或最低价触及ATR止损
- 止损: 是
- 默认值:
StopLossAtrMultiplier= 1.75TrailingActivationMultiplier= 2.25FastCciPeriod= 25SlowCciPeriod= 50HullMaLength= 34TrailingEmaLength= 20AtrPeriod= 14CandleType= TimeSpan.FromMinutes(5)
- 过滤器:
- 类别: Reversal
- 方向: Long
- 指标: CCI, HMA, EMA, ATR
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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>
/// Double CCI Confirmed Hull MA Reversal strategy.
/// Long only: buys when the close crosses above the Hull Moving Average while both the fast and the slow CCI are above zero.
/// At entry the stop is set StopLossAtrMultiplier ATRs below the close and the trailing activation level TrailingActivationMultiplier
/// ATRs above it. The long closes when a candle low reaches the stop, or, once a high has reached the activation level, when the
/// close falls below the trailing EMA.
/// </summary>
public class DoubleCciConfirmedHullMovingAverageReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _trailingActivationMultiplier;
private readonly StrategyParam<int> _fastCciPeriod;
private readonly StrategyParam<int> _slowCciPeriod;
private readonly StrategyParam<int> _hullMaLength;
private readonly StrategyParam<int> _trailingEmaLength;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevClose;
private decimal? _prevHma;
private decimal _stopPrice;
private decimal _activationPrice;
private bool _trailingActive;
/// <summary>
/// Stop distance in ATRs below the entry close.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// Profit in ATRs that activates the trailing EMA exit.
/// </summary>
public decimal TrailingActivationMultiplier
{
get => _trailingActivationMultiplier.Value;
set => _trailingActivationMultiplier.Value = value;
}
/// <summary>
/// Fast CCI period.
/// </summary>
public int FastCciPeriod
{
get => _fastCciPeriod.Value;
set => _fastCciPeriod.Value = value;
}
/// <summary>
/// Slow CCI period.
/// </summary>
public int SlowCciPeriod
{
get => _slowCciPeriod.Value;
set => _slowCciPeriod.Value = value;
}
/// <summary>
/// Hull Moving Average period.
/// </summary>
public int HullMaLength
{
get => _hullMaLength.Value;
set => _hullMaLength.Value = value;
}
/// <summary>
/// Trailing EMA period.
/// </summary>
public int TrailingEmaLength
{
get => _trailingEmaLength.Value;
set => _trailingEmaLength.Value = value;
}
/// <summary>
/// ATR period.
/// </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 DoubleCciConfirmedHullMovingAverageReversalStrategy()
{
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 1.75m)
.SetGreaterThanZero()
.SetDisplay("Stop ATR Mult", "Stop distance in ATRs below the entry close", "Risk");
_trailingActivationMultiplier = Param(nameof(TrailingActivationMultiplier), 2.25m)
.SetGreaterThanZero()
.SetDisplay("Trailing Activation Mult", "Profit in ATRs that activates the trailing EMA exit", "Risk");
_fastCciPeriod = Param(nameof(FastCciPeriod), 25)
.SetGreaterThanZero()
.SetDisplay("Fast CCI", "Fast CCI period", "Indicators");
_slowCciPeriod = Param(nameof(SlowCciPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow CCI", "Slow CCI period", "Indicators");
_hullMaLength = Param(nameof(HullMaLength), 34)
.SetGreaterThanZero()
.SetDisplay("Hull MA Length", "Hull Moving Average period", "Indicators");
_trailingEmaLength = Param(nameof(TrailingEmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Trailing EMA", "Trailing EMA period", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "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();
ResetState();
}
private void ResetState()
{
_prevClose = null;
_prevHma = null;
_stopPrice = 0m;
_activationPrice = 0m;
_trailingActive = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var hma = new HullMovingAverage { Length = HullMaLength };
var fastCci = new CommodityChannelIndex { Length = FastCciPeriod };
var slowCci = new CommodityChannelIndex { Length = SlowCciPeriod };
var trailingEma = new ExponentialMovingAverage { Length = TrailingEmaLength };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(hma, fastCci, slowCci, trailingEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hma);
DrawIndicator(area, trailingEma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, fastCci);
DrawIndicator(oscillators, slowCci);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hmaValue, IIndicatorValue fastCciValue, IIndicatorValue slowCciValue, IIndicatorValue emaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!hmaValue.IsFormed || !fastCciValue.IsFormed || !slowCciValue.IsFormed || !emaValue.IsFormed || !atrValue.IsFormed)
return;
var hma = hmaValue.GetValue<decimal>();
var fastCci = fastCciValue.GetValue<decimal>();
var slowCci = slowCciValue.GetValue<decimal>();
var trailingEma = emaValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
var prevClose = _prevClose;
var prevHma = _prevHma;
_prevClose = close;
_prevHma = hma;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if (candle.LowPrice <= _stopPrice)
{
SellMarket(Position);
return;
}
if (candle.HighPrice >= _activationPrice)
_trailingActive = true;
if (_trailingActive && close < trailingEma)
SellMarket(Position);
return;
}
if (prevClose is not decimal pc || prevHma is not decimal ph)
return;
var crossUp = pc <= ph && close > hma;
if (Position == 0 && crossUp && fastCci > 0 && slowCci > 0)
{
_stopPrice = close - atr * StopLossAtrMultiplier;
_activationPrice = close + atr * TrailingActivationMultiplier;
_trailingActive = false;
BuyMarket(Volume);
}
}
}
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, ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class double_cci_confirmed_hull_moving_average_reversal_strategy(Strategy):
"""
Double CCI Confirmed Hull MA Reversal strategy.
Long only: buys when the close crosses above the Hull Moving Average while both the fast and the slow CCI are above zero.
At entry the stop is set StopLossAtrMultiplier ATRs below the close and the trailing activation level TrailingActivationMultiplier
ATRs above it. The long closes when a candle low reaches the stop, or, once a high has reached the activation level, when the
close falls below the trailing EMA.
"""
def __init__(self):
super(double_cci_confirmed_hull_moving_average_reversal_strategy, self).__init__()
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 1.75).SetGreaterThanZero().SetDisplay("Stop ATR Mult", "Stop distance in ATRs below the entry close", "Risk")
self._trailing_activation_multiplier = self.Param("TrailingActivationMultiplier", 2.25).SetGreaterThanZero().SetDisplay("Trailing Activation Mult", "Profit in ATRs that activates the trailing EMA exit", "Risk")
self._fast_cci_period = self.Param("FastCciPeriod", 25).SetGreaterThanZero().SetDisplay("Fast CCI", "Fast CCI period", "Indicators")
self._slow_cci_period = self.Param("SlowCciPeriod", 50).SetGreaterThanZero().SetDisplay("Slow CCI", "Slow CCI period", "Indicators")
self._hull_ma_length = self.Param("HullMaLength", 34).SetGreaterThanZero().SetDisplay("Hull MA Length", "Hull Moving Average period", "Indicators")
self._trailing_ema_length = self.Param("TrailingEmaLength", 20).SetGreaterThanZero().SetDisplay("Trailing EMA", "Trailing EMA period", "Risk")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "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_close = None
self._prev_hma = None
self._stop_price = Decimal(0)
self._activation_price = Decimal(0)
self._trailing_active = False
def OnReseted(self):
super(double_cci_confirmed_hull_moving_average_reversal_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(double_cci_confirmed_hull_moving_average_reversal_strategy, self).OnStarted2(time)
self._reset_state()
hma = HullMovingAverage()
hma.Length = self._hull_ma_length.Value
fast_cci = CommodityChannelIndex()
fast_cci.Length = self._fast_cci_period.Value
slow_cci = CommodityChannelIndex()
slow_cci.Length = self._slow_cci_period.Value
trailing_ema = ExponentialMovingAverage()
trailing_ema.Length = self._trailing_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(hma, fast_cci, slow_cci, trailing_ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawIndicator(area, trailing_ema)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, fast_cci)
self.DrawIndicator(oscillators, slow_cci)
def _process_candle(self, candle, hma_value, fast_cci_value, slow_cci_value, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not hma_value.IsFormed or not fast_cci_value.IsFormed or not slow_cci_value.IsFormed or not ema_value.IsFormed or not atr_value.IsFormed:
return
hma = hma_value.GetValue[Decimal](None)
fast_cci = fast_cci_value.GetValue[Decimal](None)
slow_cci = slow_cci_value.GetValue[Decimal](None)
trailing_ema = ema_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
prev_close = self._prev_close
prev_hma = self._prev_hma
self._prev_close = close
self._prev_hma = hma
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if candle.LowPrice <= self._stop_price:
self.SellMarket(self.Position)
return
if candle.HighPrice >= self._activation_price:
self._trailing_active = True
if self._trailing_active and close < trailing_ema:
self.SellMarket(self.Position)
return
if prev_close is None or prev_hma is None:
return
cross_up = prev_close <= prev_hma and close > hma
if self.Position == 0 and cross_up and fast_cci > 0 and slow_cci > 0:
self._stop_price = close - atr * Decimal(self._stop_loss_atr_multiplier.Value)
self._activation_price = close + atr * Decimal(self._trailing_activation_multiplier.Value)
self._trailing_active = False
self.BuyMarket(self.Volume)
def CreateClone(self):
return double_cci_confirmed_hull_moving_average_reversal_strategy()