Summary: Professional MT4 EA implementing a classic single moving average crossover strategy. Features compound position sizing based on margin percentage, drawdown reduction module, and simple yet effective entry/exit logic. Complete source code included.




# Classic Single Moving Average Crossover EA - Complete MQL4 Source Code

This article provides a fully functional Expert Advisor based on the classic single moving average crossover strategy. This EA is distributed as a sample with MetaTrader 4 and has become a foundational learning tool for algorithmic trading . The strategy uses a single moving average as both entry signal generator and exit trigger.

Strategy Logic



The EA implements a price-cross-MA strategy that is elegant in its simplicity. Unlike complex indicators like Bollinger Bands or RSI, this approach relies on pure price action relative to a smoothed average .

How the Entry Logic Works



  • BUY Signal: When the previous bar`s Open price is below the MA AND the previous bar`s Close price is above the MA (upward crossover)

  • SELL Signal: When the previous bar`s Open price is above the MA AND the previous bar`s Close price is below the MA (downward crossover)


  • The system checks conditions only on new bar formation using `Volume[0] > 1` detection, ensuring each signal is processed only once .

    Position Sizing System



    This EA implements two advanced position sizing mechanisms:

    1. Compound Position Sizing: `Lot = AccountFreeMargin() * MaximumRisk / 1000.0` - Automatically adjusts lot size based on available margin, enabling compound growth
    2. Drawdown Control Module: When consecutive losing trades occur, the EA reduces position size proportionally using the `DecreaseFactor` parameter, implementing a "win more when winning, lose less when losing" approach

    Complete MQL4 Code



    ```mql4
    //+------------------------------------------------------------------+
    //| SingleMA_Trend.mq4 |
    //| Independent Compilation |
    //| Based on Classic MA Strategy |
    //+------------------------------------------------------------------+
    #property copyright "AI Assistant"
    #property link ""
    #property version "1.00"
    #property strict

    //--- Magic number for order identification
    #define MAGICMA 20260715

    //--- Input Parameters
    input double FixedLots = 0.1; // Fixed lot size (baseline)
    input double MaximumRisk = 0.02; // Maximum risk % of free margin (2% = 0.02)
    input double DecreaseFactor = 3.0; // Drawdown reduction factor
    input int MAPeriod = 12; // Moving average period
    input int MAShift = 6; // Moving average shift
    input int Slippage = 3; // Maximum slippage
    input bool UseCloseSignal = true; // Use MA for exit signals
    input int MagicNumber = 20260715; // EA magic number
    input int MaxSpread = 30; // Maximum allowed spread in pips

    //--- Global variables
    int pointMultiplier = 10;
    datetime lastSignalTime = 0;

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

    if(MAPeriod < 2)
    {
    Print("Error: MA period must be at least 2");
    return(INIT_PARAMETERS_INCORRECT);
    }

    Print("Single MA Trend EA initialized successfully");
    Print("MA Period: ", MAPeriod, " | MA Shift: ", MAShift);
    Print("Risk Management: MaxRisk=", MaximumRisk*100, "% | DecreaseFactor=", DecreaseFactor);

    return(INIT_SUCCEEDED);
    }

    //+------------------------------------------------------------------+
    //| Expert deinitialization function |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
    Print("Single MA Trend EA removed. Reason: ", reason);
    }

    //+------------------------------------------------------------------+
    //| Calculate optimized lot size with compound and drawdown control |
    //+------------------------------------------------------------------+
    double CalculateOptimizedLotSize()
    {
    double lot = FixedLots;

    //--- Compound position sizing based on free margin
    // Formula: Lot = FreeMargin * Risk% / 1000
    // The divisor 1000 creates reasonable lot increments
    double marginLot = NormalizeDouble(AccountFreeMargin() * MaximumRisk / 1000.0, 2);

    if(marginLot > lot)
    lot = marginLot;

    //--- Drawdown control module
    // When consecutive losses occur, reduce position size
    if(DecreaseFactor > 0)
    {
    int totalHistory = OrdersHistoryTotal();
    int consecutiveLosses = 0;

    // Count consecutive losses from most recent trades
    for(int i = totalHistory - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
    {
    Print("Error selecting history order");
    break;
    }

    // Skip non-matching symbol or pending orders
    if(OrderSymbol() != Symbol() || OrderType() > OP_SELL)
    continue;

    // Stop counting when hitting a profitable trade
    if(OrderProfit() > 0)
    break;

    if(OrderProfit() < 0)
    consecutiveLosses++;
    }

    // Reduce lot size based on consecutive losses
    if(consecutiveLosses > 1)
    {
    double reduction = lot * consecutiveLosses / DecreaseFactor;
    lot = NormalizeDouble(lot - reduction, 2);
    Print("Drawdown control active: ", consecutiveLosses, " consecutive losses, reduced lot to ", lot);
    }
    }

    //--- Ensure minimum lot size (broker minimum or 0.01)
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    if(minLot == 0) minLot = 0.01;

    if(lot < minLot)
    lot = minLot;

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

    return NormalizeDouble(lot, 2);
    }

    //+------------------------------------------------------------------+
    //| Check spread condition |
    //+------------------------------------------------------------------+
    bool IsSpreadOK()
    {
    if(MaxSpread <= 0) return true;

    int currentSpread = (int)((Ask - Bid) / Point / pointMultiplier);
    bool spreadOK = (currentSpread <= MaxSpread);

    if(!spreadOK)
    Print("Spread too high: ", currentSpread, " (max allowed: ", MaxSpread, ")");

    return spreadOK;
    }

    //+------------------------------------------------------------------+
    //| Calculate moving average value |
    //+------------------------------------------------------------------+
    double GetMA(int shift)
    {
    return iMA(Symbol(), 0, MAPeriod, MAShift, MODE_SMA, PRICE_CLOSE, shift);
    }

    //+------------------------------------------------------------------+
    //| Check for buy signal - price crosses above MA |
    //+------------------------------------------------------------------+
    bool IsBuySignal()
    {
    double maValue = GetMA(0);

    // Classic buy condition: previous bar opened below MA and closed above MA
    bool condition = (Open[1] < maValue && Close[1] > maValue);

    // Ensure signal is on a new bar to avoid multiple triggers
    if(condition && Time[0] != lastSignalTime)
    return true;

    return false;
    }

    //+------------------------------------------------------------------+
    //| Check for sell signal - price crosses below MA |
    //+------------------------------------------------------------------+
    bool IsSellSignal()
    {
    double maValue = GetMA(0);

    // Classic sell condition: previous bar opened above MA and closed below MA
    bool condition = (Open[1] > maValue && Close[1] < maValue);

    // Ensure signal is on a new bar to avoid multiple triggers
    if(condition && Time[0] != lastSignalTime)
    return true;

    return false;
    }

    //+------------------------------------------------------------------+
    //| Check if buy position should be closed |
    //+------------------------------------------------------------------+
    bool ShouldCloseBuy()
    {
    if(!UseCloseSignal) return false;

    double maValue = GetMA(0);

    // Close buy when price crosses back below MA
    return (Open[1] > maValue && Close[1] < maValue);
    }

    //+------------------------------------------------------------------+
    //| Check if sell position should be closed |
    //+------------------------------------------------------------------+
    bool ShouldCloseSell()
    {
    if(!UseCloseSignal) return false;

    double maValue = GetMA(0);

    // Close sell when price crosses back above MA
    return (Open[1] < maValue && Close[1] > maValue);
    }

    //+------------------------------------------------------------------+
    //| Open market order |
    //+------------------------------------------------------------------+
    void OpenOrder(int command)
    {
    if(!IsSpreadOK()) return;

    double price = (command == OP_BUY) ? Ask : Bid;
    double lot = CalculateOptimizedLotSize();
    string comment = "Single MA Trend";

    int ticket = OrderSend(Symbol(), command, lot, price, Slippage, 0, 0, comment, MagicNumber, 0, clrNONE);

    if(ticket < 0)
    {
    Print("OrderSend failed. Error: ", GetLastError());
    }
    else
    {
    Print("Order opened successfully. Ticket: ", ticket);
    Print("Direction: ", command == OP_BUY ? "BUY" : "SELL");
    Print("Lot size: ", lot);
    Print("Entry price: ", price);
    }
    }

    //+------------------------------------------------------------------+
    //| Close all positions for this EA |
    //+------------------------------------------------------------------+
    void CloseAllPositions()
    {
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    continue;

    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    {
    double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
    bool closed = OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);

    if(!closed)
    Print("Failed to close order ", OrderTicket(), ". Error: ", GetLastError());
    else
    Print("Closed order: ", OrderTicket());
    }
    }
    }

    //+------------------------------------------------------------------+
    //| Close specific position type |
    //+------------------------------------------------------------------+
    void ClosePositionsByType(int targetType)
    {
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
    if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    continue;

    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == targetType)
    {
    double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
    OrderClose(OrderTicket(), OrderLots(), closePrice, Slippage, clrNONE);
    }
    }
    }

    //+------------------------------------------------------------------+
    //| Count positions for this EA |
    //+------------------------------------------------------------------+
    int CountPositions()
    {
    int count = 0;
    for(int i = 0; i < OrdersTotal(); i++)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    count++;
    }
    }
    return count;
    }

    //+------------------------------------------------------------------+
    //| Get current position type (-1 if none) |
    //+------------------------------------------------------------------+
    int GetCurrentPositionType()
    {
    for(int i = 0; i < OrdersTotal(); i++)
    {
    if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
    {
    if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
    return OrderType();
    }
    }
    return -1;
    }

    //+------------------------------------------------------------------+
    //| Expert tick function - main entry point |
    //+------------------------------------------------------------------+
    void OnTick()
    {
    //--- Minimum bar check
    if(Bars < 100)
    return;

    //--- Only trade on new bar open (Volume[0]==1 indicates first tick)
    // This prevents multiple signals on the same bar
    if(Volume[0] > 1)
    return;

    //--- Update signal timestamp to prevent duplicate
    if(Time[0] != lastSignalTime)
    lastSignalTime = Time[0];

    //--- Check if we already have a position
    int currentPosition = GetCurrentPositionType();
    int positionCount = CountPositions();

    //--- No position: look for entry signals
    if(positionCount == 0)
    {
    if(IsBuySignal())
    {
    Print("Buy signal detected at ", TimeToString(Time[0]));
    OpenOrder(OP_BUY);
    }
    else if(IsSellSignal())
    {
    Print("Sell signal detected at ", TimeToString(Time[0]));
    OpenOrder(OP_SELL);
    }
    }
    //--- Have existing position: check for exit signals
    else
    {
    if(currentPosition == OP_BUY && ShouldCloseBuy())
    {
    Print("Buy exit signal detected at ", TimeToString(Time[0]));
    ClosePositionsByType(OP_BUY);
    }
    else if(currentPosition == OP_SELL && ShouldCloseSell())
    {
    Print("Sell exit signal detected at ", TimeToString(Time[0]));
    ClosePositionsByType(OP_SELL);
    }
    }
    }
    //+------------------------------------------------------------------+
    ```

    Parameter Explanation



    | Parameter | Description | Recommended Values |
    |-----------|-------------|--------------------|
    | FixedLots | Baseline lot size (used when risk% calculation yields smaller value) | 0.01, 0.05, 0.1 |
    | MaximumRisk | Percentage of free margin to risk per trade (0.02 = 2%) | 0.01-0.05 (1%-5%) |
    | DecreaseFactor | Drawdown control sensitivity. Higher = more aggressive reduction | 2.0-5.0 |
    | MAPeriod | Moving average calculation period | 12, 20, 50 |
    | MAShift | MA shift to the right (filters false signals) | 0-6 |
    | Slippage | Maximum slippage in pips | 3-5 |
    | UseCloseSignal | Whether to use MA for exit signals | true |
    | MagicNumber | Unique EA identifier for order management | any unique number |
    | MaxSpread | Maximum spread allowed before blocking trade | 20-40 |

    Core Algorithm Explanation



    The entry logic uses a simple but effective price-MA crossover method :

    ```
    IF (Open[1] < MA AND Close[1] > MA) THEN BUY
    IF (Open[1] > MA AND Close[1] < MA) THEN SELL
    ```

    This approach checks the previous completed bar`s open and close relative to the current MA value, which helps filter out intra-bar noise.

    The compound position sizing formula is:

    ```
    Lot = AccountFreeMargin() × MaximumRisk / 1000
    ```

    This creates a scaling effect where account growth leads to larger position sizes, and drawdown leads to reduction .

    Installation Instructions



    1. Open MetaEditor in MT4 (press F4)
    2. Create a new Expert Advisor (File > New > Expert Advisor)
    3. Replace all default code with the code above
    4. Press Compile (F7) - verify zero errors
    5. Attach EA to a chart (EURUSD, M15 or H1 recommended)
    6. Adjust parameters in Inputs tab
    7. Enable AutoTrading (Alt+T)

    Recommended Settings by Timeframe



    | Timeframe | MAPeriod | MaximumRisk | UseCloseSignal |
    |-----------|----------|-------------|----------------|
    | M15 | 12-20 | 0.01-0.02 | true |
    | H1 | 20-50 | 0.02-0.03 | true |
    | H4 | 50-100 | 0.02-0.03 | false |

    Strategy Advantages and Limitations



    Strengths :
  • Extremely simple logic with minimal parameters to optimize

  • Compound position sizing enables exponential growth potential

  • Drawdown control module reduces risk during losing streaks

  • Works well in strong trending markets


  • Limitations:
  • Poor performance in ranging/choppy markets (whipsaw risk)

  • No stop loss or take profit implementation (rely on MA reversal)

  • Only holds one position at a time


  • Compilation & Modification Tips



    To modify for your needs:

    1. Add stop loss and take profit: Insert SL/TP calculation in OpenOrder() function

    2. Change to EMA from SMA: Replace MODE_SMA with MODE_EMA in GetMA() function

    3. Adjust minimum lot constraint: In CalculateOptimizedLotSize(), change the 0.01 threshold

    4. Add time filter: Insert hour check at beginning of OnTick()

    Verification steps:
  • Compile with `#property strict` enabled

  • Test on demo account before live trading

  • Verify MagicNumber doesn`t conflict with other EAs


  • Reference



    This EA source code is independently compiled based on the classic Moving Average sample EA distributed with MetaTrader 4 . The compound position sizing and drawdown control modules are enhancements that provide professional risk management capabilities.

    *For professionally optimized EA strategies with multi-timeframe analysis, AI-powered market regime detection, complete backtest reports, and premium support, check our paid EA collection. Subscribe for weekly updates and exclusive trading tools.*