Estrategia MACD + DMI
Combina el Moving Average Convergence Divergence con el Directional Movement Index para operar solo cuando se confirma la fuerza de la tendencia. El sistema espera un cruce de MACD y verifica que la línea direccional dominante supere la línea opuesta mientras el ADX está por encima de un nivel clave.
La estrategia está diseñada para posiciones largas y cortas. Al combinar filtros de momentum y tendencia, busca evitar las señales falsas en mercados laterales. Los stops de protección basados en volatilidad mantienen el riesgo controlado.
Detalles
- Criterios de entrada:
- Largo: La línea MACD cruza por encima de la señal, +DI > -DI, y ADX por encima del nivel clave.
- Corto: La línea MACD cruza por debajo de la señal, -DI > +DI, y ADX por encima del nivel clave.
- Criterios de salida:
- Señal inversa o stop de volatilidad alcanzado.
- Indicadores:
- MACD (rápida 12, lenta 26, señal 9)
- Directional Movement Index (longitud 14, suavizado ADX 14)
- Stops: Utiliza stop-loss y take-profit incorporados mediante StartProtection.
- Valores predeterminados:
Ma1Length= 10Ma2Length= 20DmiLength= 14AdxSmoothing= 14KeyLevel= 20
- Filtros:
- Seguimiento de tendencia
- Funciona en múltiples marcos temporales
- Indicadores: MACD, DMI
- Stops: Sí
- Complejidad: Moderado
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>
/// MACD + DMI strategy.
/// The MACD line is the difference of Ma1Length and Ma2Length EMAs. A long opens when it crosses above its signal line
/// while +DI is above -DI and ADX is above KeyLevel; a short opens on the mirrored setup. The reverse signal flips the
/// position, and percent stop-loss and take-profit protection limit each trade.
/// </summary>
public class MacdDmiStrategy : Strategy
{
private readonly StrategyParam<int> _ma1Length;
private readonly StrategyParam<int> _ma2Length;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<int> _dmiLength;
private readonly StrategyParam<int> _adxSmoothing;
private readonly StrategyParam<decimal> _keyLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _adx;
private decimal? _prevMacd;
private decimal? _prevSignal;
/// <summary>
/// Fast EMA period of MACD.
/// </summary>
public int Ma1Length
{
get => _ma1Length.Value;
set => _ma1Length.Value = value;
}
/// <summary>
/// Slow EMA period of MACD.
/// </summary>
public int Ma2Length
{
get => _ma2Length.Value;
set => _ma2Length.Value = value;
}
/// <summary>
/// MACD signal line period.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Directional movement period.
/// </summary>
public int DmiLength
{
get => _dmiLength.Value;
set => _dmiLength.Value = value;
}
/// <summary>
/// ADX smoothing period.
/// </summary>
public int AdxSmoothing
{
get => _adxSmoothing.Value;
set => _adxSmoothing.Value = value;
}
/// <summary>
/// ADX level required for entries.
/// </summary>
public decimal KeyLevel
{
get => _keyLevel.Value;
set => _keyLevel.Value = value;
}
/// <summary>
/// Stop-loss percentage. 0 disables it.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take-profit percentage. 0 disables it.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MacdDmiStrategy()
{
_ma1Length = Param(nameof(Ma1Length), 10)
.SetGreaterThanZero()
.SetDisplay("MA1 Length", "Fast EMA period of MACD", "MACD");
_ma2Length = Param(nameof(Ma2Length), 20)
.SetGreaterThanZero()
.SetDisplay("MA2 Length", "Slow EMA period of MACD", "MACD");
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "MACD signal line period", "MACD");
_dmiLength = Param(nameof(DmiLength), 14)
.SetGreaterThanZero()
.SetDisplay("DMI Length", "Directional movement period", "DMI");
_adxSmoothing = Param(nameof(AdxSmoothing), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Smoothing", "ADX smoothing period", "DMI");
_keyLevel = Param(nameof(KeyLevel), 20m)
.SetDisplay("Key Level", "ADX level required for entries", "DMI");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 4m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_adx = null;
_prevMacd = null;
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMacd = null;
_prevSignal = null;
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = Ma1Length },
LongMa = { Length = Ma2Length },
},
SignalMa = { Length = SignalLength },
};
var dmi = new DirectionalIndex { Length = DmiLength };
// ADX is the DX of the DmiLength directional lines smoothed over AdxSmoothing bars.
_adx = new SmoothedMovingAverage { Length = AdxSmoothing };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, dmi, ProcessCandle)
.Start();
StartProtection(
TakeProfitPercent > 0 ? new Unit(TakeProfitPercent, UnitTypes.Percent) : new Unit(),
StopLossPercent > 0 ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(),
useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, macd);
DrawIndicator(oscillators, dmi);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue dmiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!dmiValue.IsFormed)
return;
var dmi = (DirectionalIndexValue)dmiValue;
if (dmi.Plus is not decimal diPlus || dmi.Minus is not decimal diMinus)
return;
var diSum = diPlus + diMinus;
var dx = diSum == 0 ? 0m : 100m * Math.Abs(diPlus - diMinus) / diSum;
var adx = _adx.Process(dx, candle.ServerTime, true).ToDecimal();
if (!macdValue.IsFormed)
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
return;
var prevMacd = _prevMacd;
var prevSignal = _prevSignal;
_prevMacd = macd;
_prevSignal = signal;
if (!_adx.IsFormed || prevMacd is not decimal lastMacd || prevSignal is not decimal lastSignal)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var crossUp = lastMacd <= lastSignal && macd > signal;
var crossDown = lastMacd >= lastSignal && macd < signal;
if (crossUp && diPlus > diMinus && adx > KeyLevel && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDown && diMinus > diPlus && adx > KeyLevel && Position >= 0)
SellMarket(Volume + Math.Abs(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, Unit, UnitTypes
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal, DirectionalIndex, SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class macd_dmi_strategy(Strategy):
"""
MACD + DMI strategy.
The MACD line is the difference of Ma1Length and Ma2Length EMAs. A long opens when it crosses above its signal line
while +DI is above -DI and ADX is above KeyLevel; a short opens on the mirrored setup. The reverse signal flips the
position, and percent stop-loss and take-profit protection limit each trade.
"""
def __init__(self):
super(macd_dmi_strategy, self).__init__()
self._ma1_length = self.Param("Ma1Length", 10) \
.SetGreaterThanZero() \
.SetDisplay("MA1 Length", "Fast EMA period of MACD", "MACD")
self._ma2_length = self.Param("Ma2Length", 20) \
.SetGreaterThanZero() \
.SetDisplay("MA2 Length", "Slow EMA period of MACD", "MACD")
self._signal_length = self.Param("SignalLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("Signal Length", "MACD signal line period", "MACD")
self._dmi_length = self.Param("DmiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("DMI Length", "Directional movement period", "DMI")
self._adx_smoothing = self.Param("AdxSmoothing", 14) \
.SetGreaterThanZero() \
.SetDisplay("ADX Smoothing", "ADX smoothing period", "DMI")
self._key_level = self.Param("KeyLevel", 20.0) \
.SetDisplay("Key Level", "ADX level required for entries", "DMI")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 4.0) \
.SetNotNegative() \
.SetDisplay("Take Profit %", "Take-profit percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation", "General")
self._adx = None
self._prev_macd = None
self._prev_signal = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(macd_dmi_strategy, self).OnReseted()
self._adx = None
self._prev_macd = None
self._prev_signal = None
def OnStarted2(self, time):
super(macd_dmi_strategy, self).OnStarted2(time)
self._prev_macd = None
self._prev_signal = None
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._ma1_length.Value
macd.Macd.LongMa.Length = self._ma2_length.Value
macd.SignalMa.Length = self._signal_length.Value
dmi = DirectionalIndex()
dmi.Length = self._dmi_length.Value
# ADX is the DX of the DmiLength directional lines smoothed over AdxSmoothing bars.
self._adx = SmoothedMovingAverage()
self._adx.Length = self._adx_smoothing.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(macd, dmi, self._process_candle).Start()
tp = float(self._take_profit_percent.Value)
sl = float(self._stop_loss_percent.Value)
self.StartProtection(
Unit(Decimal(tp), UnitTypes.Percent) if tp > 0 else Unit(),
Unit(Decimal(sl), UnitTypes.Percent) if sl > 0 else Unit(),
useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, macd)
self.DrawIndicator(oscillators, dmi)
def _process_candle(self, candle, macd_value, dmi_value):
if candle.State != CandleStates.Finished:
return
if not dmi_value.IsFormed:
return
if dmi_value.Plus is None or dmi_value.Minus is None:
return
di_plus = float(dmi_value.Plus)
di_minus = float(dmi_value.Minus)
di_sum = di_plus + di_minus
dx = 0.0 if di_sum == 0 else 100.0 * abs(di_plus - di_minus) / di_sum
adx = float(to_decimal(process_float(self._adx, dx, candle.ServerTime, True)))
if not macd_value.IsFormed:
return
if macd_value.Macd is None or macd_value.Signal is None:
return
macd = float(macd_value.Macd)
signal = float(macd_value.Signal)
prev_macd = self._prev_macd
prev_signal = self._prev_signal
self._prev_macd = macd
self._prev_signal = signal
if not self._adx.IsFormed or prev_macd is None or prev_signal is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
key_level = float(self._key_level.Value)
cross_up = prev_macd <= prev_signal and macd > signal
cross_down = prev_macd >= prev_signal and macd < signal
if cross_up and di_plus > di_minus and adx > key_level and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_down and di_minus > di_plus and adx > key_level and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return macd_dmi_strategy()