Estrategia de Cruce ADX
La Estrategia de Cruce ADX está construida en torno al indicador Average Directional Index (ADX). Analiza el cruce del índice direccional positivo (+DI) y el índice direccional negativo (-DI) para identificar posibles cambios de tendencia.
Cuando +DI cruza por encima de -DI, la estrategia considera una señal alcista y puede abrir posiciones largas mientras cierra opcionalmente posiciones cortas existentes. A la inversa, cuando +DI cruza por debajo de -DI, se trata como una señal bajista, lo que provoca entradas cortas y el cierre opcional de posiciones largas. Se admiten niveles opcionales de stop-loss y take-profit mediante gestión de riesgo incorporada.
Indicador
La estrategia utiliza el indicador AverageDirectionalIndex de StockSharp. Solo se necesitan las líneas direccionales; el valor principal del ADX no se usa en la toma de decisiones.
Parámetros
ADX Period – longitud del cálculo del ADX. El valor predeterminado es 50.
Candle Type – marco temporal utilizado para la suscripción de velas. El valor predeterminado es 1 hora.
Allow Buy Open – habilitar la apertura de posiciones largas. El valor predeterminado es true.
Allow Sell Open – habilitar la apertura de posiciones cortas. El valor predeterminado es true.
Allow Buy Close – permitir cerrar posiciones largas en señal de venta. El valor predeterminado es true.
Allow Sell Close – permitir cerrar posiciones cortas en señal de compra. El valor predeterminado es true.
Stop Loss – distancia de stop-loss en unidades de precio absolutas. El valor predeterminado es 1000.
Take Profit – distancia de take-profit en unidades de precio absolutas. El valor predeterminado es 2000.
Lógica de trading
- Suscribirse a velas del marco temporal seleccionado y calcular el indicador ADX.
- Rastrear los valores anteriores de +DI y -DI para detectar cruces.
- En un cruce alcista (+DI cruza por encima de -DI):
- Cerrar posición corta si
Allow Sell Close está habilitado.
- Abrir posición larga si
Allow Buy Open está habilitado.
- En un cruce bajista (+DI cruza por debajo de -DI):
- Cerrar posición larga si
Allow Buy Close está habilitado.
- Abrir posición corta si
Allow Sell Open está habilitado.
- Los niveles de stop-loss y take-profit se aplican mediante
StartProtection.
Notas
- Solo se procesan velas completadas (
CandleStates.Finished).
- La estrategia se apoya en la gestión de riesgo incorporada de StockSharp para los niveles de stop.
- Las posiciones se cierran enviando una orden de mercado opuesta con el volumen actual.
Esta estrategia está destinada a fines educativos y puede requerir ajuste adicional antes de usarse en mercados reales.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on crossovers of the +DI and -DI lines of the ADX indicator.
/// It opens or closes positions when directional lines cross each other.
/// </summary>
public class AdxCrossingStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _allowBuyOpen;
private readonly StrategyParam<bool> _allowSellOpen;
private readonly StrategyParam<bool> _allowBuyClose;
private readonly StrategyParam<bool> _allowSellClose;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trendThreshold;
private decimal _prevPlusDi;
private decimal _prevMinusDi;
private bool _isInitialized;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Permission to open long positions.
/// </summary>
public bool AllowBuyOpen
{
get => _allowBuyOpen.Value;
set => _allowBuyOpen.Value = value;
}
/// <summary>
/// Permission to open short positions.
/// </summary>
public bool AllowSellOpen
{
get => _allowSellOpen.Value;
set => _allowSellOpen.Value = value;
}
/// <summary>
/// Permission to close long positions.
/// </summary>
public bool AllowBuyClose
{
get => _allowBuyClose.Value;
set => _allowBuyClose.Value = value;
}
/// <summary>
/// Permission to close short positions.
/// </summary>
public bool AllowSellClose
{
get => _allowSellClose.Value;
set => _allowSellClose.Value = value;
}
/// <summary>
/// Stop-loss in absolute price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take-profit in absolute price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Minimal ADX strength required to trade.
/// </summary>
public decimal TrendThreshold
{
get => _trendThreshold.Value;
set => _trendThreshold.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public AdxCrossingStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 50)
.SetDisplay("ADX Period", "Period of ADX indicator", "Indicators")
.SetOptimize(10, 100, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculations", "General");
_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
.SetDisplay("Allow Buy Open", "Enable opening long positions", "Permissions");
_allowSellOpen = Param(nameof(AllowSellOpen), true)
.SetDisplay("Allow Sell Open", "Enable opening short positions", "Permissions");
_allowBuyClose = Param(nameof(AllowBuyClose), true)
.SetDisplay("Allow Buy Close", "Enable closing long positions", "Permissions");
_allowSellClose = Param(nameof(AllowSellClose), true)
.SetDisplay("Allow Sell Close", "Enable closing short positions", "Permissions");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetDisplay("Stop Loss", "Absolute stop-loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetDisplay("Take Profit", "Absolute take-profit in price units", "Risk");
_trendThreshold = Param(nameof(TrendThreshold), 15m)
.SetDisplay("Trend Threshold", "Minimal ADX strength required to trade", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPlusDi = default;
_prevMinusDi = default;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawOwnTrades(area);
}
StartProtection(
stopLoss: StopLoss > 0m ? new Unit(StopLoss, UnitTypes.Absolute) : null,
takeProfit: TakeProfit > 0m ? new Unit(TakeProfit, UnitTypes.Absolute) : null);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var adx = (AverageDirectionalIndexValue)adxValue;
if (adx.Dx.Plus is not decimal plusDi || adx.Dx.Minus is not decimal minusDi)
return;
if (!_isInitialized)
{
_prevPlusDi = plusDi;
_prevMinusDi = minusDi;
_isInitialized = true;
return;
}
if (adx.MovingAverage is not decimal adxStrength || adxStrength < TrendThreshold)
{
_prevPlusDi = plusDi;
_prevMinusDi = minusDi;
return;
}
var buySignal = plusDi > minusDi && _prevPlusDi <= _prevMinusDi;
var sellSignal = plusDi < minusDi && _prevPlusDi >= _prevMinusDi;
if (buySignal)
{
if (AllowBuyOpen && Position <= 0)
BuyMarket(Position < 0 ? Volume + Math.Abs(Position) : Volume);
else if (AllowSellClose && Position < 0)
BuyMarket(Math.Abs(Position));
}
if (sellSignal)
{
if (AllowSellOpen && Position >= 0)
SellMarket(Position > 0 ? Volume + Position : Volume);
else if (AllowBuyClose && Position > 0)
SellMarket(Math.Abs(Position));
}
_prevPlusDi = plusDi;
_prevMinusDi = minusDi;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_crossing_strategy(Strategy):
def __init__(self):
super(adx_crossing_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 50) \
.SetDisplay("ADX Period", "Period of ADX indicator", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles for calculations", "General")
self._allow_buy_open = self.Param("AllowBuyOpen", True) \
.SetDisplay("Allow Buy Open", "Enable opening long positions", "Permissions")
self._allow_sell_open = self.Param("AllowSellOpen", True) \
.SetDisplay("Allow Sell Open", "Enable opening short positions", "Permissions")
self._allow_buy_close = self.Param("AllowBuyClose", True) \
.SetDisplay("Allow Buy Close", "Enable closing long positions", "Permissions")
self._allow_sell_close = self.Param("AllowSellClose", True) \
.SetDisplay("Allow Sell Close", "Enable closing short positions", "Permissions")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Absolute stop-loss in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Absolute take-profit in price units", "Risk")
self._trend_threshold = self.Param("TrendThreshold", 15.0) \
.SetDisplay("Trend Threshold", "Minimal ADX strength required to trade", "Indicators")
self._prev_plus_di = 0.0
self._prev_minus_di = 0.0
self._is_initialized = False
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AllowBuyOpen(self):
return self._allow_buy_open.Value
@AllowBuyOpen.setter
def AllowBuyOpen(self, value):
self._allow_buy_open.Value = value
@property
def AllowSellOpen(self):
return self._allow_sell_open.Value
@AllowSellOpen.setter
def AllowSellOpen(self, value):
self._allow_sell_open.Value = value
@property
def AllowBuyClose(self):
return self._allow_buy_close.Value
@AllowBuyClose.setter
def AllowBuyClose(self, value):
self._allow_buy_close.Value = value
@property
def AllowSellClose(self):
return self._allow_sell_close.Value
@AllowSellClose.setter
def AllowSellClose(self, value):
self._allow_sell_close.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def TrendThreshold(self):
return self._trend_threshold.Value
@TrendThreshold.setter
def TrendThreshold(self, value):
self._trend_threshold.Value = value
def OnStarted2(self, time):
super(adx_crossing_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.BindEx(adx, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, adx)
self.DrawOwnTrades(area)
sl = Unit(self.StopLoss, UnitTypes.Absolute) if self.StopLoss > 0 else None
tp = Unit(self.TakeProfit, UnitTypes.Absolute) if self.TakeProfit > 0 else None
self.StartProtection(stopLoss=sl, takeProfit=tp)
def ProcessCandle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
plus_raw = adx_value.Dx.Plus
minus_raw = adx_value.Dx.Minus
if plus_raw is None or minus_raw is None:
return
plus_di = float(plus_raw)
minus_di = float(minus_raw)
if not self._is_initialized:
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
self._is_initialized = True
return
ma_raw = adx_value.MovingAverage
if ma_raw is None:
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
adx_strength = float(ma_raw)
if adx_strength < float(self.TrendThreshold):
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
buy_signal = plus_di > minus_di and self._prev_plus_di <= self._prev_minus_di
sell_signal = plus_di < minus_di and self._prev_plus_di >= self._prev_minus_di
if buy_signal:
if self.AllowBuyOpen and self.Position <= 0:
volume = self.Volume + abs(self.Position) if self.Position < 0 else self.Volume
self.BuyMarket(volume)
elif self.AllowSellClose and self.Position < 0:
self.BuyMarket(abs(self.Position))
if sell_signal:
if self.AllowSellOpen and self.Position >= 0:
volume = self.Volume + self.Position if self.Position > 0 else self.Volume
self.SellMarket(volume)
elif self.AllowBuyClose and self.Position > 0:
self.SellMarket(abs(self.Position))
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
def OnReseted(self):
super(adx_crossing_strategy, self).OnReseted()
self._prev_plus_di = 0.0
self._prev_minus_di = 0.0
self._is_initialized = False
def CreateClone(self):
return adx_crossing_strategy()