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:
- Long/Short: Both
- Exit Criteria: MACD reversal
- Stops: Percent-based using
StopLossPercent - Default Values:
DonchianPeriod= 20MacdFast= 12MacdSlow= 26MacdSignal= 9StopLossPercent= 2mCandleType= 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;
}
}
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, Decimal
from StockSharp.Messages import UnitTypes, Unit, DataType, CandleStates
from StockSharp.Algo.Indicators import DonchianChannels, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class donchian_macd_strategy(Strategy):
"""
Strategy combining Donchian Channel breakout with MACD trend confirmation.
"""
def __init__(self):
super(donchian_macd_strategy, self).__init__()
self._previousHighest = 0.0
self._previousLowest = float("inf")
self._previousMacd = None
self._previousSignal = None
self._entryPrice = None
self._cooldown = 0
self._donchianPeriod = self.Param("DonchianPeriod", 20) \
.SetDisplay("Donchian Period", "Channel lookback period", "Indicators")
self._macdFast = self.Param("MacdFast", 12) \
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators")
self._macdSlow = self.Param("MacdSlow", 26) \
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators")
self._macdSignal = self.Param("MacdSignal", 9) \
.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators")
self._cooldownBars = self.Param("CooldownBars", 50) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._stopLossPercent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop-Loss %", "Stop-loss percentage from entry price", "Risk Management")
self._candleType = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def CandleType(self):
return self._candleType.Value
def OnReseted(self):
super(donchian_macd_strategy, self).OnReseted()
self._previousHighest = 0.0
self._previousLowest = float('inf')
self._previousMacd = None
self._previousSignal = None
self._entryPrice = None
self._cooldown = 0
def OnStarted2(self, time):
super(donchian_macd_strategy, self).OnStarted2(time)
self._previousHighest = 0.0
self._previousLowest = float('inf')
self._previousMacd = None
self._previousSignal = None
self._entryPrice = None
self._cooldown = 0
donchian = DonchianChannels()
donchian.Length = self._donchianPeriod.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macdFast.Value
macd.Macd.LongMa.Length = self._macdSlow.Value
macd.SignalMa.Length = self._macdSignal.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(donchian, macd, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, donchianValue, macdValue):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
signalValue = macdValue.Signal
macdDec = macdValue.Macd
if signalValue is None or macdDec is None:
# Update donchian values
if donchianValue.UpperBand is not None:
self._previousHighest = float(donchianValue.UpperBand)
if donchianValue.LowerBand is not None:
self._previousLowest = float(donchianValue.LowerBand)
self._previousMacd = macdDec
self._previousSignal = signalValue
return
macd_f = float(macdDec)
signal_f = float(signalValue)
# Determine MACD crosses
isBullishCross = False
isBearishCross = False
if self._previousMacd is not None and self._previousSignal is not None:
prev_m = float(self._previousMacd)
prev_s = float(self._previousSignal)
isBullishCross = prev_m <= prev_s and macd_f > signal_f
isBearishCross = prev_m >= prev_s and macd_f < signal_f
if self._cooldown > 0:
self._cooldown -= 1
price = float(candle.ClosePrice)
cooldown_val = int(self._cooldownBars.Value)
# Long entry
if self._cooldown == 0 and price > self._previousHighest * 1.001 and self.Position <= 0 and isBullishCross:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._entryPrice = price
self._cooldown = cooldown_val
# Short entry
elif self._cooldown == 0 and price < self._previousLowest * 0.999 and self.Position >= 0 and isBearishCross:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._entryPrice = price
self._cooldown = cooldown_val
# MACD trend reversal exit
elif (self.Position > 0 and isBearishCross) or (self.Position < 0 and isBullishCross):
self.ClosePosition()
self._entryPrice = None
self._cooldown = cooldown_val
# Update previous values
if donchianValue.UpperBand is not None:
self._previousHighest = float(donchianValue.UpperBand)
if donchianValue.LowerBand is not None:
self._previousLowest = float(donchianValue.LowerBand)
self._previousMacd = macdDec
self._previousSignal = signalValue
def CreateClone(self):
return donchian_macd_strategy()