Стратегия DMI Winner
DMI Winner — трендовая стратегия на основе индекса направленного движения
(DMI). Сделки открываются при пересечении линий +DI и -DI, когда ADX
превышает ключевой уровень, что указывает на сильный тренд.
Опциональный фильтр скользящей средней помогает торговать в направлении долгосрочного тренда. Стоп‑лосс можно включить, но по умолчанию выход происходит при обратном пересечении индикатора.
Подробности
- Данные: свечи цены.
- Условия входа:
- Лонг:
+DIпересекает-DIснизу иADX>KeyLevel(с учётом фильтра MA). - Шорт:
-DIпересекает+DIиADX>KeyLevel(с учётом фильтра MA).
- Лонг:
- Условия выхода: обратное пересечение DI или срабатывание стоп‑лосса.
- Стопы: опциональный стоп‑лосс (
UseSL). - Параметры по умолчанию:
DILength= 14KeyLevel= 23UseMA= TrueUseSL= False
- Фильтры:
- Категория: следование тренду
- Направление: лонг и шорт
- Индикаторы: DMI, Moving Average
- Сложность: средняя
- Уровень риска: средний
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>
/// DMI Winner strategy.
/// Goes long when +DI crosses above -DI and short when -DI crosses above +DI, both only while ADX is above KeyLevel
/// and, when UseMA is set, price is on the trade side of the moving average. The opposite DI cross closes the position
/// (and reverses it when it is a valid entry), and an optional percent stop-loss caps the risk.
/// </summary>
public class DmiWinnerStrategy : Strategy
{
private readonly StrategyParam<int> _diLength;
private readonly StrategyParam<int> _adxSmoothing;
private readonly StrategyParam<decimal> _keyLevel;
private readonly StrategyParam<bool> _useMa;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<bool> _useSl;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _adx;
private decimal? _prevDiPlus;
private decimal? _prevDiMinus;
/// <summary>
/// Directional indicator period.
/// </summary>
public int DILength
{
get => _diLength.Value;
set => _diLength.Value = value;
}
/// <summary>
/// ADX smoothing period.
/// </summary>
public int ADXSmoothing
{
get => _adxSmoothing.Value;
set => _adxSmoothing.Value = value;
}
/// <summary>
/// ADX level a crossover needs to be traded.
/// </summary>
public decimal KeyLevel
{
get => _keyLevel.Value;
set => _keyLevel.Value = value;
}
/// <summary>
/// Enable the moving average trend filter.
/// </summary>
public bool UseMA
{
get => _useMa.Value;
set => _useMa.Value = value;
}
/// <summary>
/// Moving average period.
/// </summary>
public int MALength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Enable the stop-loss.
/// </summary>
public bool UseSL
{
get => _useSl.Value;
set => _useSl.Value = value;
}
/// <summary>
/// Stop-loss percentage from the entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DmiWinnerStrategy()
{
_diLength = Param(nameof(DILength), 14)
.SetGreaterThanZero()
.SetDisplay("DI Length", "Directional indicator period", "DMI");
_adxSmoothing = Param(nameof(ADXSmoothing), 13)
.SetGreaterThanZero()
.SetDisplay("ADX Smoothing", "ADX smoothing period", "DMI");
_keyLevel = Param(nameof(KeyLevel), 23m)
.SetDisplay("Key Level", "ADX level a crossover needs to be traded", "DMI");
_useMa = Param(nameof(UseMA), true)
.SetDisplay("Use MA", "Trade only on the trend side of the moving average", "Moving Average");
_maLength = Param(nameof(MALength), 50)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average period", "Moving Average");
_useSl = Param(nameof(UseSL), false)
.SetDisplay("Use Stop Loss", "Enable the percent stop-loss", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "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;
_prevDiPlus = null;
_prevDiMinus = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevDiPlus = null;
_prevDiMinus = null;
var dmi = new DirectionalIndex { Length = DILength };
// ADX is the DX of the DILength directional lines smoothed over ADXSmoothing bars.
_adx = new SmoothedMovingAverage { Length = ADXSmoothing };
var ma = new ExponentialMovingAverage { Length = MALength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(dmi, ma, ProcessCandle)
.Start();
if (UseSL)
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
var dmiArea = CreateChartArea();
if (dmiArea != null)
DrawIndicator(dmiArea, dmi);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue dmiValue, IIndicatorValue maValue)
{
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();
var prevPlus = _prevDiPlus;
var prevMinus = _prevDiMinus;
_prevDiPlus = diPlus;
_prevDiMinus = diMinus;
if (!_adx.IsFormed || !maValue.IsFormed || prevPlus is not decimal lastPlus || prevMinus is not decimal lastMinus)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma = maValue.ToDecimal();
var close = candle.ClosePrice;
var crossUp = lastPlus <= lastMinus && diPlus > diMinus;
var crossDown = lastPlus >= lastMinus && diPlus < diMinus;
var longSignal = crossUp && adx > KeyLevel && (!UseMA || close > ma);
var shortSignal = crossDown && adx > KeyLevel && (!UseMA || close < ma);
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && crossDown)
SellMarket(Position);
else if (Position < 0 && crossUp)
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import DirectionalIndex, SmoothedMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class dmi_winner_strategy(Strategy):
"""
DMI Winner strategy.
Goes long when +DI crosses above -DI and short when -DI crosses above +DI, both only while ADX is above KeyLevel
and, when UseMA is set, price is on the trade side of the moving average. The opposite DI cross closes the position
(and reverses it when it is a valid entry), and an optional percent stop-loss caps the risk.
"""
def __init__(self):
super(dmi_winner_strategy, self).__init__()
self._di_length = self.Param("DILength", 14) \
.SetGreaterThanZero() \
.SetDisplay("DI Length", "Directional indicator period", "DMI")
self._adx_smoothing = self.Param("ADXSmoothing", 13) \
.SetGreaterThanZero() \
.SetDisplay("ADX Smoothing", "ADX smoothing period", "DMI")
self._key_level = self.Param("KeyLevel", 23.0) \
.SetDisplay("Key Level", "ADX level a crossover needs to be traded", "DMI")
self._use_ma = self.Param("UseMA", True) \
.SetDisplay("Use MA", "Trade only on the trend side of the moving average", "Moving Average")
self._ma_length = self.Param("MALength", 50) \
.SetGreaterThanZero() \
.SetDisplay("MA Length", "Moving average period", "Moving Average")
self._use_sl = self.Param("UseSL", False) \
.SetDisplay("Use Stop Loss", "Enable the percent stop-loss", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "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_di_plus = None
self._prev_di_minus = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(dmi_winner_strategy, self).OnReseted()
self._adx = None
self._prev_di_plus = None
self._prev_di_minus = None
def OnStarted2(self, time):
super(dmi_winner_strategy, self).OnStarted2(time)
self._prev_di_plus = None
self._prev_di_minus = None
dmi = DirectionalIndex()
dmi.Length = self._di_length.Value
# ADX is the DX of the DILength directional lines smoothed over ADXSmoothing bars.
self._adx = SmoothedMovingAverage()
self._adx.Length = self._adx_smoothing.Value
ma = ExponentialMovingAverage()
ma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(dmi, ma, self._process_candle).Start()
if self._use_sl.Value:
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
dmi_area = self.CreateChartArea()
if dmi_area is not None:
self.DrawIndicator(dmi_area, dmi)
def _process_candle(self, candle, dmi_value, ma_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)))
prev_plus = self._prev_di_plus
prev_minus = self._prev_di_minus
self._prev_di_plus = di_plus
self._prev_di_minus = di_minus
if not self._adx.IsFormed or not ma_value.IsFormed or prev_plus is None or prev_minus is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ma = float(ma_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
key_level = float(self._key_level.Value)
use_ma = self._use_ma.Value
cross_up = prev_plus <= prev_minus and di_plus > di_minus
cross_down = prev_plus >= prev_minus and di_plus < di_minus
long_signal = cross_up and adx > key_level and (not use_ma or close > ma)
short_signal = cross_down and adx > key_level and (not use_ma or close < ma)
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and cross_down:
self.SellMarket(self.Position)
elif self.Position < 0 and cross_up:
self.BuyMarket(-self.Position)
def CreateClone(self):
return dmi_winner_strategy()