Summary: Full MT4 EA implementing the legendary Turtle Trading System with 20/55-day Donchian channel breakouts, ATR-based unit position sizing, pyramiding entries, and anti-martingale capital management. Complete source code with all parameters explained.




# Turtle Trading System EA - Complete MQL4 Source Code with ATR Position Sizing

This article provides a fully functional Expert Advisor implementing the legendary Turtle Trading System, originally developed by Richard Dennis and William Eckhardt in the 1980s. The strategy produced over $100 million in profits for the original Turtles during their training period .

Strategy Overview



The Turtle Trading System is a pure trend-following strategy with two subsystems: System 1 (20-day breakout) and System 2 (55-day breakout). The EA uses Donchian channels for entry signals, ATR (Average True Range) for position sizing, and incorporates the anti-martingale principle - increasing position size only after winning trades .

Core Components



1. Donchian Channel Breakout: Enters when price exceeds highest high of N periods
2. ATR-Based Position Sizing: Risk = 1% of account / (ATR × $ value per point)
3. Pyramiding: Up to 4 incremental entries per direction
4. Anti-Martingale: Profit-based position scaling (only increase on wins)
5. Volatility-Based Stops: Initial stop at 2 ATR, trailing at channel breach

Complete MQL4 Code



```mql4
//+------------------------------------------------------------------+
//| TurtleSystemEA.mq4 |
//| Independent Compilation |
//| Based on Turtle Trading Rules |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link ""
#property version "2.00"
#property strict

//--- Input Parameters - System Selection
input int SystemChoice = 2; // 1=System1(20day), 2=System2(55day)
input int CustomBreakout = 0; // Custom breakout period (0=use system default)
input int CustomExit = 0; // Custom exit period (0=use system default)

//--- Position Sizing (Risk Management)
input double RiskPercent = 1.0; // Risk per trade (% of account)
input double ATRMultiplier = 2.0; // Initial stop as ATR multiple
input double MaxRiskPerUnit = 0.02; // Max risk per unit (2% default)
input double MaxDrawdownPercent = 30.0; // Max allowed drawdown (emergency stop)

//--- Pyramiding Settings
input int MaxAdds = 3; // Maximum pyramid entries (1-4)
input double AddDistance = 0.5; // ATR multiple between pyramid entries

//--- Anti-Martingale (Profit Scaling)
input bool UseAntiMartingale = true; // Increase size after winning trades
input int WinStreakRequired = 2; // Consecutive wins before scaling up
input double ScaleUpFactor = 0.25; // Position size increase factor (25%)

//--- Trailing Stop Parameters
input bool UseChandelierExit = true; // Exit on 10-day low/high
input int ChandelierPeriod = 10; // Period for exit channel

//--- Filter and Protection
input int MaxSpread = 35; // Maximum spread in pips
input int Slippage = 10; // Maximum slippage
input int MagicNumber = 198312; // EA magic number (Turtle year)
input string TradeComment = "Turtle v2";

//--- Global Variables
int breakoutPeriod = 20;
int exitPeriod = 10;
double unitSize = 0;
double currentATR = 0;
int consecutiveWins = 0;
int entriesThisSignal = 0;
datetime lastSignalTime = 0;
double lastTradeProfit = 0;
int pointMultiplier = 10;

//--- Trade tracking structures
struct TradeRecord {
int ticket;
datetime openTime;
double openPrice;
double unitNumber;
double stopLoss;
};
TradeRecord activeTrades[];

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Detect broker digit format
if(Digits == 3 || Digits == 5)
pointMultiplier = 10;
else
pointMultiplier = 1;

// Set system parameters based on selection
if(CustomBreakout > 0) {
breakoutPeriod = CustomBreakout;
} else if(SystemChoice == 1) {
breakoutPeriod = 20; // System 1: 20-day breakout
exitPeriod = 10; // System 1: 10-day exit
} else {
breakoutPeriod = 55; // System 2: 55-day breakout
exitPeriod = 20; // System 2: 20-day exit
}

if(CustomExit > 0) exitPeriod = CustomExit;

// Validate parameters
if(breakoutPeriod < 10) {
Print("Error: Breakout period must be at least 10");
return(INIT_PARAMETERS_INCORRECT);
}

Print("Turtle System EA initialized");
Print("Breakout period: ", breakoutPeriod, " | Exit period: ", exitPeriod);
Print("Risk per trade: ", RiskPercent, "% | Max adds: ", MaxAdds);

// Initialize array
ArrayResize(activeTrades, 0);

return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("Turtle System EA removed");
}

//+------------------------------------------------------------------+
//| Check for new bar |
//+------------------------------------------------------------------+
bool IsNewBar()
{
static datetime lastBarTime = 0;
datetime currentBarTime = iTime(Symbol(), 0, 0);
if(currentBarTime != lastBarTime) {
lastBarTime = currentBarTime;
return true;
}
return false;
}

//+------------------------------------------------------------------+
//| Calculate Donchian channel high |
//+------------------------------------------------------------------+
double GetDonchianHigh(int period, int shift)
{
double highest = 0;
for(int i = shift; i < shift + period; i++) {
double high = iHigh(Symbol(), 0, i);
if(high > highest || highest == 0)
highest = high;
}
return highest;
}

//+------------------------------------------------------------------+
//| Calculate Donchian channel low |
//+------------------------------------------------------------------+
double GetDonchianLow(int period, int shift)
{
double lowest = DBL_MAX;
for(int i = shift; i < shift + period; i++) {
double low = iLow(Symbol(), 0, i);
if(low < lowest)
lowest = low;
}
return lowest;
}

//+------------------------------------------------------------------+
//| Calculate ATR with Wilder smoothing |
//+------------------------------------------------------------------+
double CalculateATR(int period, int shift)
{
if(period < 1 || shift + period >= Bars) return 0;

double sumTR = 0;
for(int i = shift; i < shift + period; i++) {
double high = iHigh(Symbol(), 0, i);
double low = iLow(Symbol(), 0, i);
double prevClose = iClose(Symbol(), 0, i + 1);
double tr = MathMax(high, prevClose) - MathMin(low, prevClose);
sumTR += tr;
}

return sumTR / period;
}

//+------------------------------------------------------------------+
//| Calculate position unit size using Turtle formula |
//| Unit = 1% of account / (ATR × $ value per point) |
//+------------------------------------------------------------------+
double CalculateUnitSize()
{
currentATR = CalculateATR(14, 0);
if(currentATR <= 0) return 0.01;

double accountEquity = AccountBalance();
double riskAmount = accountEquity * RiskPercent / 100.0;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);

if(tickValue <= 0 || tickSize <= 0) return 0.01;

// Convert ATR to monetary value
double atrPoints = currentATR / Point / pointMultiplier;
double dollarRiskPerUnit = atrPoints * tickValue;

if(dollarRiskPerUnit <= 0) return 0.01;

double calculatedUnit = riskAmount / dollarRiskPerUnit;

// Round to allowed lot step
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
if(lotStep > 0) {
calculatedUnit = MathFloor(calculatedUnit / lotStep) * lotStep;
}

// Apply anti-martingale scaling if enabled
if(UseAntiMartingale && consecutiveWins >= WinStreakRequired) {
double scaleBonus = 1.0 + (consecutiveWins - WinStreakRequired + 1) * ScaleUpFactor;
calculatedUnit = calculatedUnit * MathMin(scaleBonus, 2.0); // Cap at 2x
}

// Min/max validation
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
if(calculatedUnit < minLot) calculatedUnit = minLot;
if(calculatedUnit > maxLot) calculatedUnit = maxLot;

return NormalizeDouble(calculatedUnit, 2);
}

//+------------------------------------------------------------------+
//| Check for breakout buy signal |
//+------------------------------------------------------------------+
bool IsBreakoutBuy()
{
double currentHigh = iHigh(Symbol(), 0, 1); // Previous bar high
double breakoutLevel = GetDonchianHigh(breakoutPeriod, 1); // N-period high excluding current bar

if(breakoutLevel <= 0) return false;

// Breakout when price exceeds the Donchian high
bool breakout = (currentHigh > breakoutLevel);

// Prevent multiple signals on same bar
if(breakout && Time[0] == lastSignalTime) return false;
if(breakout) lastSignalTime = Time[0];

return breakout;
}

//+------------------------------------------------------------------+
//| Check for breakout sell signal |
//+------------------------------------------------------------------+
bool IsBreakoutSell()
{
double currentLow = iLow(Symbol(), 0, 1); // Previous bar low
double breakoutLevel = GetDonchianLow(breakoutPeriod, 1); // N-period low excluding current bar

if(breakoutLevel >= DBL_MAX - 1) return false;

// Breakout when price falls below Donchian low
bool breakout = (currentLow < breakoutLevel);

if(breakout && Time[0] == lastSignalTime) return false;
if(breakout) lastSignalTime = Time[0];

return breakout;
}

//+------------------------------------------------------------------+
//| Check for exit signal - 10/20 day low/high breach |
//+------------------------------------------------------------------+
bool ShouldExitLong()
{
if(!UseChandelierExit) return false;

double currentLow = iLow(Symbol(), 0, 1);
double exitLevel = GetDonchianLow(exitPeriod, 1);

return (currentLow < exitLevel);
}

bool ShouldExitShort()
{
if(!UseChandelierExit) return false;

double currentHigh = iHigh(Symbol(), 0, 1);
double exitLevel = GetDonchianHigh(exitPeriod, 1);

return (currentHigh > exitLevel);
}

//+------------------------------------------------------------------+
//| Calculate pyramid entry price |
//| Add position every 0.5 ATR in profit direction |
//+------------------------------------------------------------------+
double GetPyramidBuyPrice(double entryPrice)
{
double addDistancePoints = AddDistance * currentATR / Point / pointMultiplier;
double nextLevel = entryPrice + addDistancePoints * Point * pointMultiplier;
return nextLevel;
}

double GetPyramidSellPrice(double entryPrice)
{
double addDistancePoints = AddDistance * currentATR / Point / pointMultiplier;
double nextLevel = entryPrice - addDistancePoints * Point * pointMultiplier;
return nextLevel;
}

//+------------------------------------------------------------------+
//| Calculate initial stop loss (2 ATR from entry) |
//+------------------------------------------------------------------+
double GetInitialStopLoss(int cmd, double entryPrice)
{
double stopDistance = ATRMultiplier * currentATR;
if(cmd == OP_BUY)
return entryPrice - stopDistance;
else
return entryPrice + stopDistance;
}

//+------------------------------------------------------------------+
//| Open market order |
//+------------------------------------------------------------------+
int OpenOrder(int cmd, double sl = 0, double tp = 0)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double lot = CalculateUnitSize();

if(lot <= 0) return -1;

// Auto-calculate stop loss if not provided
if(sl == 0 && currentATR > 0) {
sl = GetInitialStopLoss(cmd, price);
}

// Normalize prices to broker digits
sl = NormalizeDouble(sl, Digits);
if(tp > 0) tp = NormalizeDouble(tp, Digits);
price = NormalizeDouble(price, Digits);

int ticket = OrderSend(Symbol(), cmd, lot, price, Slippage, sl, tp, TradeComment, MagicNumber, 0, clrNONE);

if(ticket > 0) {
Print("Order opened: Ticket=", ticket, " | Direction=", (cmd==OP_BUY?"BUY":"SELL"), " | Lot=", lot);

// Record trade for pyramiding tracking
int newSize = ArraySize(activeTrades);
ArrayResize(activeTrades, newSize + 1);
activeTrades[newSize].ticket = ticket;
activeTrades[newSize].openTime = TimeCurrent();
activeTrades[newSize].openPrice = price;
activeTrades[newSize].unitNumber = entriesThisSignal + 1;
activeTrades[newSize].stopLoss = sl;

entriesThisSignal++;
} else {
Print("Order failed. Error: ", GetLastError());
}

return ticket;
}

//+------------------------------------------------------------------+
//| Close specific order by ticket |
//+------------------------------------------------------------------+
bool CloseOrder(int ticket)
{
if(OrderSelect(ticket, SELECT_BY_TICKET)) {
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
bool closed = OrderClose(ticket, OrderLots(), closePrice, Slippage, clrNONE);
if(closed) {
// Track profit for anti-martingale
double profit = (closePrice - OrderOpenPrice()) * OrderLots() * MarketInfo(Symbol(), MODE_TICKVALUE);
if(profit > 0) consecutiveWins++;
else consecutiveWins = 0;

Print("Order closed: Ticket=", ticket, " | Profit=", profit);
}
return closed;
}
return false;
}

//+------------------------------------------------------------------+
//| Close all positions |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
CloseOrder(OrderTicket());
}
}
}
entriesThisSignal = 0;
ArrayResize(activeTrades, 0);
}

//+------------------------------------------------------------------+
//| Close all positions in one direction |
//+------------------------------------------------------------------+
void ClosePositionsByType(int cmd)
{
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == cmd) {
CloseOrder(OrderTicket());
}
}
}
if((cmd == OP_BUY && CountPositions(OP_BUY) == 0) ||
(cmd == OP_SELL && CountPositions(OP_SELL) == 0)) {
entriesThisSignal = 0;
ArrayResize(activeTrades, 0);
}
}

//+------------------------------------------------------------------+
//| 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)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
if(type == -1 || OrderType() == type)
count++;
}
}
}
return count;
}

//+------------------------------------------------------------------+
//| Update trailing stops based on Donchian channel |
//+------------------------------------------------------------------+
void UpdateTrailingStops()
{
if(!UseChandelierExit) return;

double exitLevelLong = GetDonchianLow(exitPeriod, 1);
double exitLevelShort = GetDonchianHigh(exitPeriod, 1);

for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
double newSL = 0;

if(OrderType() == OP_BUY) {
if(exitLevelLong > OrderStopLoss() && exitLevelLong < Bid) {
newSL = exitLevelLong;
}
}
else if(OrderType() == OP_SELL) {
if(exitLevelShort < OrderStopLoss() && exitLevelShort > Ask) {
newSL = exitLevelShort;
}
}

if(newSL != 0 && newSL != OrderStopLoss()) {
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
Print("Trailing stop updated: Ticket=", OrderTicket(), " | New SL=", newSL);
}
}
}
}
}

//+------------------------------------------------------------------+
//| Check for pyramid entry opportunity |
//+------------------------------------------------------------------+
void CheckPyramidEntries()
{
if(entriesThisSignal >= MaxAdds) return;
if(currentATR <= 0) return;

int currentDirection = 0;
double avgPrice = 0;
int positionCount = 0;

// Determine current position direction and average price
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
if(OrderType() == OP_BUY) {
currentDirection = 1;
avgPrice += OrderOpenPrice() * OrderLots();
positionCount += OrderLots();
}
else if(OrderType() == OP_SELL) {
currentDirection = -1;
avgPrice += OrderOpenPrice() * OrderLots();
positionCount += OrderLots();
}
}
}
}

if(positionCount > 0) avgPrice = avgPrice / positionCount;

// Check pyramid conditions
if(currentDirection == 1) {
double targetPrice = GetPyramidBuyPrice(avgPrice);
if(Ask >= targetPrice && CountPositions(OP_BUY) < MaxAdds) {
Print("Pyramid BUY triggered at level ", entriesThisSignal + 1);
OpenOrder(OP_BUY);
}
}
else if(currentDirection == -1) {
double targetPrice = GetPyramidSellPrice(avgPrice);
if(Bid <= targetPrice && CountPositions(OP_SELL) < MaxAdds) {
Print("Pyramid SELL triggered at level ", entriesThisSignal + 1);
OpenOrder(OP_SELL);
}
}
}

//+------------------------------------------------------------------+
//| Check max drawdown protection |
//+------------------------------------------------------------------+
bool IsDrawdownExceeded()
{
double equity = AccountEquity();
double balance = AccountBalance();
if(balance <= 0) return false;

double drawdownPercent = (balance - equity) / balance * 100;
return (drawdownPercent >= MaxDrawdownPercent);
}

//+------------------------------------------------------------------+
//| Check if spread is acceptable |
//+------------------------------------------------------------------+
bool IsSpreadOK()
{
if(MaxSpread <= 0) return true;
int currentSpread = (int)((Ask - Bid) / Point / pointMultiplier);
return (currentSpread <= MaxSpread);
}

//+------------------------------------------------------------------+
//| Update ATR value on each tick |
//+------------------------------------------------------------------+
void UpdateATR()
{
currentATR = CalculateATR(14, 0);
}

//+------------------------------------------------------------------+
//| Check spread and drawdown before trading |
//+------------------------------------------------------------------+
bool CanTrade()
{
if(!IsSpreadOK()) return false;
if(IsDrawdownExceeded()) {
Print("Max drawdown exceeded. Trading halted.");
return false;
}
return true;
}

//+------------------------------------------------------------------+
//| Expert tick function - Main entry point |
//+------------------------------------------------------------------+
void OnTick()
{
// Update technical values
UpdateATR();

// Safety checks
if(!CanTrade()) return;

// Update trailing stops for existing positions
if(UseChandelierExit) {
UpdateTrailingStops();
}

// Only check for new signals on new bar
if(!IsNewBar()) {
// Check pyramid entries on every tick
if(MaxAdds > 0 && entriesThisSignal > 0 && entriesThisSignal < MaxAdds) {
CheckPyramidEntries();
}
return;
}

// Check exit signals first
if(ShouldExitLong() && CountPositions(OP_BUY) > 0) {
Print("Exit signal: Closing all LONG positions");
ClosePositionsByType(OP_BUY);
entriesThisSignal = 0;
}

if(ShouldExitShort() && CountPositions(OP_SELL) > 0) {
Print("Exit signal: Closing all SHORT positions");
ClosePositionsByType(OP_SELL);
entriesThisSignal = 0;
}

// Check breakout entry signals
if(IsBreakoutBuy() && CountPositions(OP_SELL) == 0) {
Print("Breakout BUY signal detected");
ClosePositionsByType(OP_SELL); // No short positions when going long
OpenOrder(OP_BUY);
}
else if(IsBreakoutSell() && CountPositions(OP_BUY) == 0) {
Print("Breakout SELL signal detected");
ClosePositionsByType(OP_BUY); // No long positions when going short
OpenOrder(OP_SELL);
}
}
//+------------------------------------------------------------------+
```

