# 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
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 :
Limitations:
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:
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.*