# Donchian Channel Breakout EA with ATR Filter - Complete MQL4 Source Code
This article provides a fully functional Expert Advisor based on the Donchian Channel breakout strategy, a cornerstone of the legendary Turtle Trading System developed by Richard Dennis and William Eckhardt in the 1980s . The original system proved that trading could be taught systematically, achieving over $100 million in profits within five years.
Strategy Logic
The Donchian Channel is a volatility-based indicator that tracks the highest high and lowest low over a specified period. When price breaks above the channel high, a long signal is generated; when price breaks below the channel low, a short signal is generated .
How the Algorithm Works
This EA implements a dual-confirmation approach:
1. Breakout Detection: Entry triggered when price closes beyond Donchian Channel boundaries
2. Volatility Confirmation: ATR filter ensures sufficient market volatility before entry, filtering out false breakouts in low-volatility environments
3. Trend Confirmation: Optional EMA filter aligns trades with the broader market direction
4. Risk Management: Fixed or ATR-based stop loss with multiple exit strategies including trailing stop and time-based exits
Complete MQL4 Code
```mql4
//+------------------------------------------------------------------+
//| Donchian_Breakout_EA.mq4 |
//| Independent Compilation |
//| Based on Turtle Trading System |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link ""
#property version "1.00"
#property strict
//--- Input Parameters
input double LotSize = 0.1; // Fixed lot size
input double RiskPercent = 1.5; // Risk % of balance (0=use fixed lot)
input int DonchianPeriod = 20; // Donchian Channel period
input int ATRPeriod = 14; // ATR period for filter
input double MinATR = 0; // Minimum ATR value in pips (0=disable)
input bool UseTrendFilter = true; // Enable EMA trend filter
input int TrendEMAPeriod = 200; // Trend filter EMA period
input int StopLossMethod = 1; // SL: 1=ATR multiple, 2=Fixed pips
input double ATRMultiplier = 2.0; // ATR multiplier for stop loss
input int FixedStopLoss = 80; // Fixed stop loss in pips
input int TakeProfit = 150; // Take profit in pips (0=disabled)
input int TrailingStop = 40; // Trailing stop in pips (0=off)
input int BreakEvenTrigger = 30; // Breakeven trigger in pips (0=off)
input int MaxSpread = 35; // Maximum spread in pips
input int MaxDailyTrades = 3; // Maximum trades per day
input int Slippage = 10; // Maximum slippage
input int MagicNumber = 202610; // EA magic number
input bool CloseOpposite = true; // Close opposite on new signal
input bool UseHeikinAshi = false; // Use Heikin Ashi close for entry
//--- Global variables
double donchianHigh = 0, donchianLow = 0;
double atrValue = 0;
int pointMultiplier = 10;
datetime lastTradeDate = 0;
int tradesToday = 0;
datetime lastSignalTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Detect broker digit format
if(Digits == 3 || Digits == 5)
pointMultiplier = 10;
else if(Digits == 2 || Digits == 4)
pointMultiplier = 1;
// Validate parameters
if(DonchianPeriod < 5)
{
Print("Error: Donchian period must be at least 5");
return(INIT_PARAMETERS_INCORRECT);
}
if(ATRPeriod < 2)
{
Print("Error: ATR period must be at least 2");
return(INIT_PARAMETERS_INCORRECT);
}
Print("Donchian Breakout EA initialized");
Print("Donchian Period: ", DonchianPeriod, " | ATR Period: ", ATRPeriod);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("Donchian Breakout EA removed");
}
//+------------------------------------------------------------------+
//| Check if new day has started |
//+------------------------------------------------------------------+
void UpdateDailyTradeCount()
{
datetime currentTime = TimeCurrent();
MqlDateTime dt;
TimeToStruct(currentTime, dt);
currentTime = StringToTime(TimeToString(currentTime));
if(lastTradeDate == 0)
{
lastTradeDate = currentTime;
tradesToday = 0;
}
else if(currentTime > lastTradeDate + 86400)
{
lastTradeDate = currentTime;
tradesToday = 0;
}
}
//+------------------------------------------------------------------+
//| Get close price (standard or Heikin Ashi) |
//+------------------------------------------------------------------+
double GetClosePrice(int shift)
{
if(UseHeikinAshi && shift >= 0 && shift < Bars)
{
double haClose = (Open[shift] + High[shift] + Low[shift] + Close[shift]) / 4.0;
return haClose;
}
return Close[shift];
}
//+------------------------------------------------------------------+
//| Calculate Donchian Channel high |
//+------------------------------------------------------------------+
double GetDonchianHigh(int period, int shift)
{
if(shift + period >= Bars) return 0;
double highest = -1;
for(int i = shift; i < shift + period; i++)
{
double h = High[i];
if(h > highest) highest = h;
}
return highest;
}
//+------------------------------------------------------------------+
//| Calculate Donchian Channel low |
//+------------------------------------------------------------------+
double GetDonchianLow(int period, int shift)
{
if(shift + period >= Bars) return 0;
double lowest = DBL_MAX;
for(int i = shift; i < shift + period; i++)
{
double l = Low[i];
if(l < lowest) lowest = l;
}
return lowest;
}
//+------------------------------------------------------------------+
//| Calculate ATR |
//+------------------------------------------------------------------+
double CalculateATR(int period, int shift)
{
if(period < 2 || shift + period >= Bars) return 0;
double sumTR = 0;
for(int i = shift; i < shift + period; i++)
{
double high = High[i];
double low = Low[i];
double prevClose = (i + 1 < Bars) ? Close[i + 1] : Close[i];
double tr = MathMax(high, prevClose) - MathMin(low, prevClose);
sumTR += tr;
}
return sumTR / period;
}
//+------------------------------------------------------------------+
//| Calculate ATR-based stop loss |
//+------------------------------------------------------------------+
double GetATRStopLoss(int direction, double entryPrice)
{
double atrPoints = atrValue / Point / pointMultiplier;
double stopDistance = atrPoints * ATRMultiplier;
if(direction == OP_BUY)
return entryPrice - stopDistance * Point * pointMultiplier;
else
return entryPrice + stopDistance * Point * pointMultiplier;
}
//+------------------------------------------------------------------+
//| Check if volatility is sufficient |
//+------------------------------------------------------------------+
bool IsVolatilityOK()
{
if(MinATR <= 0) return true;
double atrPoints = atrValue / Point / pointMultiplier;
return (atrPoints >= MinATR);
}
//+------------------------------------------------------------------+
//| Check trend direction using EMA |
//+------------------------------------------------------------------+
int GetTrendDirection()
{
if(!UseTrendFilter) return 0;
double emaValue = iMA(Symbol(), 0, TrendEMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
double currentClose = GetClosePrice(0);
if(currentClose > emaValue)
return 1; // Bullish
else if(currentClose < emaValue)
return -1; // Bearish
return 0;
}
//+------------------------------------------------------------------+
//| Check for buy breakout signal |
//+------------------------------------------------------------------+
bool IsBuySignal()
{
// Calculate indicators
donchianHigh = GetDonchianHigh(DonchianPeriod, 1);
double currentClose = GetClosePrice(0);
double prevClose = GetClosePrice(1);
if(donchianHigh <= 0) return false;
// Breakout condition: price closes above Donchian high
bool breakout = (prevClose <= donchianHigh && currentClose > donchianHigh);
if(!breakout) return false;
// ATR filter
if(!IsVolatilityOK()) return false;
// Trend filter - only buy in uptrend
if(UseTrendFilter && GetTrendDirection() != 1) return false;
// Prevent duplicate signals on same bar
if(lastSignalTime == Time[0]) return false;
lastSignalTime = Time[0];
return true;
}
//+------------------------------------------------------------------+
//| Check for sell breakout signal |
//+------------------------------------------------------------------+
bool IsSellSignal()
{
// Calculate indicators
donchianLow = GetDonchianLow(DonchianPeriod, 1);
double currentClose = GetClosePrice(0);
double prevClose = GetClosePrice(1);
if(donchianLow <= 0) return false;
// Breakout condition: price closes below Donchian low
bool breakout = (prevClose >= donchianLow && currentClose < donchianLow);
if(!breakout) return false;
// ATR filter
if(!IsVolatilityOK()) return false;
// Trend filter - only sell in downtrend
if(UseTrendFilter && GetTrendDirection() != -1) return false;
// Prevent duplicate signals on same bar
if(lastSignalTime == Time[0]) return false;
lastSignalTime = Time[0];
return true;
}
//+------------------------------------------------------------------+
//| Calculate lot size based on risk percentage |
//+------------------------------------------------------------------+
double CalculateLotSize(int direction, double entryPrice)
{
if(RiskPercent <= 0)
return LotSize;
double accountBalance = AccountBalance();
double riskAmount = accountBalance * RiskPercent / 100.0;
// Determine stop distance
double stopDistance = 0;
if(StopLossMethod == 1)
{
double atrPoints = atrValue / Point / pointMultiplier;
stopDistance = atrPoints * ATRMultiplier;
}
else
{
stopDistance = FixedStopLoss;
}
if(stopDistance <= 0) return LotSize;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(tickValue <= 0 || lotStep <= 0) return LotSize;
double calculatedLot = riskAmount / (stopDistance * tickValue);
calculatedLot = MathFloor(calculatedLot / lotStep) * lotStep;
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
if(calculatedLot < minLot) calculatedLot = minLot;
if(calculatedLot > maxLot) calculatedLot = maxLot;
return NormalizeDouble(calculatedLot, 2);
}
//+------------------------------------------------------------------+
//| Open market order |
//+------------------------------------------------------------------+
void OpenOrder(int cmd)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;
// Calculate stop loss
if(StopLossMethod == 1)
{
sl = GetATRStopLoss(cmd, price);
}
else if(FixedStopLoss > 0)
{
if(cmd == OP_BUY)
sl = price - FixedStopLoss * Point * pointMultiplier;
else
sl = price + FixedStopLoss * Point * pointMultiplier;
}
// Calculate take profit
if(TakeProfit > 0)
{
if(cmd == OP_BUY)
tp = price + TakeProfit * Point * pointMultiplier;
else
tp = price - TakeProfit * Point * pointMultiplier;
}
double lot = CalculateLotSize(cmd, price);
int ticket = OrderSend(Symbol(), cmd, lot, price, Slippage, sl, tp, "Donchian EA", MagicNumber, 0, clrNONE);
if(ticket < 0)
{
Print("OrderSend failed. Error: ", GetLastError());
}
else
{
Print("Order opened. Ticket: ", ticket, " | Lot: ", lot, " | Direction: ", cmd == OP_BUY ? "BUY" : "SELL");
tradesToday++;
}
}
//+------------------------------------------------------------------+
//| Close all positions |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);
}
}
}
}
//+------------------------------------------------------------------+
//| Close opposite positions |
//+------------------------------------------------------------------+
void CloseOppositePositions(int newDirection)
{
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
bool isOpposite = (newDirection == OP_BUY && OrderType() == OP_SELL) ||
(newDirection == OP_SELL && OrderType() == OP_BUY);
if(isOpposite)
{
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Count positions by type |
//+------------------------------------------------------------------+
int CountPositions(int type = -1)
{
int count = 0;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
if(type == -1 || OrderType() == type)
count++;
}
}
}
return count;
}
//+------------------------------------------------------------------+
//| Manage trailing stop |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
if(TrailingStop <= 0) return;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double trailPoints = TrailingStop * Point * pointMultiplier;
if(OrderType() == OP_BUY)
{
double newSL = Bid - trailPoints;
if(newSL > OrderStopLoss() && OrderStopLoss() != 0)
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
else if(OrderType() == OP_SELL)
{
double newSL = Ask + trailPoints;
if((newSL < OrderStopLoss() || OrderStopLoss() == 0) && newSL > 0)
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Manage breakeven stop loss |
//+------------------------------------------------------------------+
void ManageBreakeven()
{
if(BreakEvenTrigger <= 0) return;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
double breakevenPoints = BreakEvenTrigger * Point * pointMultiplier;
if(OrderType() == OP_BUY)
{
double profitPoints = (Bid - OrderOpenPrice()) / Point / pointMultiplier;
if(profitPoints >= BreakEvenTrigger && OrderStopLoss() < OrderOpenPrice())
{
OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, clrNONE);
Print("Breakeven triggered for BUY #", OrderTicket());
}
}
else if(OrderType() == OP_SELL)
{
double profitPoints = (OrderOpenPrice() - Ask) / Point / pointMultiplier;
if(profitPoints >= BreakEvenTrigger && (OrderStopLoss() > OrderOpenPrice() || OrderStopLoss() == 0))
{
OrderModify(OrderTicket(), OrderOpenPrice(), OrderOpenPrice(), OrderTakeProfit(), 0, clrNONE);
Print("Breakeven triggered for SELL #", OrderTicket());
}
}
}
}
}
}
//+------------------------------------------------------------------+
//| Check if spread is within limit |
//+------------------------------------------------------------------+
bool IsSpreadOK()
{
if(MaxSpread <= 0) return true;
int currentSpread = (int)((Ask - Bid) / Point / pointMultiplier);
return (currentSpread <= MaxSpread);
}
//+------------------------------------------------------------------+
//| Check daily trade limit |
//+------------------------------------------------------------------+
bool IsDailyLimitOK()
{
return (tradesToday < MaxDailyTrades);
}
//+------------------------------------------------------------------+
//| Update trailing stop for buy positions |
//+------------------------------------------------------------------+
void UpdateBuyTrailingStop()
{
if(TrailingStop <= 0) return;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_BUY)
{
double trailPoints = TrailingStop * Point * pointMultiplier;
double newSL = Bid - trailPoints;
if(newSL > OrderStopLoss() && newSL > OrderOpenPrice())
{
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Update trailing stop for sell positions |
//+------------------------------------------------------------------+
void UpdateSellTrailingStop()
{
if(TrailingStop <= 0) return;
for(int i = 0; i < OrdersTotal(); i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == OP_SELL)
{
double trailPoints = TrailingStop * Point * pointMultiplier;
double newSL = Ask + trailPoints;
if((newSL < OrderStopLoss() || OrderStopLoss() == 0) && newSL < OrderOpenPrice())
{
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Update daily trade counter
UpdateDailyTradeCount();
// Spread check
if(!IsSpreadOK())
return;
// Update indicators
atrValue = CalculateATR(ATRPeriod, 0);
// Manage existing positions
if(TrailingStop > 0)
{
UpdateBuyTrailingStop();
UpdateSellTrailingStop();
}
ManageBreakeven();
// Only check for new signals on new bar
static datetime lastBarTime = 0;
datetime currentBarTime = Time[0];
if(currentBarTime == lastBarTime)
return;
lastBarTime = currentBarTime;
// Check daily limit
if(!IsDailyLimitOK())
return;
// Check for buy signal
if(IsBuySignal())
{
if(CloseOpposite)
CloseOppositePositions(OP_BUY);
if(CountPositions(OP_BUY) == 0)
OpenOrder(OP_BUY);
}
// Check for sell signal
else if(IsSellSignal())
{
if(CloseOpposite)
CloseOppositePositions(OP_SELL);
if(CountPositions(OP_SELL) == 0)
OpenOrder(OP_SELL);
}
}
//+------------------------------------------------------------------+
```
Parameter Explanation
| Parameter | Description | Recommended |
|-----------|-------------|-------------|
| DonchianPeriod | Lookback period for channel high/low | 20 (classic), 55 (long-term) |
| ATRPeriod | Period for volatility calculation | 14 |
| MinATR | Minimum ATR threshold in pips (0=disable) | 10-20 |
| UseTrendFilter | Enable EMA200 trend filter | true |
| StopLossMethod | 1=ATR multiple, 2=Fixed pips | 1 |
| ATRMultiplier | ATR multiplier for stop distance | 2.0 |
| FixedStopLoss | Fixed SL in pips (method 2) | 60-100 |
| TakeProfit | Fixed TP in pips (0=disabled) | 0 or 150-200 |
| TrailingStop | Trailing stop distance in pips | 30-50 |
| BreakEvenTrigger | Pips to move SL to breakeven | 20-40 |
| MaxDailyTrades | Maximum trades per day | 1-3 |
| MaxSpread | Maximum allowed spread | 30-40 |
| CloseOpposite | Close opposite on new signal | true |
| UseHeikinAshi | Smoothed entry confirmation | false |
Installation Instructions
1. Copy the code into MetaEditor (press F4 in MT4)
2. Click Compile (F7) - verify zero errors
3. Attach EA to a chart (EURUSD, GBPUSD, XAUUSD recommended)
4. Adjust parameters in Inputs tab
5. Enable AutoTrading (Alt+T)
Recommended Settings by Timeframe
| Timeframe | Donchian Period | ATR Multiplier | Trailing Stop |
|-----------|-----------------|----------------|---------------|
| M15 (Scalping) | 10-20 | 1.5 | 15-25 |
| H1 (Day Trading) | 20 | 2.0 | 30-40 |
| H4 (Swing) | 20-55 | 2.5 | 50-70 |
| D1 (Position) | 55 | 2.5-3.0 | 70-100 |
Compilation & Modification Tips
Key modifications to consider:
Best market conditions:
This breakout strategy performs best in trending markets with sustained directional movement. Avoid using during low-volatility consolidation phases or major news events .
Reference
This EA source code is independently compiled based on the Turtle Trading System principles documented in market literature. The original Donchian Channel breakout method was popularized by Richard Dennis and William Eckhardt in the 1980s, with the MT4 implementation following standard MQL4 practices .
*For professionally optimized EA strategies with advanced multi-timeframe analysis, machine learning market regime detection, complete backtest reports, and premium support, check our paid EA collection. Subscribe for weekly updates and exclusive trading tools.*