This tutorial guides you through creating a complete, working Expert Advisor. We'll build a simple Moving Average Crossover EA that can actually place trades.
What We'll Build
A simple but functional EA that:
- Uses two Moving Averages (fast and slow)
- Buys when fast MA crosses above slow MA
- Sells when fast MA crosses below slow MA
- Includes basic risk management
- Has proper position management
Step 1: Create the EA File
- Open MetaEditor (F4 in MT5)
- File → New → Expert Advisor
- Name it "MACrossoverEA"
- Click Finish
Step 2: Define Input Parameters
Input parameters let users customize the EA:
``mql5 //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input group "=== Moving Average Settings ===" input int FastMAPeriod = 10; // Fast MA Period input int SlowMAPeriod = 20; // Slow MA Period input ENUM_MA_METHOD MAMethod = MODE_SMA; // MA Method
input group "=== Trading Settings ===" input double LotSize = 0.1; // Lot Size input int StopLossPips = 50; // Stop Loss (pips) input int TakeProfitPips = 100; // Take Profit (pips) input int MagicNumber = 12345; // Magic Number `
Step 3: Declare Global Variables
`mql5 //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ int fastMAHandle; // Handle for fast MA indicator int slowMAHandle; // Handle for slow MA indicator double fastMA[]; // Array to store fast MA values double slowMA[]; // Array to store slow MA values `
Step 4: Initialize in OnInit()
`mql5 //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Create MA indicator handles fastMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, MAMethod, PRICE_CLOSE); slowMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, MAMethod, PRICE_CLOSE);
// Check if handles are valid if(fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE) { Print("Error creating MA indicators"); return(INIT_FAILED); }
// Set arrays as series (index 0 = most recent) ArraySetAsSeries(fastMA, true); ArraySetAsSeries(slowMA, true);
Print("EA initialized successfully"); Print("Fast MA: ", FastMAPeriod, " | Slow MA: ", SlowMAPeriod);
return(INIT_SUCCEEDED); } `
Step 5: Clean Up in OnDeinit()
`mql5 //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Release indicator handles IndicatorRelease(fastMAHandle); IndicatorRelease(slowMAHandle);
Print("EA stopped. Reason: ", reason); } `
Step 6: Main Logic in OnTick()
`mql5 //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Only trade at the start of a new bar if(!IsNewBar()) return;
// Copy MA values if(CopyBuffer(fastMAHandle, 0, 0, 3, fastMA) < 3) return; if(CopyBuffer(slowMAHandle, 0, 0, 3, slowMA) < 3) return;
// Check for crossover signals bool buySignal = (fastMA[1] > slowMA[1]) && (fastMA[2] <= slowMA[2]); bool sellSignal = (fastMA[1] < slowMA[1]) && (fastMA[2] >= slowMA[2]);
// Check current positions bool hasPosition = HasOpenPosition();
// Execute trades if(buySignal && !hasPosition) { CloseAllPositions(); // Close any opposite positions OpenBuyTrade(); } else if(sellSignal && !hasPosition) { CloseAllPositions(); OpenSellTrade(); } } `
Step 7: Helper Functions
Check for New Bar
`mql5 //+------------------------------------------------------------------+ //| Check if new bar has formed | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime lastBarTime = 0; datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBarTime != lastBarTime) { lastBarTime = currentBarTime; return true; } return false; } `
Check for Open Positions
`mql5 //+------------------------------------------------------------------+ //| Check if we have open position | //+------------------------------------------------------------------+ bool HasOpenPosition() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { return true; } } } return false; } `
Open Buy Trade
`mql5 //+------------------------------------------------------------------+ //| Open a buy trade | //+------------------------------------------------------------------+ void OpenBuyTrade() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double sl = NormalizeDouble(ask - StopLossPips point 10, digits); double tp = NormalizeDouble(ask + TakeProfitPips point 10, digits);
MqlTradeRequest request = {}; MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = LotSize; request.type = ORDER_TYPE_BUY; request.price = ask; request.sl = sl; request.tp = tp; request.deviation = 10; request.magic = MagicNumber; request.comment = "MA Crossover Buy";
if(OrderSend(request, result)) { Print("Buy order placed. Ticket: ", result.order); } else { Print("Buy order failed. Error: ", GetLastError()); } } `
Open Sell Trade
`mql5 //+------------------------------------------------------------------+ //| Open a sell trade | //+------------------------------------------------------------------+ void OpenSellTrade() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
double sl = NormalizeDouble(bid + StopLossPips point 10, digits); double tp = NormalizeDouble(bid - TakeProfitPips point 10, digits);
MqlTradeRequest request = {}; MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = LotSize; request.type = ORDER_TYPE_SELL; request.price = bid; request.sl = sl; request.tp = tp; request.deviation = 10; request.magic = MagicNumber; request.comment = "MA Crossover Sell";
if(OrderSend(request, result)) { Print("Sell order placed. Ticket: ", result.order); } else { Print("Sell order failed. Error: ", GetLastError()); } } `
Close All Positions
`mql5 //+------------------------------------------------------------------+ //| Close all positions for this EA | //+------------------------------------------------------------------+ void CloseAllPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { MqlTradeRequest request = {}; MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = PositionGetDouble(POSITION_VOLUME); request.deviation = 10; request.magic = MagicNumber;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { request.type = ORDER_TYPE_SELL; request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID); } else { request.type = ORDER_TYPE_BUY; request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); }
OrderSend(request, result); } } } } `
Complete EA Code
Here's the complete EA in one block:
`mql5 //+------------------------------------------------------------------+ //| MACrossoverEA.mq5 | //| TradingTeaching.com | //+------------------------------------------------------------------+ #property copyright "TradingTeaching.com" #property version "1.00"
//--- Input Parameters input group "=== Moving Average Settings ===" input int FastMAPeriod = 10; input int SlowMAPeriod = 20; input ENUM_MA_METHOD MAMethod = MODE_SMA;
input group "=== Trading Settings ===" input double LotSize = 0.1; input int StopLossPips = 50; input int TakeProfitPips = 100; input int MagicNumber = 12345;
//--- Global Variables int fastMAHandle, slowMAHandle; double fastMA[], slowMA[];
//+------------------------------------------------------------------+ int OnInit() { fastMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastMAPeriod, 0, MAMethod, PRICE_CLOSE); slowMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowMAPeriod, 0, MAMethod, PRICE_CLOSE);
if(fastMAHandle == INVALID_HANDLE || slowMAHandle == INVALID_HANDLE) return(INIT_FAILED);
ArraySetAsSeries(fastMA, true); ArraySetAsSeries(slowMA, true);
return(INIT_SUCCEEDED); }
//+------------------------------------------------------------------+ void OnDeinit(const int reason) { IndicatorRelease(fastMAHandle); IndicatorRelease(slowMAHandle); }
//+------------------------------------------------------------------+ void OnTick() { if(!IsNewBar()) return;
if(CopyBuffer(fastMAHandle, 0, 0, 3, fastMA) < 3) return; if(CopyBuffer(slowMAHandle, 0, 0, 3, slowMA) < 3) return;
bool buySignal = (fastMA[1] > slowMA[1]) && (fastMA[2] <= slowMA[2]); bool sellSignal = (fastMA[1] < slowMA[1]) && (fastMA[2] >= slowMA[2]);
if(buySignal && !HasOpenPosition()) { CloseAllPositions(); OpenBuyTrade(); } else if(sellSignal && !HasOpenPosition()) { CloseAllPositions(); OpenSellTrade(); } }
// Include all helper functions from above... ``
Testing Your EA
Step 1: Compile
- Press F7 in MetaEditor
- Fix any errors
- Ensure "0 errors, 0 warnings"
Step 2: Strategy Tester
- In MT5: View → Strategy Tester
- Select your EA
- Set symbol and timeframe
- Choose date range
- Click "Start"
Step 3: Analyze Results
Check the results tab for:
- Total profit/loss
- Win rate
- Maximum drawdown
- Profit factor
Key Takeaways
- Input parameters make EAs customizable
- Indicator handles provide efficient access to indicator values
- Trade on new bars to avoid overtrading
- Always use Magic Numbers to identify your trades
- Include proper error handling
- Test thoroughly in Strategy Tester first
What's Next?
Learn how to create custom indicators in MQL5 to enhance your trading systems.
This article is for educational purposes only and does not constitute financial advice.