Estrategia de Trading Basada en Hull Moving Average MH
Estrategia de ruptura basada en Hull Moving Average.
La estrategia compara el precio de apertura con los niveles dinámicos derivados de la Hull Moving Average. Entra largo cuando el precio rompe por encima del nivel superior y corto cuando cae por debajo del nivel inferior. Las posiciones existentes se cierran en rupturas opuestas.
Detalles
- Criterios de entrada: Relación del precio con los niveles de Hull Moving Average.
- Largo/Corto: Ambas direcciones.
- Criterios de salida: Ruptura opuesta.
- Stops: Ninguno.
- Valores predeterminados:
HullPeriod= 210CandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Tendencia
- Dirección: Ambos
- Indicadores: MA
- Stops: No
- Complejidad: Básico
- Marco temporal: Intradía (5m)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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>
/// HMA cross strategy with threshold and cooldown to reduce signal noise.
/// </summary>
public class MhHullMovingAverageBasedTradingStrategy : Strategy
{
private readonly StrategyParam<int> _hullPeriod;
private readonly StrategyParam<decimal> _signalThresholdPercent;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private HullMovingAverage _hma;
private decimal _prevDiffPercent;
private bool _hasPrevDiff;
private int _barsFromSignal;
/// <summary>
/// Period for Hull Moving Average.
/// </summary>
public int HullPeriod
{
get => _hullPeriod.Value;
set => _hullPeriod.Value = value;
}
/// <summary>
/// Minimum price to HMA distance in percent required for a signal.
/// </summary>
public decimal SignalThresholdPercent
{
get => _signalThresholdPercent.Value;
set => _signalThresholdPercent.Value = value;
}
/// <summary>
/// Minimum bars between entries.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the strategy.
/// </summary>
public MhHullMovingAverageBasedTradingStrategy()
{
_hullPeriod = Param(nameof(HullPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("Hull Period", "Period for Hull Moving Average", "Indicators");
_signalThresholdPercent = Param(nameof(SignalThresholdPercent), 0.15m)
.SetGreaterThanZero()
.SetDisplay("Signal Threshold %", "Minimum distance from HMA", "Indicators");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "Indicators");
_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();
_hma = null;
_prevDiffPercent = 0m;
_hasPrevDiff = false;
_barsFromSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_hma = new HullMovingAverage { Length = HullPeriod };
_prevDiffPercent = 0m;
_hasPrevDiff = false;
_barsFromSignal = SignalCooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_hma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal hmaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_hma.IsFormed)
return;
var price = candle.ClosePrice;
if (price <= 0m)
return;
var diffPercent = (price - hmaValue) / price * 100m;
var threshold = SignalThresholdPercent;
var crossedUp = _hasPrevDiff && _prevDiffPercent <= threshold && diffPercent > threshold;
var crossedDown = _hasPrevDiff && _prevDiffPercent >= -threshold && diffPercent < -threshold;
_prevDiffPercent = diffPercent;
_hasPrevDiff = true;
_barsFromSignal++;
if (_barsFromSignal < SignalCooldownBars)
return;
if (crossedUp && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_barsFromSignal = 0;
}
else if (crossedDown && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_barsFromSignal = 0;
}
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class mh_hull_moving_average_based_trading_strategy(Strategy):
def __init__(self):
super(mh_hull_moving_average_based_trading_strategy, self).__init__()
self._hull_period = self.Param("HullPeriod", 120) \
.SetGreaterThanZero() \
.SetDisplay("Hull Period", "Period for Hull Moving Average", "Indicators")
self._signal_threshold_percent = self.Param("SignalThresholdPercent", 0.15) \
.SetGreaterThanZero() \
.SetDisplay("Signal Threshold %", "Minimum distance from HMA", "Indicators")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 10) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_diff_percent = 0.0
self._has_prev_diff = False
self._bars_from_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(mh_hull_moving_average_based_trading_strategy, self).OnReseted()
self._prev_diff_percent = 0.0
self._has_prev_diff = False
self._bars_from_signal = 0
def OnStarted2(self, time):
super(mh_hull_moving_average_based_trading_strategy, self).OnStarted2(time)
self._prev_diff_percent = 0.0
self._has_prev_diff = False
self._bars_from_signal = self._signal_cooldown_bars.Value
self._hma = HullMovingAverage()
self._hma.Length = self._hull_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._hma, self.OnProcess).Start()
def OnProcess(self, candle, hma_value):
if candle.State != CandleStates.Finished:
return
if not self._hma.IsFormed:
return
price = float(candle.ClosePrice)
if price <= 0.0:
return
hv = float(hma_value)
diff_percent = (price - hv) / price * 100.0
threshold = float(self._signal_threshold_percent.Value)
crossed_up = self._has_prev_diff and self._prev_diff_percent <= threshold and diff_percent > threshold
crossed_down = self._has_prev_diff and self._prev_diff_percent >= -threshold and diff_percent < -threshold
self._prev_diff_percent = diff_percent
self._has_prev_diff = True
self._bars_from_signal += 1
if self._bars_from_signal < self._signal_cooldown_bars.Value:
return
if crossed_up and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif crossed_down and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
def CreateClone(self):
return mh_hull_moving_average_based_trading_strategy()