Hull MA 傾斜ブレイクアウト戦略
Hull MA 傾斜ブレイクアウト戦略は、Hull の変化率を追跡します。異常に急な傾斜は、新しいトレンドが形成されつつあることを示唆します。
テストでは年平均リターン約121%を示しています。暗号通貨市場で最もよく機能します。
傾斜が標準偏差の倍数によって典型的なレベルを超えると、加速の方向にトレードが行われ、保護ストップが付きます。
早期のトレンド参入を望む積極的なトレーダーに向いています。傾斜が正常な値に戻るとポジションは終了します。デフォルト HullLength = 9。
詳細
- エントリー条件: インジケーターが偏差乗数分だけ平均を上回る。
- ロング/ショート: 両方向。
- エグジット条件: インジケーターが平均に戻る。
- ストップ: はい。
- デフォルト値:
HullLength= 9LookbackPeriod= 20DeviationMultiplier= 2mStopLoss= new Unit(2CandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: Hull
- ストップ: はい
- 複雑さ: 中級
- 時間軸: 短期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Hull Moving Average Slope breakout
/// Enters positions when the slope of Hull MA exceeds average slope plus a multiple of standard deviation
/// </summary>
public class HullMaSlopeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _hullLength;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<Unit> _stopLoss;
private HullMovingAverage _hullMa;
private AverageTrueRange _atr;
private decimal _prevHullValue;
private decimal _currentSlope;
private decimal _avgSlope;
private decimal _stdDevSlope;
private decimal[] _slopes;
private int _currentIndex;
private bool _isInitialized;
/// <summary>
/// Hull Moving Average length
/// </summary>
public int HullLength
{
get => _hullLength.Value;
set => _hullLength.Value = value;
}
/// <summary>
/// Lookback period for slope statistics calculation
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for breakout detection
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Candle type
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop loss value
/// </summary>
public Unit StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Constructor
/// </summary>
public HullMaSlopeBreakoutStrategy()
{
_hullLength = Param(nameof(HullLength), 9)
.SetGreaterThanZero()
.SetDisplay("Hull MA Length", "Period for Hull Moving Average", "Indicator Parameters")
.SetOptimize(5, 20, 1);
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Period for slope statistics calculation", "Strategy Parameters")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for breakout detection", "Strategy Parameters")
.SetOptimize(1m, 3m, 0.5m);
_stopLoss = Param(nameof(StopLoss), new Unit(2, UnitTypes.Absolute))
.SetDisplay("Stop Loss", "Stop loss value in ATRs", "Risk Management");
_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();
_prevHullValue = 0;
_currentSlope = 0;
_avgSlope = 0;
_stdDevSlope = 0;
_currentIndex = 0;
_isInitialized = false;
_slopes = new decimal[LookbackPeriod];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
_slopes = new decimal[LookbackPeriod];
_hullMa = new HullMovingAverage { Length = HullLength };
_atr = new AverageTrueRange { Length = 14 }; // ATR for stop-loss
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_hullMa, _atr, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _hullMa);
DrawOwnTrades(area);
}
// Set up position protection
StartProtection(
takeProfit: null, // We'll handle exits via strategy logic
stopLoss: StopLoss,
isStopTrailing: false
);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, decimal hullValue, decimal atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if indicator is formed
if (!_hullMa.IsFormed)
return;
// Extract Hull MA value
decimal currentHullValue = hullValue;
// Calculate the slope
if (!_isInitialized)
{
_prevHullValue = currentHullValue;
_isInitialized = true;
return;
}
// Calculate current slope (simple difference for now)
_currentSlope = currentHullValue - _prevHullValue;
if (_slopes is null || _slopes.Length != LookbackPeriod)
{
_slopes = new decimal[LookbackPeriod];
_currentIndex = 0;
_prevHullValue = currentHullValue;
return;
}
// Store slope in array and update index
_slopes[_currentIndex] = _currentSlope;
_currentIndex = (_currentIndex + 1) % _slopes.Length;
// Calculate statistics once we have enough data
if (!IsFormedAndOnlineAndAllowTrading())
return;
CalculateStatistics();
// Trading logic
if (Math.Abs(_avgSlope) > 0) // Avoid division by zero
{
// Long signal: slope exceeds average + k*stddev (slope is positive and we don't have a long position)
if (_currentSlope > 0 &&
_currentSlope > _avgSlope + DeviationMultiplier * _stdDevSlope &&
Position <= 0)
{
// Cancel existing orders
CancelActiveOrders();
// Enter long position
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
LogInfo($"Long signal: Slope {_currentSlope} > Avg {_avgSlope} + {DeviationMultiplier}*StdDev {_stdDevSlope}");
}
// Short signal: slope exceeds average + k*stddev in negative direction (slope is negative and we don't have a short position)
else if (_currentSlope < 0 &&
_currentSlope < _avgSlope - DeviationMultiplier * _stdDevSlope &&
Position >= 0)
{
// Cancel existing orders
CancelActiveOrders();
// Enter short position
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
LogInfo($"Short signal: Slope {_currentSlope} < Avg {_avgSlope} - {DeviationMultiplier}*StdDev {_stdDevSlope}");
}
// Exit conditions - when slope returns to average
if (Position > 0 && _currentSlope < _avgSlope)
{
// Exit long position
SellMarket(Math.Abs(Position));
LogInfo($"Exit long: Slope {_currentSlope} < Avg {_avgSlope}");
}
else if (Position < 0 && _currentSlope > _avgSlope)
{
// Exit short position
BuyMarket(Math.Abs(Position));
LogInfo($"Exit short: Slope {_currentSlope} > Avg {_avgSlope}");
}
}
// Store current Hull MA value for next slope calculation
_prevHullValue = currentHullValue;
}
private void CalculateStatistics()
{
var period = _slopes?.Length ?? 0;
if (period <= 0)
return;
// Reset statistics
_avgSlope = 0;
decimal sumSquaredDiffs = 0;
// Calculate average
for (int i = 0; i < period; i++)
{
_avgSlope += _slopes[i];
}
_avgSlope /= period;
// Calculate standard deviation
for (int i = 0; i < period; i++)
{
decimal diff = _slopes[i] - _avgSlope;
sumSquaredDiffs += diff * diff;
}
_stdDevSlope = (decimal)Math.Sqrt((double)(sumSquaredDiffs / period));
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import HullMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_slope_breakout_strategy(Strategy):
"""
Hull MA slope breakout. Enters when slope exceeds avg + k*stddev.
"""
def __init__(self):
super(hull_ma_slope_breakout_strategy, self).__init__()
self._hull_length = self.Param("HullLength", 9).SetDisplay("Hull Length", "Hull MA period", "Indicators")
self._lookback = self.Param("LookbackPeriod", 20).SetDisplay("Lookback", "Slope stats period", "Strategy")
self._dev_mult = self.Param("DeviationMultiplier", 2.0).SetDisplay("Dev Mult", "Stddev multiplier", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._prev_hull = 0.0
self._slopes = None
self._current_index = 0
self._is_init = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(hull_ma_slope_breakout_strategy, self).OnReseted()
self._prev_hull = 0.0
self._slopes = [0.0] * int(self._lookback.Value)
self._current_index = 0
self._is_init = False
def OnStarted2(self, time):
super(hull_ma_slope_breakout_strategy, self).OnStarted2(time)
lb = int(self._lookback.Value)
self._slopes = [0.0] * lb
self._current_index = 0
hma = HullMovingAverage()
hma.Length = self._hull_length.Value
atr = AverageTrueRange()
atr.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(hma, atr, self._process_candle).Start()
self.StartProtection(None, Unit(2, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, hull_val, atr_val):
if candle.State != CandleStates.Finished:
return
hull = float(hull_val)
if not self._is_init:
self._prev_hull = hull
self._is_init = True
return
current_slope = hull - self._prev_hull
lb = int(self._lookback.Value)
if self._slopes is None or len(self._slopes) != lb:
self._slopes = [0.0] * lb
self._current_index = 0
self._prev_hull = hull
return
# Store slope in circular array
self._slopes[self._current_index] = current_slope
self._current_index = (self._current_index + 1) % lb
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_hull = hull
return
# Calculate statistics
avg_slope = sum(self._slopes) / lb
sum_sq = sum((s - avg_slope) ** 2 for s in self._slopes)
std_slope = math.sqrt(sum_sq / lb)
dm = float(self._dev_mult.Value)
if abs(avg_slope) > 0:
# Long signal
if current_slope > 0 and current_slope > avg_slope + dm * std_slope and self.Position <= 0:
self.CancelActiveOrders()
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
# Short signal
elif current_slope < 0 and current_slope < avg_slope - dm * std_slope and self.Position >= 0:
self.CancelActiveOrders()
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
# Exit conditions
if self.Position > 0 and current_slope < avg_slope:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0 and current_slope > avg_slope:
self.BuyMarket(Math.Abs(self.Position))
self._prev_hull = hull
def CreateClone(self):
return hull_ma_slope_breakout_strategy()