Parameter Explanation



| Parameter | Description | Turtle Original Values |
|-----------|-------------|------------------------|
| SystemChoice | 1=System1(20day breakout), 2=System2(55day breakout) | 1 or 2 |
| CustomBreakout | Override breakout period (0=use system default) | 0 |
| RiskPercent | Risk per trade as % of account | 1.0-2.0% |
| ATRMultiplier | Initial stop distance as ATR multiple | 2.0 |
| MaxAdds | Maximum pyramid entries per signal | 4 |
| AddDistance | ATR multiple between pyramid entries | 0.5 |
| UseAntiMartingale | Scale up position size after wins | true |
| WinStreakRequired | Consecutive wins before scaling | 2 |
| ScaleUpFactor | Position size increase factor | 0.25 |
| UseChandelierExit | Exit on channel breach | true |
| ChandelierPeriod | Period for exit channel (S1=10, S2=20) | 10/20 |
| MaxDrawdownPercent | Emergency stop on drawdown | 30 |

Installation Instructions



1. Copy code into MetaEditor (F4 in MT4)
2. Click Compile (F7) - verify zero errors
3. Attach to chart (Best on H1 or H4 timeframe)
4. Adjust parameters in Inputs tab
5. Enable AutoTrading (Alt+T)

Recommended Settings by Asset



