Estrategia Hull Ma Adx
Estrategia basada en Hull Moving Average y ADX. Entra en largo cuando el HMA sube y ADX > 25 (tendencia fuerte). Entra en corto cuando el HMA baja y ADX > 25 (tendencia fuerte). Sale cuando ADX < 20 (tendencia debilitándose).
Las pruebas indican un rendimiento anual promedio de aproximadamente 178%. Funciona mejor en el mercado de acciones.
Hull MA muestra la tendencia, mientras que ADX confirma su intensidad. Las entradas siguen la pendiente de Hull cuando ADX indica fortaleza.
Efectiva para operadores que se centran en tendencias suaves con confirmación. Los stops basados en ATR mantienen las pérdidas bajo control.
Detalles
- Criterios de entrada:
- Largo:
HullMA turning up && ADX > 25 - Corto:
HullMA turning down && ADX > 25
- Largo:
- Largo/Corto: Ambos
- Criterios de salida: reversión de Hull MA
- Stops: Basados en ATR usando
AtrMultiplier - Valores predeterminados:
HmaPeriod= 9AdxPeriod= 14AdxThreshold= 25AdxExitThreshold= 20AtrMultiplier= 2AtrPeriod= 14CandleType= TimeSpan.FromMinutes(15).TimeFrame() Los valores 25 y 20 de las reglas son los predeterminados de AdxThreshold y AdxExitThreshold. La HMA gira al alza cuando sube después de bajar y a la baja cuando baja después de subir; un largo se cierra cuando baja y un corto cuando sube, y cualquiera se cierra cuando el ADX cae por debajo de AdxExitThreshold. El stop está a AtrMultiplier ATR (AtrPeriod) del cierre de entrada y se comprueba en los cierres de vela. Una señal de entrada contra una posición abierta la invierte.
- Filtros:
- Categoría: Tendencia
- Dirección: Ambos
- Indicadores: Hull MA, Moving Average, ADX
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Medio plazo
- 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>
/// Hull MA ADX strategy.
/// The Hull average turns up when it rises after falling and turns down when it falls after rising. While ADX is above AdxThreshold, a turn up
/// goes long and a turn down goes short, reversing an opposite position. A long closes when the Hull average falls, a short when it rises,
/// and either closes once ADX drops below AdxExitThreshold. The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
/// </summary>
public class HullMaAdxStrategy : Strategy
{
private readonly StrategyParam<int> _hmaPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _adxExitThreshold;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevHull;
private decimal? _prevPrevHull;
private decimal _stopPrice;
/// <summary>
/// Period of the Hull moving average.
/// </summary>
public int HmaPeriod
{
get => _hmaPeriod.Value;
set => _hmaPeriod.Value = value;
}
/// <summary>
/// Period of ADX.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX level required to enter.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// ADX level below which the position closes.
/// </summary>
public decimal AdxExitThreshold
{
get => _adxExitThreshold.Value;
set => _adxExitThreshold.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Period of the stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public HullMaAdxStrategy()
{
_hmaPeriod = Param(nameof(HmaPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("HMA Period", "Period of the Hull moving average", "Indicators");
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period of ADX", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "ADX level required to enter", "Indicators");
_adxExitThreshold = Param(nameof(AdxExitThreshold), 20m)
.SetDisplay("ADX Exit Threshold", "ADX level below which the position closes", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the stop ATR", "Risk");
_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();
_prevHull = null;
_prevPrevHull = null;
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHull = null;
_prevPrevHull = null;
_stopPrice = default;
var hull = new HullMovingAverage { Length = HmaPeriod };
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(hull, adx, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hull);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, adx);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hullValue, IIndicatorValue adxValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!hullValue.IsFormed || !adxValue.IsFormed || !atrValue.IsFormed)
return;
if (adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal strength })
return;
var hull = hullValue.GetValue<decimal>();
var prevHull = _prevHull;
var prevPrevHull = _prevPrevHull;
_prevPrevHull = prevHull;
_prevHull = hull;
if (prevHull is not decimal last || prevPrevHull is not decimal beforeLast)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
var rising = hull > last;
var falling = hull < last;
var turnsUp = rising && last < beforeLast;
var turnsDown = falling && last > beforeLast;
var strong = strength > AdxThreshold;
var weak = strength < AdxExitThreshold;
if (turnsUp && strong && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - AtrMultiplier * atr;
}
else if (turnsDown && strong && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + AtrMultiplier * atr;
}
else if (Position > 0 && (falling || weak || (AtrMultiplier > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (rising || weak || (AtrMultiplier > 0 && close >= _stopPrice)))
{
BuyMarket(-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, AverageDirectionalIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class hull_ma_adx_strategy(Strategy):
"""
Hull MA ADX strategy.
The Hull average turns up when it rises after falling and turns down when it falls after rising. While ADX is above AdxThreshold, a turn up
goes long and a turn down goes short, reversing an opposite position. A long closes when the Hull average falls, a short when it rises,
and either closes once ADX drops below AdxExitThreshold. The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(hull_ma_adx_strategy, self).__init__()
self._hma_period = self.Param("HmaPeriod", 9).SetGreaterThanZero().SetDisplay("HMA Period", "Period of the Hull moving average", "Indicators")
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "Period of ADX", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0).SetDisplay("ADX Threshold", "ADX level required to enter", "Indicators")
self._adx_exit_threshold = self.Param("AdxExitThreshold", 20.0).SetDisplay("ADX Exit Threshold", "ADX level below which the position closes", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Multiplier", "Stop distance from the entry in ATRs", "Risk")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the stop ATR", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_hull = None
self._prev_prev_hull = None
self._stop_price = Decimal(0)
def OnReseted(self):
super(hull_ma_adx_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(hull_ma_adx_strategy, self).OnStarted2(time)
self._reset_state()
hull = HullMovingAverage()
hull.Length = self._hma_period.Value
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(hull, adx, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hull)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _process_candle(self, candle, hull_value, adx_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not hull_value.IsFormed or not adx_value.IsFormed or not atr_value.IsFormed or adx_value.MovingAverage is None:
return
hull = hull_value.GetValue[Decimal](None)
prev_hull = self._prev_hull
prev_prev_hull = self._prev_prev_hull
self._prev_prev_hull = prev_hull
self._prev_hull = hull
if prev_hull is None or prev_prev_hull is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
strength = adx_value.MovingAverage
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
rising = hull > prev_hull
falling = hull < prev_hull
turns_up = rising and prev_hull < prev_prev_hull
turns_down = falling and prev_hull > prev_prev_hull
strong = strength > Decimal(self._adx_threshold.Value)
weak = strength < Decimal(self._adx_exit_threshold.Value)
stop_atr = Decimal(self._atr_multiplier.Value)
if turns_up and strong and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif turns_down and strong and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (falling or weak or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (rising or weak or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return hull_ma_adx_strategy()