Hull MA Implied Volatility Breakout
Hull MA Implied Volatility Breakout 策略基于 Hull MA Implied Volatility Breakout。
测试表明年均收益约为 121%,该策略在加密市场表现最佳。
当 its indicators confirms breakout opportunities 在日内(15m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 HmaPeriod, IVPeriod 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
HmaPeriod = 9IVPeriod = 20IVMultiplier = 2mStopLossAtr = 2mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: multiple indicators
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (15m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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>
/// Hull MA with Implied Volatility Breakout strategy.
/// Entry condition:
/// Long: HMA(t) > HMA(t-1) && IV > Avg(IV, N) + k*StdDev(IV, N)
/// Short: HMA(t) < HMA(t-1) && IV > Avg(IV, N) + k*StdDev(IV, N)
/// Exit condition:
/// Long: HMA(t) < HMA(t-1)
/// Short: HMA(t) > HMA(t-1)
/// </summary>
public class HullMAWithImpliedVolatilityBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _ivPeriod;
private readonly StrategyParam<decimal> _ivMultiplier;
private readonly StrategyParam<decimal> _stopLossAtr;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _impliedVolatilityHistory = [];
private decimal _ivAverage;
private decimal _ivStdDev;
private decimal _currentIv;
private decimal _prevHmaValue;
private decimal _currentAtr;
// Track trade direction
private bool _isLong;
private bool _isShort;
/// <summary>
/// Hull Moving Average period.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// Implied Volatility averaging period.
/// </summary>
public int IVPeriod
{
get => _ivPeriod.Value;
set => _ivPeriod.Value = value;
}
/// <summary>
/// IV standard deviation multiplier for breakout threshold.
/// </summary>
public decimal IVMultiplier
{
get => _ivMultiplier.Value;
set => _ivMultiplier.Value = value;
}
/// <summary>
/// Stop loss in ATR multiples.
/// </summary>
public decimal StopLossAtr
{
get => _stopLossAtr.Value;
set => _stopLossAtr.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor with default parameters.
/// </summary>
public HullMAWithImpliedVolatilityBreakoutStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("HMA Period", "Hull Moving Average period", "HMA Settings")
.SetOptimize(5, 15, 2);
_ivPeriod = Param(nameof(IVPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("IV Period", "Implied Volatility averaging period", "Volatility Settings")
.SetOptimize(10, 30, 5);
_ivMultiplier = Param(nameof(IVMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("IV StdDev Multiplier", "Multiplier for IV standard deviation", "Volatility Settings")
.SetOptimize(1.5m, 3m, 0.5m);
_stopLossAtr = Param(nameof(StopLossAtr), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (ATR)", "Stop Loss in multiples of ATR", "Risk Management")
.SetOptimize(1m, 3m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_isLong = _isShort = default;
_prevHmaValue = _currentAtr = _currentIv = _ivAverage = _ivStdDev = default;
_impliedVolatilityHistory.Clear();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var hma = new HullMovingAverage { Length = HmaPeriod };
var atr = new AverageTrueRange { Length = 14 }; // Fixed ATR period for stop-loss
// Subscribe to candles and bind indicators
var subscription = SubscribeCandles(CandleType);
// We need to bind both HMA and ATR
subscription
.Bind(hma, atr, ProcessCandle)
.Start();
// Create chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hma);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
/// <summary>
/// Process each candle with HMA and ATR values.
/// </summary>
private void ProcessCandle(ICandleMessage candle, decimal hmaValue, decimal atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Store ATR value for stop-loss calculation
_currentAtr = atrValue;
// Update implied volatility (in a real system, this would come from market data)
UpdateImpliedVolatility(candle);
// First run, just store the HMA value
if (_prevHmaValue == 0)
{
_prevHmaValue = hmaValue;
return;
}
var price = candle.ClosePrice;
// Determine HMA direction
var hmaRising = hmaValue > _prevHmaValue;
var hmaFalling = hmaValue < _prevHmaValue;
// Calculate IV breakout threshold
var ivBreakoutThreshold = _ivAverage + IVMultiplier * _ivStdDev;
var ivBreakout = _currentIv > ivBreakoutThreshold;
// Trading logic
// Entry conditions
// Long entry: HMA rising and IV breakout
if (hmaRising && ivBreakout && !_isLong && Position <= 0)
{
LogInfo($"Long signal: HMA rising ({hmaValue} > {_prevHmaValue}), IV breakout ({_currentIv} > {ivBreakoutThreshold})");
BuyMarket(Volume);
_isLong = true;
_isShort = false;
}
// Short entry: HMA falling and IV breakout
else if (hmaFalling && ivBreakout && !_isShort && Position >= 0)
{
LogInfo($"Short signal: HMA falling ({hmaValue} < {_prevHmaValue}), IV breakout ({_currentIv} > {ivBreakoutThreshold})");
SellMarket(Volume);
_isShort = true;
_isLong = false;
}
// Exit conditions
// Exit long: HMA starts falling
if (_isLong && hmaFalling && Position > 0)
{
LogInfo($"Exit long: HMA falling ({hmaValue} < {_prevHmaValue})");
SellMarket(Math.Abs(Position));
_isLong = false;
}
// Exit short: HMA starts rising
else if (_isShort && hmaRising && Position < 0)
{
LogInfo($"Exit short: HMA rising ({hmaValue} > {_prevHmaValue})");
BuyMarket(Math.Abs(Position));
_isShort = false;
}
// Apply ATR-based stop loss
ApplyAtrStopLoss(price);
// Store HMA value for next iteration
_prevHmaValue = hmaValue;
}
/// <summary>
/// Update implied volatility value.
/// In a real implementation, this would fetch data from market.
/// </summary>
private void UpdateImpliedVolatility(ICandleMessage candle)
{
// Simple IV simulation based on candle's high-low range and volume
// In reality, this would come from option pricing data
var range = (candle.HighPrice - candle.LowPrice) / candle.LowPrice;
var volume = candle.TotalVolume > 0 ? candle.TotalVolume : 1;
// Simulate IV based on range and volume with some randomness
decimal iv = (decimal)(range * (1 + 0.5m * (decimal)RandomGen.GetDouble()) * 100);
// Add volume factor - higher volume often correlates with higher IV
iv *= (decimal)Math.Min(1.5, 1 + Math.Log10((double)volume) * 0.1);
_currentIv = iv;
// Add to history
_impliedVolatilityHistory.Add(_currentIv);
if (_impliedVolatilityHistory.Count > IVPeriod)
{
_impliedVolatilityHistory.RemoveAt(0);
}
// Calculate average
decimal sum = 0;
foreach (var value in _impliedVolatilityHistory)
{
sum += value;
}
_ivAverage = _impliedVolatilityHistory.Count > 0
? sum / _impliedVolatilityHistory.Count
: 0;
// Calculate standard deviation
if (_impliedVolatilityHistory.Count > 1)
{
decimal sumSquaredDiffs = 0;
foreach (var value in _impliedVolatilityHistory)
{
var diff = value - _ivAverage;
sumSquaredDiffs += diff * diff;
}
_ivStdDev = (decimal)Math.Sqrt((double)(sumSquaredDiffs / (_impliedVolatilityHistory.Count - 1)));
}
else
{
_ivStdDev = 0.5m; // Default value until we have enough data
}
LogInfo($"IV: {_currentIv}, Avg: {_ivAverage}, StdDev: {_ivStdDev}");
}
/// <summary>
/// Apply ATR-based stop loss.
/// </summary>
private void ApplyAtrStopLoss(decimal price)
{
// Only apply stop-loss if ATR is available and position exists
if (_currentAtr <= 0 || Position == 0)
return;
// Calculate stop levels
if (Position > 0) // Long position
{
var stopLevel = price - (StopLossAtr * _currentAtr);
if (price <= stopLevel)
{
LogInfo($"ATR Stop Loss triggered for long position: Current {price} <= Stop {stopLevel}");
SellMarket(Math.Abs(Position));
_isLong = false;
}
}
else if (Position < 0) // Short position
{
var stopLevel = price + (StopLossAtr * _currentAtr);
if (price >= stopLevel)
{
LogInfo($"ATR Stop Loss triggered for short position: Current {price} >= Stop {stopLevel}");
BuyMarket(Math.Abs(Position));
_isShort = false;
}
}
}
}
import clr
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
from StockSharp.Algo.Indicators import HullMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_implied_volatility_breakout_strategy(Strategy):
"""
Hull MA with Implied Volatility Breakout strategy.
"""
def __init__(self):
super(hull_ma_implied_volatility_breakout_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("HMA Period", "Hull Moving Average period", "HMA Settings")
self._iv_period = self.Param("IVPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("IV Period", "Implied Volatility averaging period", "Volatility Settings")
self._iv_multiplier = self.Param("IVMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("IV StdDev Multiplier", "Multiplier for IV standard deviation", "Volatility Settings")
self._stop_loss_atr = self.Param("StopLossAtr", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (ATR)", "Stop Loss in multiples of ATR", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._is_long = False
self._is_short = False
self._iv_history = []
self._iv_average = 0.0
self._iv_std_dev = 0.0
self._current_iv = 0.0
self._prev_hma = 0.0
self._current_atr = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(hull_ma_implied_volatility_breakout_strategy, self).OnReseted()
self._is_long = False
self._is_short = False
self._iv_history = []
self._iv_average = 0.0
self._iv_std_dev = 0.0
self._current_iv = 0.0
self._prev_hma = 0.0
self._current_atr = 0.0
def OnStarted2(self, time):
super(hull_ma_implied_volatility_breakout_strategy, self).OnStarted2(time)
hma = HullMovingAverage()
hma.Length = int(self._hma_period.Value)
atr = AverageTrueRange()
atr.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(hma, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, hma_val, atr_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
hma_value = float(hma_val)
atr_value = float(atr_val)
self._current_atr = atr_value
self.UpdateImpliedVolatility(candle)
if self._prev_hma == 0.0:
self._prev_hma = hma_value
return
price = float(candle.ClosePrice)
hma_rising = hma_value > self._prev_hma
hma_falling = hma_value < self._prev_hma
iv_mult = float(self._iv_multiplier.Value)
iv_threshold = self._iv_average + iv_mult * self._iv_std_dev
iv_breakout = self._current_iv > iv_threshold
if hma_rising and iv_breakout and not self._is_long and self.Position <= 0:
self.BuyMarket(self.Volume)
self._is_long = True
self._is_short = False
elif hma_falling and iv_breakout and not self._is_short and self.Position >= 0:
self.SellMarket(self.Volume)
self._is_short = True
self._is_long = False
if self._is_long and hma_falling and self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self._is_long = False
elif self._is_short and hma_rising and self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._is_short = False
self.ApplyAtrStopLoss(price)
self._prev_hma = hma_value
def UpdateImpliedVolatility(self, candle):
range_val = float((candle.HighPrice - candle.LowPrice) / candle.LowPrice)
vol = float(candle.TotalVolume) if candle.TotalVolume > 0 else 1.0
iv = float(range_val * 100.0)
iv *= min(1.5, 1.0 + Math.Log10(vol) * 0.1)
self._current_iv = iv
self._iv_history.append(iv)
period = int(self._iv_period.Value)
if len(self._iv_history) > period:
self._iv_history.pop(0)
n = len(self._iv_history)
if n > 0:
total = 0.0
for v in self._iv_history:
total += v
self._iv_average = total / n
else:
self._iv_average = 0.0
if n > 1:
sq_sum = 0.0
for v in self._iv_history:
diff = v - self._iv_average
sq_sum += diff * diff
self._iv_std_dev = Math.Sqrt(sq_sum / (n - 1))
else:
self._iv_std_dev = 0.5
self.LogInfo("IV: {0}, Avg: {1}, StdDev: {2}".format(self._current_iv, self._iv_average, self._iv_std_dev))
def ApplyAtrStopLoss(self, price):
if self._current_atr <= 0 or self.Position == 0:
return
sl_mult = float(self._stop_loss_atr.Value)
if self.Position > 0:
stop_level = price - sl_mult * self._current_atr
if price <= stop_level:
self.SellMarket(Math.Abs(self.Position))
self._is_long = False
elif self.Position < 0:
stop_level = price + sl_mult * self._current_atr
if price >= stop_level:
self.BuyMarket(Math.Abs(self.Position))
self._is_short = False
def CreateClone(self):
return hull_ma_implied_volatility_breakout_strategy()