Estratégia ADX Donchian
Esta estratégia utiliza os indicadores ADX Donchian para gerar sinais. A entrada comprada ocorre quando ADX > AdxThreshold && Price >= upperBorder (tendência forte com rompimento para cima). A entrada vendida ocorre quando ADX > AdxThreshold && Price <= lowerBorder (tendência forte com rompimento para baixo). É adequada para traders que buscam oportunidades em mercados mistos.
Os testes indicam um retorno anual médio de aproximadamente 67%. Funciona melhor no mercado de ações.
Detalhes
- Critérios de entrada:
- Comprado: ADX > AdxThreshold && Price >= upperBorder (strong trend with breakout up)
- Vendido: ADX > AdxThreshold && Price <= lowerBorder (strong trend with breakout down)
- Comprado/Vendido: Ambos os lados.
- Critérios de saída:
- Comprado: Sair da posição quando ADX cai abaixo de (threshold - 5)
- Vendido: Sair da posição quando ADX cai abaixo de (threshold - 5)
- Stops: Sim.
- Valores padrão:
AdxPeriod= 14DonchianPeriod= 5StopLossPercent= 2CandleType= TimeSpan.FromMinutes(5)AdxThreshold= 10Multiplier= 0.1 upperBorder é a máxima dos últimos DonchianPeriod candles, incluindo o atual, reduzida em Multiplier por cento, e lowerBorder é a mínima elevada em Multiplier por cento. O stop é um StopLossPercent fixo do preço de entrada, vigiado também entre os candles. Um sinal de entrada contra uma posição aberta a inverte.
- Filtros:
- Categoria: Misto
- Direção: Ambos
- Indicadores: ADX Donchian
- Stops: Sim
- Complexidade: Intermediário
- Período: Intradiário
- 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>
/// ADX Donchian strategy.
/// The borders sit Multiplier percent inside the DonchianPeriod channel, which includes the current candle. With ADX above AdxThreshold
/// a close at or above the upper border goes long and one at or below the lower border goes short, reversing an opposite position.
/// The position closes once ADX falls below AdxThreshold minus 5, and a percent stop limits the loss.
/// </summary>
public class AdxDonchianStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _adxThreshold;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Period of ADX.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Candles the channel spans.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// ADX value for strong trend detection.
/// </summary>
public int AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Percent the borders sit inside the channel.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdxDonchianStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period of ADX", "Indicators");
_donchianPeriod = Param(nameof(DonchianPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Candles the channel spans", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 10)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "ADX value for strong trend detection", "Indicators");
_multiplier = Param(nameof(Multiplier), 0.1m)
.SetNotNegative()
.SetDisplay("Multiplier", "Percent the borders sit inside the channel", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var donchian = new DonchianChannels { Length = DonchianPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, donchian, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
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 area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, adx);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue donchianValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFormed || !donchianValue.IsFormed)
return;
if (adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal strength })
return;
if (donchianValue is not IDonchianChannelsValue { UpperBand: decimal upper, LowerBand: decimal lower })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var upperBorder = upper * (1 - Multiplier / 100);
var lowerBorder = lower * (1 + Multiplier / 100);
var strong = strength > AdxThreshold;
var close = candle.ClosePrice;
if (strong && close >= upperBorder && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (strong && close <= lowerBorder && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && strength < AdxThreshold - 5)
SellMarket(Position);
else if (Position < 0 && strength < AdxThreshold - 5)
BuyMarket(-Position);
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import AverageDirectionalIndex, DonchianChannels
from StockSharp.Algo.Strategies import Strategy
class adx_donchian_strategy(Strategy):
"""
ADX Donchian strategy.
The borders sit Multiplier percent inside the DonchianPeriod channel, which includes the current candle. With ADX above AdxThreshold
a close at or above the upper border goes long and one at or below the lower border goes short, reversing an opposite position.
The position closes once ADX falls below AdxThreshold minus 5, and a percent stop limits the loss.
"""
def __init__(self):
super(adx_donchian_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "Period of ADX", "Indicators")
self._donchian_period = self.Param("DonchianPeriod", 5).SetGreaterThanZero().SetDisplay("Donchian Period", "Candles the channel spans", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 10).SetGreaterThanZero().SetDisplay("ADX Threshold", "ADX value for strong trend detection", "Indicators")
self._multiplier = self.Param("Multiplier", 0.1).SetNotNegative().SetDisplay("Multiplier", "Percent the borders sit inside the channel", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(adx_donchian_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
donchian = DonchianChannels()
donchian.Length = self._donchian_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, donchian, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
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()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, adx_value, donchian_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFormed or not donchian_value.IsFormed:
return
if adx_value.MovingAverage is None or donchian_value.UpperBand is None or donchian_value.LowerBand is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
strength = adx_value.MovingAverage
threshold = Decimal(self._adx_threshold.Value)
shift = Decimal(self._multiplier.Value) / Decimal(100)
upper_border = donchian_value.UpperBand * (Decimal(1) - shift)
lower_border = donchian_value.LowerBand * (Decimal(1) + shift)
strong = strength > threshold
weak = strength < threshold - Decimal(5)
close = candle.ClosePrice
if strong and close >= upper_border and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif strong and close <= lower_border and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and weak:
self.SellMarket(self.Position)
elif self.Position < 0 and weak:
self.BuyMarket(-self.Position)
def CreateClone(self):
return adx_donchian_strategy()