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
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>
/// Trades strong, rising ADX with a directional DI spread; exits on weakening or an ATR stop.
/// </summary>
public class DmiPowerMoveStrategy : Strategy
{
private readonly StrategyParam<int> _dmiPeriod;
private readonly StrategyParam<decimal> _diDifferenceThreshold;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<decimal> _adxExitThreshold;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousAdx;
private decimal _stopPrice;
public int DmiPeriod { get => _dmiPeriod.Value; set => _dmiPeriod.Value = value; }
public decimal DiDifferenceThreshold { get => _diDifferenceThreshold.Value; set => _diDifferenceThreshold.Value = value; }
public decimal AdxThreshold { get => _adxThreshold.Value; set => _adxThreshold.Value = value; }
public decimal AdxExitThreshold { get => _adxExitThreshold.Value; set => _adxExitThreshold.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public DmiPowerMoveStrategy()
{
_dmiPeriod = Param(nameof(DmiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("DMI Period", "Period for DMI and ATR", "Indicators");
_diDifferenceThreshold = Param(nameof(DiDifferenceThreshold), 5m)
.SetRange(0m, 100m)
.SetDisplay("DI Difference Threshold", "Minimum directional DI spread", "Trading");
_adxThreshold = Param(nameof(AdxThreshold), 30m)
.SetRange(0m, 100m)
.SetDisplay("ADX Threshold", "Minimum ADX for entry", "Trading");
_adxExitThreshold = Param(nameof(AdxExitThreshold), 25m)
.SetRange(0m, 100m)
.SetDisplay("ADX Exit Threshold", "Close when ADX falls below this value", "Trading");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Stop distance in entry ATR multiples", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_previousAdx = null;
_stopPrice = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var dmi = new AverageDirectionalIndex { Length = DmiPeriod };
var atr = new AverageTrueRange { Length = DmiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(dmi, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, dmi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue dmiValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished || !dmiValue.IsFormed || !atrValue.IsFormed)
return;
if (dmiValue is not AverageDirectionalIndexValue dmi
|| dmi.MovingAverage is not decimal adx || dmi.Dx.Plus is not decimal plus || dmi.Dx.Minus is not decimal minus)
return;
var rising = _previousAdx is decimal previous && adx > previous;
_previousAdx = adx;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var spread = plus - minus;
var stopped = _stopPrice != 0m && (Position > 0 ? candle.ClosePrice <= _stopPrice : candle.ClosePrice >= _stopPrice);
if (Position > 0 && (stopped || adx < AdxExitThreshold || spread <= DiDifferenceThreshold))
{
SellMarket(Position);
_stopPrice = 0m;
return;
}
if (Position < 0 && (stopped || adx < AdxExitThreshold || spread >= -DiDifferenceThreshold))
{
BuyMarket(-Position);
_stopPrice = 0m;
return;
}
var atr = atrValue.GetValue<decimal>();
if (Position != 0 || !rising || adx <= AdxThreshold || atr <= 0)
return;
if (spread > DiDifferenceThreshold)
{
BuyMarket();
_stopPrice = candle.ClosePrice - AtrMultiplier * atr;
}
else if (spread < -DiDifferenceThreshold)
{
SellMarket();
_stopPrice = candle.ClosePrice + AtrMultiplier * atr;
}
}
}
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 AverageDirectionalIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class dmi_power_move_strategy(Strategy):
"""Trades strong, rising ADX with a DI spread; exits on weakening or an ATR stop."""
def __init__(self):
super(dmi_power_move_strategy, self).__init__()
self._dmi_period = self.Param("DmiPeriod", 14) \
.SetDisplay("DMI Period", "Period for DMI and ATR", "Indicators")
self._di_difference_threshold = self.Param("DiDifferenceThreshold", 5.0) \
.SetDisplay("DI Difference Threshold", "Minimum directional DI spread", "Trading")
self._adx_threshold = self.Param("AdxThreshold", 30.0) \
.SetDisplay("ADX Threshold", "Minimum ADX for entry", "Trading")
self._adx_exit_threshold = self.Param("AdxExitThreshold", 25.0) \
.SetDisplay("ADX Exit Threshold", "Close when ADX falls below this value", "Trading")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "Stop distance in entry ATR multiples", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._previous_adx = None
self._stop_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(dmi_power_move_strategy, self).OnReseted()
self._previous_adx = None
self._stop_price = 0.0
def OnStarted2(self, time):
super(dmi_power_move_strategy, self).OnStarted2(time)
dmi = AverageDirectionalIndex()
dmi.Length = int(self._dmi_period.Value)
atr = AverageTrueRange()
atr.Length = int(self._dmi_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(dmi, atr, 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, atr_value):
if candle.State != CandleStates.Finished or not dmi.IsFormed or not atr_value.IsFormed:
return
if dmi.MovingAverage is None or dmi.Dx.Plus is None or dmi.Dx.Minus is None:
return
adx = float(dmi.MovingAverage)
spread = float(dmi.Dx.Plus) - float(dmi.Dx.Minus)
rising = self._previous_adx is not None and adx > self._previous_adx
self._previous_adx = adx
if not self.IsFormedAndOnlineAndAllowTrading():
return
price = float(candle.ClosePrice)
threshold = float(self._di_difference_threshold.Value)
stopped = self._stop_price != 0.0 and (price <= self._stop_price if self.Position > 0 else price >= self._stop_price)
if self.Position > 0 and (stopped or adx < float(self._adx_exit_threshold.Value) or spread <= threshold):
self.SellMarket(self.Position)
self._stop_price = 0.0
return
if self.Position < 0 and (stopped or adx < float(self._adx_exit_threshold.Value) or spread >= -threshold):
self.BuyMarket(-self.Position)
self._stop_price = 0.0
return
atr = float(atr_value.GetValue[Decimal](None))
if self.Position != 0 or not rising or adx <= float(self._adx_threshold.Value) or atr <= 0:
return
offset = float(self._atr_multiplier.Value) * atr
if spread > threshold:
self.BuyMarket()
self._stop_price = price - offset
elif spread < -threshold:
self.SellMarket()
self._stop_price = price + offset
def CreateClone(self):
return dmi_power_move_strategy()