Estratégia ADX DI
Estratégia baseada nos indicadores ADX e Movimento Direcional
Os testes indicam um retorno anual médio de aproximadamente 103%. Funciona melhor no mercado de ações.
ADX DI foca no cruzamento de +DI e -DI com ADX em alta. Um cruzamento altista de +DI sobre -DI combinado com ADX forte abre posições compradas, enquanto o oposto abre vendidas. As posições fecham com enfraquecimento do ADX ou cruzamento oposto.
Essa combinação ajuda a evitar operar em cada cruzamento de DI ao exigir confirmação do ADX. O sistema visa capturar tendências sustentáveis em vez de oscilações de curto prazo.
Detalhes
- Critérios de entrada: Sinais baseados em ADX, ATR.
- Comprado/Vendido: Ambos os direções.
- Critérios de saída: Sinal oposto ou stop.
- Stops: Sim.
- Valores padrão:
AdxPeriod= 14AdxThreshold= 25mAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoria: Tendência
- Direção: Ambos
- Indicadores: ADX, ATR
- Stops: Sim
- Complexidade: Básico
- Período: Intradiário (5m)
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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 ADX and Directional Movement indicators.
/// Trades strict DI crossings with rising strong ADX, exits on weakening/opposite crosses,
/// and protects actual fills with entry-frozen native ATR distances.
/// </summary>
public class AdxDiStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private decimal _prevPlusDi, _prevMinusDi, _prevAdx;
private bool _hasPrevValues;
private Order _pendingOrder, _entryOrder;
private Unit _stopDistance;
private decimal _requestedDistance;
private bool _protectionStarted;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX threshold for trend confirmation.
/// </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;
}
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
/// <summary>
/// Initializes a new instance of the <see cref="AdxDiStrategy"/>.
/// </summary>
public AdxDiStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
.SetOptimize(10, 20, 2);
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "ADX level to confirm trend", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14).SetGreaterThanZero()
.SetDisplay("ATR Period", "Wilder ATR lookback for entry-frozen protection.", "Protection");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m).SetNotNegative()
.SetDisplay("ATR Multiplier", "Signal ATR distance; zero disables the stop.", "Protection");
OrderRegistering += order => _pendingOrder = order;
Trades.TradeAdded += ProcessEntryFill;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPlusDi = _prevMinusDi = _prevAdx = 0m;
_hasPrevValues = default;
_pendingOrder = _entryOrder = null;
_stopDistance = null;
_requestedDistance = 0m;
_protectionStarted = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, atr, ProcessCandle, false)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before the callback, including between bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var adx = (IAverageDirectionalIndexValue)adxValue;
if (adx.MovingAverage is not decimal adxMain || adx.Dx.Plus is not decimal plusDi || adx.Dx.Minus is not decimal minusDi)
return;
if (!_hasPrevValues)
{
_hasPrevValues = true;
SavePrevious(plusDi, minusDi, adxMain);
return;
}
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
{
SavePrevious(plusDi, minusDi, adxMain);
return;
}
// Equality alone is not a bearish crossing.
var upCross = _prevPlusDi <= _prevMinusDi && plusDi > minusDi;
var downCross = _prevMinusDi <= _prevPlusDi && minusDi > plusDi;
var confirmed = adxMain >= AdxThreshold && adxMain > _prevAdx;
if (confirmed && upCross && Position <= 0m)
Enter(Sides.Buy, atrValue.GetValue<decimal>());
else if (confirmed && downCross && Position >= 0m)
Enter(Sides.Sell, atrValue.GetValue<decimal>());
else if (Position > 0m && (adxMain < _prevAdx || downCross))
SellMarket(Position);
else if (Position < 0m && (adxMain < _prevAdx || upCross))
BuyMarket(Math.Abs(Position));
SavePrevious(plusDi, minusDi, adxMain);
}
private void SavePrevious(decimal plus, decimal minus, decimal adx)
{
_prevPlusDi = plus;
_prevMinusDi = minus;
_prevAdx = adx;
}
private void Enter(Sides side, decimal atr)
{
_requestedDistance = atr * AtrMultiplier;
_stopDistance ??= new Unit(_requestedDistance);
if (Position == 0m) _stopDistance.Value = _requestedDistance;
if (!_protectionStarted && _requestedDistance > 0m)
{
StartProtection(new Unit(), _stopDistance, useMarketOrders: true, isLocalStop: true);
_protectionStarted = true;
}
_entryOrder = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = Volume + Math.Abs(Position),
Comment = "ADX signal",
};
RegisterOrder(_entryOrder);
}
private void ProcessEntryFill(MyTrade trade)
{
// Keep the old native distance until a partial reversal actually changes position direction.
if (trade.Order == _entryOrder && Position != 0m && (Position > 0m) == (trade.Order.Side == Sides.Buy))
_stopDistance.Value = _requestedDistance;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, OrderStates, OrderTypes, Sides, Level1Fields
from StockSharp.BusinessEntities import Subscription, Order
from StockSharp.Algo.Indicators import AverageDirectionalIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class adx_di_strategy(Strategy):
"""Strict DI crossings with rising ADX, weakening/opposite exits and actual-fill ATR protection."""
def __init__(self):
super(adx_di_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetDisplay("ADX Threshold", "ADX level to confirm trend", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero() \
.SetDisplay("ATR Period", "Wilder ATR lookback for entry-frozen protection.", "Protection")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative() \
.SetDisplay("ATR Multiplier", "Signal ATR distance; zero disables the stop.", "Protection")
self._reset_state()
self.OrderRegistering += self._track_pending
self.Trades.TradeAdded += self._process_entry_fill
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def _reset_state(self):
self._prev_plus_di = self._prev_minus_di = self._prev_adx = Decimal.Zero
self._has_prev_values = False
self._pending_order = self._entry_order = None
self._stop_distance = None
self._requested_distance = Decimal.Zero
self._protection_started = False
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def AdxThreshold(self):
return self._adx_threshold.Value
@AdxThreshold.setter
def AdxThreshold(self, value):
self._adx_threshold.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(adx_di_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
self.SubscribeCandles(self.CandleType) \
.BindEx(adx, atr, self.ProcessCandle, False) \
.Start()
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between signal bars.
pass
def ProcessCandle(self, candle, adx_value, atr_value):
if candle.State != CandleStates.Finished or not self.IsFormedAndOnlineAndAllowTrading():
return
if adx_value.IsEmpty:
return
adx_main = adx_value.MovingAverage
plus_di = adx_value.Dx.Plus
minus_di = adx_value.Dx.Minus
if adx_main is None or plus_di is None or minus_di is None:
return
if not self._has_prev_values:
self._has_prev_values = True
self._save_previous(plus_di, minus_di, adx_main)
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
self._save_previous(plus_di, minus_di, adx_main)
return
up_cross = self._prev_plus_di <= self._prev_minus_di and plus_di > minus_di
down_cross = self._prev_minus_di <= self._prev_plus_di and minus_di > plus_di
confirmed = adx_main >= Decimal(self.AdxThreshold) and adx_main > self._prev_adx
if confirmed and up_cross and self.Position <= 0:
self._enter(Sides.Buy, atr_value.GetValue[Decimal](None))
elif confirmed and down_cross and self.Position >= 0:
self._enter(Sides.Sell, atr_value.GetValue[Decimal](None))
elif self.Position > 0 and (adx_main < self._prev_adx or down_cross):
self.SellMarket(self.Position)
elif self.Position < 0 and (adx_main < self._prev_adx or up_cross):
self.BuyMarket(Math.Abs(self.Position))
self._save_previous(plus_di, minus_di, adx_main)
def _save_previous(self, plus, minus, adx):
self._prev_plus_di = plus
self._prev_minus_di = minus
self._prev_adx = adx
def _enter(self, side, atr):
self._requested_distance = atr * Decimal(self._atr_multiplier.Value)
if self._stop_distance is None:
self._stop_distance = Unit(self._requested_distance)
if self.Position == 0:
self._stop_distance.Value = self._requested_distance
if not self._protection_started and self._requested_distance > 0:
self.StartProtection(Unit(), self._stop_distance, useMarketOrders=True, isLocalStop=True)
self._protection_started = True
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = self.Volume + Math.Abs(self.Position)
order.Comment = "ADX signal"
self._entry_order = order
self.RegisterOrder(order)
def _process_entry_fill(self, trade):
# Preserve the old distance until an actual reversal fill changes direction.
if (trade.Order == self._entry_order and self.Position != 0
and (self.Position > 0) == (trade.Order.Side == Sides.Buy)):
self._stop_distance.Value = self._requested_distance
def OnReseted(self):
super(adx_di_strategy, self).OnReseted()
self._reset_state()
def CreateClone(self):
return adx_di_strategy()