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
s Open price is below the MA AND the previous bars Close price is above the MA (upward crossover)s Open price is above the MA AND the previous bars 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:
Lot = AccountFreeMargin() * MaximumRisk / 1000.0 - Automatically adjusts lot size based on available margin, enabling compound growthDecreaseFactor 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=", MaximumRisk100, "% | 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 bars 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
Open MetaEditor in MT4 (press F4)
Create a new Expert Advisor (File > New > Expert Advisor)
Replace all default code with the code above
Press Compile (F7) - verify zero errors
Attach EA to a chart (EURUSD, M15 or H1 recommended)
Adjust parameters in Inputs tab
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:
<strong>Add stop loss and take profit</strong>: Insert SL/TP calculation in OpenOrder() function
<strong>Change to EMA from SMA</strong>: Replace MODE_SMA with MODE_EMA in GetMA() function
<strong>Adjust minimum lot constraint</strong>: In CalculateOptimizedLotSize(), change the 0.01 threshold
<strong>Add time filter</strong>: 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 EAsReference
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.