Hull 均线反转策略
Hull 移动平均在保持平滑的同时对价格变化反应迅速,其方向的变化往往预示短期反转。本策略监控连续的 Hull MA 值,当斜率翻转时入场。
测试表明年均收益约为 154%,该策略在股票市场表现最佳。
当均线由下行转为上行时做多;由上行转为下行时做空。风险通过基于 ATR 的止损控制,止损放在最近蜡烛之外。
离场依靠该保护性止损,捕捉 Hull MA 指示动能转向后的部分走势。
细节
- 入场条件:Hull MA 斜率改变方向。
- 多/空:双向。
- 退出条件:止损。
- 止损:有,基于 ATR。
- 默认值:
HmaPeriod= 9AtrMultiplier= 2 ATRCandleType= 15 分钟
- 过滤条件:
- 类别: 趋势跟随
- 方向: 双向
- 指标: Hull MA, 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>
/// Hull MA Reversal strategy.
/// Enters long when Hull MA changes direction from down to up.
/// Enters short when Hull MA changes direction from up to down.
/// Uses cooldown to control trade frequency.
/// </summary>
public class HullMaReversalStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevHma;
private decimal _prevPrevHma;
private int _cooldown;
/// <summary>
/// HMA period.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HullMaReversalStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetRange(5, 20)
.SetDisplay("HMA Period", "Period for Hull Moving Average", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHma = default;
_prevPrevHma = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHma = 0;
_prevPrevHma = 0;
_cooldown = 0;
var hma = new HullMovingAverage { Length = HmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(hma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal hmaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_prevHma == 0)
{
_prevHma = hmaValue;
return;
}
if (_prevPrevHma == 0)
{
_prevPrevHma = _prevHma;
_prevHma = hmaValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevPrevHma = _prevHma;
_prevHma = hmaValue;
return;
}
// Direction change detection
var dirChangedUp = _prevHma < _prevPrevHma && hmaValue > _prevHma;
var dirChangedDown = _prevHma > _prevPrevHma && hmaValue < _prevHma;
if (Position == 0 && dirChangedUp)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && dirChangedDown)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && dirChangedDown)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && dirChangedUp)
{
BuyMarket();
_cooldown = CooldownBars;
}
_prevPrevHma = _prevHma;
_prevHma = hmaValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import HullMovingAverage
from StockSharp.Algo.Strategies import Strategy
class hull_ma_reversal_strategy(Strategy):
"""
Hull MA Reversal strategy.
Enters long when Hull MA changes direction from down to up.
Enters short when Hull MA changes direction from up to down.
Uses cooldown to control trade frequency.
"""
def __init__(self):
super(hull_ma_reversal_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9).SetDisplay("HMA Period", "Period for Hull Moving Average", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._prev_hma = 0.0
self._prev_prev_hma = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hull_ma_reversal_strategy, self).OnReseted()
self._prev_hma = 0.0
self._prev_prev_hma = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(hull_ma_reversal_strategy, self).OnStarted2(time)
self._prev_hma = 0.0
self._prev_prev_hma = 0.0
self._cooldown = 0
hma = HullMovingAverage()
hma.Length = self._hma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(hma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, hma_val):
if candle.State != CandleStates.Finished:
return
hv = float(hma_val)
if self._prev_hma == 0:
self._prev_hma = hv
return
if self._prev_prev_hma == 0:
self._prev_prev_hma = self._prev_hma
self._prev_hma = hv
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_prev_hma = self._prev_hma
self._prev_hma = hv
return
cd = self._cooldown_bars.Value
# Direction change detection
dir_changed_up = self._prev_hma < self._prev_prev_hma and hv > self._prev_hma
dir_changed_down = self._prev_hma > self._prev_prev_hma and hv < self._prev_hma
if self.Position == 0 and dir_changed_up:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and dir_changed_down:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and dir_changed_down:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and dir_changed_up:
self.BuyMarket()
self._cooldown = cd
self._prev_prev_hma = self._prev_hma
self._prev_hma = hv
def CreateClone(self):
return hull_ma_reversal_strategy()