Ver no GitHub

Estratégia de Cruzamento ADX

A Estratégia de Cruzamento ADX é construída em torno do indicador Average Directional Index (ADX). Ela analisa o cruzamento do índice direcional positivo (+DI) e do índice direcional negativo (-DI) para identificar possíveis mudanças de tendência.

Quando +DI cruza acima de -DI, a estratégia considera um sinal de alta e pode abrir posições compradas enquanto opcionalmente fecha posições vendidas existentes. De forma inversa, quando +DI cruza abaixo de -DI, é tratado como um sinal de baixa, provocando entradas vendidas e o fechamento opcional de posições compradas. Níveis opcionais de stop-loss e take-profit são suportados por gestão de risco integrada.

Indicador

A estratégia usa o indicador AverageDirectionalIndex do StockSharp. Apenas as linhas direcionais são necessárias; o valor principal do ADX não é usado na tomada de decisões.

Parâmetros

  • ADX Period – comprimento do cálculo do ADX. O padrão é 50.
  • Candle Type – período usado para a assinatura de candles. O padrão é 1 hora.
  • Allow Buy Open – habilitar a abertura de posições compradas. O padrão é true.
  • Allow Sell Open – habilitar a abertura de posições vendidas. O padrão é true.
  • Allow Buy Close – permitir fechar posições compradas no sinal de venda. O padrão é true.
  • Allow Sell Close – permitir fechar posições vendidas no sinal de compra. O padrão é true.
  • Stop Loss – distância do stop-loss em unidades de preço absolutas. O padrão é 1000.
  • Take Profit – distância do take-profit em unidades de preço absolutas. O padrão é 2000.

Lógica de trading

  1. Assinar candles do período selecionado e calcular o indicador ADX.
  2. Rastrear os valores anteriores de +DI e -DI para detectar cruzamentos.
  3. Em um cruzamento de alta (+DI cruza acima de -DI):
    • Fechar posição vendida se Allow Sell Close estiver habilitado.
    • Abrir posição comprada se Allow Buy Open estiver habilitado.
  4. Em um cruzamento de baixa (+DI cruza abaixo de -DI):
    • Fechar posição comprada se Allow Buy Close estiver habilitado.
    • Abrir posição vendida se Allow Sell Open estiver habilitado.
  5. Níveis protetores de stop-loss e take-profit são aplicados usando StartProtection.

Notas

  • Apenas candles concluídos (CandleStates.Finished) são processados.
  • A estratégia depende do gerenciamento de risco integrado do StockSharp para níveis de stop.
  • As posições são fechadas enviando uma ordem de mercado oposta com o volume atual.

Esta estratégia destina-se a fins educacionais e pode requerer ajuste adicional antes de ser usada em mercados reais.

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;
	}
}