View on GitHub

Donchian Macd Strategy

Strategy combining Donchian Channel breakout with MACD trend confirmation.

Testing indicates an average annual return of about 148%. It performs best in the forex market.

The strategy waits for a Donchian breakout and verifies momentum with MACD. Long or short trades ride the move once MACD agrees.

Aimed at breakout enthusiasts wanting confirmation. Stops are placed using an ATR multiplier.

Details

  • Entry Criteria:
    • Long: Price breaks Donchian high && MACD > Signal
    • Short: Price breaks Donchian low && MACD < Signal
  • Long/Short: Both
  • Exit Criteria: MACD reversal
  • Stops: Percent-based using StopLossPercent
  • Default Values:
    • DonchianPeriod = 20
    • MacdFast = 12
    • MacdSlow = 26
    • MacdSignal = 9
    • StopLossPercent = 2m
    • CandleType = TimeSpan.FromMinutes(5).TimeFrame()
  • Filters:
    • Category: Breakout
    • Direction: Both
    • Indicators: Donchian Channel, MACD
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Mid-term
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
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;

using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy combining Donchian Channel breakout with MACD trend confirmation.
/// </summary>
public class DonchianMacdStrategy : Strategy
{
	private readonly StrategyParam<int> _donchianPeriod;
	private readonly StrategyParam<int> _macdFast;
	private readonly StrategyParam<int> _macdSlow;
	private readonly StrategyParam<int> _macdSignal;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private DonchianChannels _donchian;
	private MovingAverageConvergenceDivergenceSignal _macd;
	
	private decimal? _previousHighest;
	private decimal? _previousLowest;
	private decimal? _previousMacd;
	private decimal? _previousSignal;
	private decimal? _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Donchian channel period.
	/// </summary>
	public int DonchianPeriod
	{
		get => _donchianPeriod.Value;
		set => _donchianPeriod.Value = value;
	}

	/// <summary>
	/// MACD fast period.
	/// </summary>
	public int MacdFast
	{
		get => _macdFast.Value;
		set => _macdFast.Value = value;
	}

	/// <summary>
	/// MACD slow period.
	/// </summary>
	public int MacdSlow
	{
		get => _macdSlow.Value;
		set => _macdSlow.Value = value;
	}

	/// <summary>
	/// MACD signal period.
	/// </summary>
	public int MacdSignal
	{
		get => _macdSignal.Value;
		set => _macdSignal.Value = value;
	}

	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Stop loss percentage.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

	/// <summary>
	/// Candle type for strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="DonchianMacdStrategy"/>.
	/// </summary>
	public DonchianMacdStrategy()
	{
		_donchianPeriod = Param(nameof(DonchianPeriod), 20)
			.SetRange(5, 50)
			
			.SetDisplay("Donchian Period", "Channel lookback period", "Indicators");

		_macdFast = Param(nameof(MacdFast), 12)
			.SetRange(8, 20)
			
			.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators");

		_macdSlow = Param(nameof(MacdSlow), 26)
			.SetRange(20, 40)
			
			.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators");

		_macdSignal = Param(nameof(MacdSignal), 9)
			.SetRange(5, 15)
			
			.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators");

		_cooldownBars = Param(nameof(CooldownBars), 50)
			.SetRange(1, 200)
			.SetDisplay("Cooldown Bars", "Bars between entries", "General");

		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetRange(1m, 5m)
			
			.SetDisplay("Stop-Loss %", "Stop-loss percentage from entry price", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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 OnReseted()
	{
		base.OnReseted();
		_previousHighest = 0;
		_previousLowest = decimal.MaxValue;
		_previousMacd = 0;
		_previousSignal = 0;
		_entryPrice = null;
		_cooldown = 0;
		_donchian = null;
		_macd = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Initialize indicators
		_donchian = new DonchianChannels
		{
			Length = DonchianPeriod
		};

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFast },
				LongMa = { Length = MacdSlow },
			},
			SignalMa = { Length = MacdSignal }
		};

		// Create subscription and bind indicators
		var subscription = SubscribeCandles(CandleType);
		
		subscription
			.BindEx(_donchian, _macd, ProcessCandle)
			.Start();

		// Setup chart visualization if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _donchian);
			DrawIndicator(area, _macd);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue donchianValue, IIndicatorValue macdValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;

		// Wait until strategy and indicators are ready
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
		var signalValue = macdTyped.Signal;
		var macdDec = macdTyped.Macd;
		var isBullishCross = _previousMacd <= _previousSignal && macdDec > signalValue;
		var isBearishCross = _previousMacd >= _previousSignal && macdDec < signalValue;

		if (_cooldown > 0)
		{
			_cooldown--;
		}

		// Check for breakouts with MACD trend confirmation
		// Long entry: Price breaks above Donchian high and MACD > Signal
		if (_cooldown == 0 && candle.ClosePrice > _previousHighest * 1.001m && Position <= 0 && isBullishCross)
		{
			CancelActiveOrders();
			
			var volume = Volume + Math.Abs(Position);
			BuyMarket(volume);
			_entryPrice = candle.ClosePrice;
			_cooldown = CooldownBars;
		}
		// Short entry: Price breaks below Donchian low and MACD < Signal
		else if (_cooldown == 0 && candle.ClosePrice < _previousLowest * 0.999m && Position >= 0 && isBearishCross)
		{
			CancelActiveOrders();
			
			var volume = Volume + Math.Abs(Position);
			SellMarket(volume);
			_entryPrice = candle.ClosePrice;
			_cooldown = CooldownBars;
		}
		// MACD trend reversal exit
		else if ((Position > 0 && isBearishCross) ||
				 (Position < 0 && isBullishCross))
		{
			ClosePosition();
			_entryPrice = null;
			_cooldown = CooldownBars;
		}

		var donchianTyped = (DonchianChannelsValue)donchianValue;

		// Update previous values for next candle
		_previousHighest = donchianTyped.UpperBand;
		_previousLowest = donchianTyped.LowerBand;
		_previousMacd = macdDec;
		_previousSignal = signalValue;
	}
}