View on GitHub

ADX Crossing Strategy

The ADX Crossing Strategy is built around the Average Directional Index (ADX) indicator. It analyzes the crossover of the positive directional index (+DI) and the negative directional index (-DI) to identify potential trend shifts.

When +DI crosses above -DI, the strategy considers it a bullish signal and can open long positions while optionally closing any existing short positions. Conversely, when +DI crosses below -DI, it is treated as a bearish signal, prompting short entries and optional closure of long positions. Optional stop-loss and take-profit levels are supported through built-in risk management.

Indicator

The strategy uses the AverageDirectionalIndex indicator from StockSharp. Only the directional lines are needed; the ADX main value is not used in decision making.

Parameters

  • ADX Period – length of the ADX calculation. Default is 50.
  • Candle Type – timeframe used for candle subscription. Default is 1 hour.
  • Allow Buy Open – enable opening long positions. Default is true.
  • Allow Sell Open – enable opening short positions. Default is true.
  • Allow Buy Close – allow closing long positions on sell signal. Default is true.
  • Allow Sell Close – allow closing short positions on buy signal. Default is true.
  • Stop Loss – stop-loss distance in absolute price units. Default is 1000.
  • Take Profit – take-profit distance in absolute price units. Default is 2000.

Trading Logic

  1. Subscribe to candles of the selected timeframe and compute the ADX indicator.
  2. Track the previous values of +DI and -DI to detect crossovers.
  3. On a bullish crossover (+DI crosses above -DI):
    • Close short position if Allow Sell Close is enabled.
    • Open long position if Allow Buy Open is enabled.
  4. On a bearish crossover (+DI crosses below -DI):
    • Close long position if Allow Buy Close is enabled.
    • Open short position if Allow Sell Open is enabled.
  5. Protective stop-loss and take-profit levels are applied using StartProtection.

Notes

  • Only completed candles (CandleStates.Finished) are processed.
  • The strategy relies on built-in StockSharp risk management for stop levels.
  • Positions are closed by sending an opposite market order with the current volume.

This strategy is intended for educational purposes and may require further tuning before being used on live markets.

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