| Asset | System | Risk % | Timeframe | Notes |
|-------|--------|--------|-----------|-------|
| EURUSD, GBPUSD | System 2 (55-day) | 1.0 | H1/H4 | Lower volatility, steady trends |
| Gold (XAUUSD) | System 1 (20-day) | 1.5 | H1 | Higher volatility, needs faster entry |
| GBPJPY, EURJPY | System 1 (20-day) | 1.0 | H1 | Strong trends, good for pyramiding |
| USDJPY | System 2 (55-day) | 1.0 | H4 | Slower trends, fewer false breaks |

Compilation & Modification Tips



Understanding the Turtle Rules:
  • The original Turtles used "1% risk per trade" - position size = 1% of account / (2 ATR × $ per point)

  • Pyramiding adds positions every 0.5 ATR in profit direction (up to 4 total)

  • Exit occurs when price breaches 10-day low (System 1) or 20-day low (System 2)


  • Key customizations:
  • Adjust RiskPercent based on account size (1-2% recommended for trend followers)

  • Increase MaxAdds to 4 for full Turtle pyramiding

  • Set UseChandelierExit = false to use fixed ATR-based stop instead

  • Lower AddDistance to 0.25 ATR for more aggressive pyramiding


  • Best market conditions:
    The Turtle System is designed for trending markets with sustained directional movement. It performs poorly in ranging/choppy markets. Use on higher timeframes (H1-H4) for best results.

    Reference



    This EA source code is independently compiled based on the original Turtle Trading System rules documented by Richard Dennis and William Eckhardt. Additional implementations referenced from open-source repositories on MQL5 and other trading platforms .

    *For professionally optimized EA strategies with advanced machine learning filters, complete backtest portfolios across 20+ instruments, and premium support, check our paid EA collection. Subscribe for weekly updates and exclusive trading tools.*