DMI Winner 戦略
DMI Winner は、Directional Movement Index(DMI)に基づくトレンドフォロー戦略です。
+DI ラインと -DI ラインがクロスし、Average Directional Index(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()