Hull Candles 策略
Hull Candles 是一种简单的趋势跟随策略,使用基于 OHLC 平均价的 Hull 移动平均线。当 HMA 上升且收盘价高于其 SMA 时做多;当 HMA 下降且收盘价低于其 SMA 时做空。
细节
- 数据:价格K线。
- 入场条件:
- 多头:HMA 上升且收盘价 > SMA。
- 空头:HMA 下降且收盘价 < SMA。
- 出场条件:反向信号。
- 止损:无。
- 默认值:
BodyLength= 10SmaLength= 1
- 过滤器:
- 类别:趋势跟随
- 方向:多头 & 空头
- 指标:HMA, SMA
- 复杂度:低
- 风险等级:高
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 Candles strategy.
/// A Hull moving average of BodyLength is built on the OHLC4 price and smoothed by an SMA of SmaLength. When the HMA rises and the
/// close is above that SMA the strategy goes long; when the HMA falls and the close is below it the strategy goes short, reversing
/// an opposite position.
/// </summary>
public class HullCandlesStrategy : Strategy
{
private readonly StrategyParam<int> _bodyLength;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<DataType> _candleType;
private HullMovingAverage _hma;
private SimpleMovingAverage _sma;
private decimal? _prevHma;
/// <summary>
/// Hull moving average length.
/// </summary>
public int BodyLength
{
get => _bodyLength.Value;
set => _bodyLength.Value = value;
}
/// <summary>
/// Length of the SMA that smooths the HMA.
/// </summary>
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HullCandlesStrategy()
{
_bodyLength = Param(nameof(BodyLength), 10)
.SetGreaterThanZero()
.SetDisplay("Body Length", "Hull moving average length", "Indicators");
_smaLength = Param(nameof(SmaLength), 1)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "Length of the SMA that smooths the HMA", "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();
_prevHma = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHma = null;
_hma = new HullMovingAverage { Length = BodyLength };
_sma = new SimpleMovingAverage { Length = SmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _hma);
DrawIndicator(area, _sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var ohlc4 = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m;
var hmaValue = _hma.Process(new DecimalIndicatorValue(_hma, ohlc4, candle.OpenTime) { IsFinal = true });
if (!_hma.IsFormed)
return;
var hma = hmaValue.GetValue<decimal>();
var smaValue = _sma.Process(new DecimalIndicatorValue(_sma, hma, candle.OpenTime) { IsFinal = true });
var prevHma = _prevHma;
_prevHma = hma;
if (!_sma.IsFormed || prevHma is not decimal previous)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sma = smaValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (hma > previous && close > sma && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (hma < previous && close < sma && 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")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import HullMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class hull_candles_strategy(Strategy):
"""
Hull Candles strategy.
A Hull moving average of BodyLength is built on the OHLC4 price and smoothed by an SMA of SmaLength. When the HMA rises and the
close is above that SMA the strategy goes long; when the HMA falls and the close is below it the strategy goes short, reversing
an opposite position.
"""
def __init__(self):
super(hull_candles_strategy, self).__init__()
self._body_length = self.Param("BodyLength", 10).SetGreaterThanZero().SetDisplay("Body Length", "Hull moving average length", "Indicators")
self._sma_length = self.Param("SmaLength", 1).SetGreaterThanZero().SetDisplay("SMA Length", "Length of the SMA that smooths the HMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._hma = None
self._sma = None
self._prev_hma = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hull_candles_strategy, self).OnReseted()
self._prev_hma = None
def OnStarted2(self, time):
super(hull_candles_strategy, self).OnStarted2(time)
self._prev_hma = None
self._hma = HullMovingAverage()
self._hma.Length = self._body_length.Value
self._sma = SimpleMovingAverage()
self._sma.Length = self._sma_length.Value
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._hma)
self.DrawIndicator(area, self._sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
ohlc4 = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(4)
hma_value = process_float(self._hma, ohlc4, candle.OpenTime, True)
if not self._hma.IsFormed:
return
hma = float(hma_value)
sma_value = process_float(self._sma, Decimal(hma), candle.OpenTime, True)
prev_hma = self._prev_hma
self._prev_hma = hma
if not self._sma.IsFormed or prev_hma is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sma = float(sma_value)
close = float(candle.ClosePrice)
if hma > prev_hma and close > sma and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif hma < prev_hma and close < sma and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return hull_candles_strategy()