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



  • <strong>Donchian Channel Breakout</strong>: Enters when price exceeds highest high of N periods

  • <strong>ATR-Based Position Sizing</strong>: Risk = 1% of account / (ATR × $ value per point)

  • <strong>Pyramiding</strong>: Up to 4 incremental entries per direction

  • <strong>Anti-Martingale</strong>: Profit-based position scaling (only increase on wins)

  • <strong>Volatility-Based Stops</strong>: 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



  • Copy code into MetaEditor (F4 in MT4)

  • Click Compile (F7) - verify zero errors

  • Attach to chart (Best on H1 or H4 timeframe)

  • Adjust parameters in Inputs tab

  • 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.