Estrategia DMI Winner
DMI Winner es una estrategia de seguimiento de tendencia basada en el Índice de
Movimiento Direccional (DMI). Abre operaciones cuando las líneas +DI y -DI se
cruzan y el Índice de Dirección Promedio (ADX) sube por encima de un umbral clave,
señalando una tendencia fuerte.
Un filtro de media móvil opcional mantiene las operaciones en la dirección de la tendencia más amplia. También se puede habilitar un stop-loss para limitar el riesgo a la baja, aunque por defecto el sistema se basa en las reversiones de señal para las salidas.
Detalles
- Datos: Velas de precio.
- Criterios de entrada:
- Largo:
+DIcruza por encima de-DIYADX>KeyLevel(con filtro de MA opcional). - Corto:
-DIcruza por encima de+DIYADX>KeyLevel(con filtro de MA opcional).
- Largo:
- Criterios de salida: Cruce de DI opuesto o stop-loss si está habilitado.
- Stops: Stop-loss opcional (
UseSL). - Valores predeterminados:
DILength= 14KeyLevel= 23UseMA= TrueUseSL= False
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Largo/Corto
- Indicadores: DMI, Moving Average
- Complejidad: Moderado
- Nivel de riesgo: Medio
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()