DMI Power Move
Strategy based on DMI (Directional Movement Index) power moves
Testing indicates an average annual return of about 76%. It performs best in the forex market.
DMI Power Move combines directional indicator differences with ADX to catch powerful trends. Trades enter when +DI markedly exceeds -DI (or vice versa) and ADX is strong. They exit when ADX fades or the DI spread narrows.
This approach filters out weak signals by requiring both strong directional movement and rising ADX. The result is fewer, but potentially higher-quality, trend trades.
Details
- Entry Criteria: Signals based on ADX, ATR, DMI.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or stop.
- Stops: Yes.
- Default Values:
DmiPeriod= 14DiDifferenceThreshold= 5mAdxThreshold= 30mAdxExitThreshold= 25mAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(15)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: ADX, ATR, DMI
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday (15m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Strategy based on DMI (Directional Movement Index) power moves.
/// Enters long when +DI exceeds -DI by threshold and ADX is strong.
/// Enters short when -DI exceeds +DI by threshold and ADX is strong.
/// </summary>
public class DmiPowerMoveStrategy : Strategy
{
private readonly StrategyParam<int> _dmiPeriod;
private readonly StrategyParam<decimal> _diDifferenceThreshold;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
private int _prevSignal; // -1 bearish, 0 neutral, 1 bullish
/// <summary>
/// Period for DMI calculation.
/// </summary>
public int DmiPeriod
{
get => _dmiPeriod.Value;
set => _dmiPeriod.Value = value;
}
/// <summary>
/// Minimum difference between +DI and -DI to generate a signal.
/// </summary>
public decimal DiDifferenceThreshold
{
get => _diDifferenceThreshold.Value;
set => _diDifferenceThreshold.Value = value;
}
/// <summary>
/// Minimum ADX value to consider trend strong enough for entry.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the DMI Power Move strategy.
/// </summary>
public DmiPowerMoveStrategy()
{
_dmiPeriod = Param(nameof(DmiPeriod), 14)
.SetDisplay("DMI Period", "Period for DMI calculation", "Indicators")
.SetOptimize(10, 20, 2);
_diDifferenceThreshold = Param(nameof(DiDifferenceThreshold), 3m)
.SetDisplay("DI Difference Threshold", "Min difference between +DI and -DI", "Trading parameters")
.SetOptimize(2, 8, 1);
_adxThreshold = Param(nameof(AdxThreshold), 15m)
.SetDisplay("ADX Threshold", "Minimum ADX value for entry", "Trading parameters")
.SetOptimize(10, 25, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_prevSignal = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var dmi = new AverageDirectionalIndex { Length = DmiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(dmi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, dmi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue dmiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (dmiValue is not AverageDirectionalIndexValue adxTyped)
return;
decimal adx, plusDi, minusDi;
try
{
if (adxTyped.MovingAverage is not decimal a ||
adxTyped.Dx.Plus is not decimal p ||
adxTyped.Dx.Minus is not decimal m)
return;
adx = a;
plusDi = p;
minusDi = m;
}
catch (IndexOutOfRangeException)
{
return;
}
var diDiff = plusDi - minusDi;
// Determine current directional signal (ignoring neutral)
int signal;
if (diDiff > DiDifferenceThreshold && adx > AdxThreshold)
signal = 1; // bullish
else if (diDiff < -DiDifferenceThreshold && adx > AdxThreshold)
signal = -1; // bearish
else
signal = _prevSignal; // keep previous signal when neutral
// Trade only on directional change
if (signal != _prevSignal && signal != 0)
{
if (signal == 1 && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
}
else if (signal == -1 && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
}
_prevSignal = signal;
}
}
}
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 AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class dmi_power_move_strategy(Strategy):
"""
Strategy based on DMI (Directional Movement Index) power moves.
Enters long when +DI exceeds -DI by threshold and ADX is strong.
Enters short when -DI exceeds +DI by threshold and ADX is strong.
"""
def __init__(self):
super(dmi_power_move_strategy, self).__init__()
self._dmi_period = self.Param("DmiPeriod", 14) \
.SetDisplay("DMI Period", "Period for DMI calculation", "Indicators")
self._di_difference_threshold = self.Param("DiDifferenceThreshold", 3.0) \
.SetDisplay("DI Difference Threshold", "Min difference between +DI and -DI", "Trading parameters")
self._adx_threshold = self.Param("AdxThreshold", 15.0) \
.SetDisplay("ADX Threshold", "Minimum ADX value for entry", "Trading parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dmi_power_move_strategy, self).OnReseted()
self._prev_signal = 0
def OnStarted2(self, time):
super(dmi_power_move_strategy, self).OnStarted2(time)
dmi = AverageDirectionalIndex()
dmi.Length = self._dmi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(dmi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, dmi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, dmi_value):
if candle.State != CandleStates.Finished:
return
if dmi_value.MovingAverage is None:
return
if dmi_value.Dx is None or dmi_value.Dx.Plus is None or dmi_value.Dx.Minus is None:
return
adx = float(dmi_value.MovingAverage)
plus_di = float(dmi_value.Dx.Plus)
minus_di = float(dmi_value.Dx.Minus)
di_diff = plus_di - minus_di
if di_diff > self._di_difference_threshold.Value and adx > self._adx_threshold.Value:
signal = 1
elif di_diff < -self._di_difference_threshold.Value and adx > self._adx_threshold.Value:
signal = -1
else:
signal = self._prev_signal
if signal != self._prev_signal and signal != 0:
if signal == 1 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif signal == -1 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._prev_signal = signal
def CreateClone(self):
return dmi_power_move_strategy()