MACD + DMI Strategy
Combines the Moving Average Convergence Divergence with the Directional Movement Index to trade only when trend strength is confirmed. The system waits for a MACD crossover and checks that the dominant directional line exceeds the opposite line while the ADX is above a key level.
The strategy is designed for both long and short positions. By pairing momentum and trend filters, it aims to avoid whipsaws in sideways markets. Protective stops based on volatility keep risk contained.
Details
- Entry Criteria:
- Long: MACD line crosses above signal, +DI > -DI, and ADX above the key level.
- Short: MACD line crosses below signal, -DI > +DI, and ADX above the key level.
- Exit Criteria:
- Reverse signal or volatility stop hit.
- Indicators:
- MACD (fast 12, slow 26, signal 9)
- Directional Movement Index (length 14, ADX smoothing 14)
- Stops: Uses built-in stop-loss and take-profit via StartProtection.
- Default Values:
Ma1Length= 10Ma2Length= 20DmiLength= 14AdxSmoothing= 14KeyLevel= 20
- Filters:
- Trend following
- Works on multiple timeframes
- Indicators: MACD, DMI
- Stops: Yes
- Complexity: Moderate
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// MACD + DMI Strategy.
/// Uses MACD for momentum and DMI for directional confirmation.
/// Buys when MACD crosses above zero and DI+ > DI-.
/// Sells when MACD crosses below zero and DI- > DI+.
/// </summary>
public class MacdDmiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _dmiLength;
private readonly StrategyParam<int> _cooldownBars;
private MovingAverageConvergenceDivergence _macd;
private DirectionalIndex _dmi;
private decimal _prevMacd;
private int _cooldownRemaining;
public MacdDmiStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_dmiLength = Param(nameof(DmiLength), 14)
.SetGreaterThanZero()
.SetDisplay("DMI Length", "DMI period", "DMI");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int DmiLength
{
get => _dmiLength.Value;
set => _dmiLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_macd = null;
_dmi = null;
_prevMacd = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergence();
_dmi = new DirectionalIndex { Length = DmiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, _dmi, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue dmiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_macd.IsFormed || !_dmi.IsFormed)
return;
if (macdValue.IsEmpty)
return;
var macdVal = macdValue.ToDecimal();
var dmiTyped = (DirectionalIndexValue)dmiValue;
if (dmiTyped.Plus is not decimal diPlus || dmiTyped.Minus is not decimal diMinus)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevMacd = macdVal;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevMacd = macdVal;
return;
}
// MACD zero crossover + DMI direction
var macdCrossUp = macdVal > 0 && _prevMacd <= 0 && _prevMacd != 0;
var macdCrossDown = macdVal < 0 && _prevMacd >= 0 && _prevMacd != 0;
// Buy: MACD crosses above zero + DI+ > DI-
if (macdCrossUp && diPlus > diMinus && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: MACD crosses below zero + DI- > DI+
else if (macdCrossDown && diMinus > diPlus && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: DI- crosses above DI+
else if (Position > 0 && diMinus > diPlus && macdVal < 0)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: DI+ crosses above DI-
else if (Position < 0 && diPlus > diMinus && macdVal > 0)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevMacd = macdVal;
}
}
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 MovingAverageConvergenceDivergence, DirectionalIndex, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class macd_dmi_strategy(Strategy):
"""MACD + DMI Strategy. MACD zero crossover with DMI directional confirmation."""
def __init__(self):
super(macd_dmi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._dmi_length = self.Param("DmiLength", 14) \
.SetDisplay("DMI Length", "DMI period", "DMI")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._macd = None
self._dmi = None
self._prev_macd = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_dmi_strategy, self).OnReseted()
self._macd = None
self._dmi = None
self._prev_macd = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(macd_dmi_strategy, self).OnStarted2(time)
self._macd = MovingAverageConvergenceDivergence()
self._dmi = DirectionalIndex()
self._dmi.Length = int(self._dmi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._macd, self._dmi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, macd_value, dmi_value):
if candle.State != CandleStates.Finished:
return
if not self._macd.IsFormed or not self._dmi.IsFormed:
return
if macd_value.IsEmpty:
return
macd_val = float(IndicatorHelper.ToDecimal(macd_value))
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)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_macd = macd_val
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_macd = macd_val
return
cooldown = int(self._cooldown_bars.Value)
macd_cross_up = macd_val > 0 and self._prev_macd <= 0 and self._prev_macd != 0
macd_cross_down = macd_val < 0 and self._prev_macd >= 0 and self._prev_macd != 0
if macd_cross_up and di_plus > di_minus and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif macd_cross_down and di_minus > di_plus and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and di_minus > di_plus and macd_val < 0:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and di_plus > di_minus and macd_val > 0:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_macd = macd_val
def CreateClone(self):
return macd_dmi_strategy()