Summary: This comprehensive guide covers all aspects of functions in MQL4 including function declaration, definition, parameters, return values, scope, and overloading. Learn to create modular EA code with practical examples.




Why Functions Matter in EA Development
Functions are reusable blocks of code that perform specific tasks. They are the building blocks of modular programming, allowing you to organize EA code into logical, maintainable, and testable units. Without functions, EA code becomes monolithic, difficult to debug, and nearly impossible to maintain.

Complete Functions Reference Table
| Component | Syntax | Purpose | Example |
|-----------|--------|---------|---------|
| Declaration | returnType functionName(parameters); | Declare function exists | double CalculateLot(double risk); |
| Definition | returnType functionName(parameters) { code } | Define function behavior | double CalculateLot(double r) { return r*100; } |
| Parameter | type parameterName | Pass data to function | double riskPercent |
| Return | return value; | Send data back from function | return result; |
| Call | functionName(arguments); | Execute function | double lot = CalculateLot(2); |

1. Function Basics - Declaration, Definition, and Calling
```mql4
// FUNCTION DECLARATION (prototype) - tells compiler the function exists
double CalculateLotSize(double riskPercent, double stopLossPoints);
bool IsTradingHours();
string GetOrderTypeName(int orderType);

// FUNCTION DEFINITION - actual implementation
double CalculateLotSize(double riskPercent, double stopLossPoints) {
double accountBalance = AccountBalance();
double riskAmount = accountBalance * riskPercent / 100;
double pointValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotSize = riskAmount / (stopLossPoints * pointValue);

// Normalize to allowed step
double stepSize = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);

lotSize = MathFloor(lotSize / stepSize) * stepSize;
lotSize = MathMax(minLot, MathMin(maxLot, lotSize));

return NormalizeDouble(lotSize, 2);
}

// FUNCTION CALL - using the function
void OnTick() {
double lot = CalculateLotSize(2.0, 50.0);
Print("Calculated lot size: ", lot);
}
```

2. Function Return Types
```mql4
// int return type - returns integer
int CountOpenOrders(int magic) {
int count = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() == magic && OrderSymbol() == Symbol()) {
count++;
}
}
}
return count;
}

// double return type - returns decimal number
double CalculateAveragePrice() {
double totalPrice = 0;
int count = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderMagicNumber() == magicNumber && OrderSymbol() == Symbol()) {
totalPrice += OrderOpenPrice();
count++;
}
}
}
if(count == 0) return 0;
return totalPrice / count;
}

// bool return type - returns true/false
bool IsTradeAllowed() {
if(!IsTradeAllowed()) return false;
if(CountOpenOrders(magicNumber) >= MAX_ORDERS) return false;
double spread = MarketInfo(Symbol(), MODE_SPREAD);
if(spread > MAX_SPREAD) return false;
return true;
}

// string return type - returns text
string GetSignalDescription(int signalType) {
switch(signalType) {
case SIGNAL_STRONG_BUY: return "Strong Buy - Multiple indicators aligned";
case SIGNAL_BUY: return "Buy - Trend confirmed";
case SIGNAL_STRONG_SELL: return "Strong Sell - Multiple indicators aligned";
case SIGNAL_SELL: return "Sell - Trend confirmed";
default: return "No signal";
}
}

// void return type - returns nothing
void LogMessage(string message) {
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES);
Print("[", timestamp, "] ", message);
}
```

3. Function Parameters - Passing Data to Functions
```mql4
// Single parameter
double CalculatePips(double priceDifference) {
return priceDifference / Point();
}

// Multiple parameters
double CalculatePositionSize(double riskPercent, double stopLossPoints, double accountBalance) {
double riskAmount = accountBalance * riskPercent / 100;
double pointValue = MarketInfo(Symbol(), MODE_TICKVALUE);
return riskAmount / (stopLossPoints * pointValue);
}

// Default parameters (must be from right to left)
double CalculateLotWithDefault(double riskPercent, double stopLossPoints = 50.0) {
return CalculatePositionSize(riskPercent, stopLossPoints, AccountBalance());
}

// Pass by reference (using &) - modifies original variable
void CalculateRiskMetrics(double &drawdown, double &profitFactor) {
double peakBalance = AccountBalance();
double currentBalance = AccountBalance();
drawdown = (peakBalance - currentBalance) / peakBalance * 100;
profitFactor = 1.5; // Simplified calculation
}

// Array parameter
double CalculateArrayAverage(double &arr[], int size) {
if(size <= 0) return 0;
double sum = 0;
for(int i = 0; i < size; i++) {
sum += arr[i];
}
return sum / size;
}

// Complete example with multiple parameter types
struct TradeResult {
int ticket;
double entryPrice;
double profit;
};

bool ExecuteTrade(int orderType, double lotSize, double stopLossPoints, double takeProfitPoints, string &errorMessage) {
double price = (orderType == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;

if(stopLossPoints > 0) {
sl = (orderType == OP_BUY) ? price - stopLossPoints * Point() : price + stopLossPoints * Point();
}
if(takeProfitPoints > 0) {
tp = (orderType == OP_BUY) ? price + takeProfitPoints * Point() : price - takeProfitPoints * Point();
}

int ticket = OrderSend(Symbol(), orderType, lotSize, price, 30, sl, tp, "Trade", magicNumber, 0, clrNONE);

if(ticket < 0) {
errorMessage = "OrderSend failed. Error: " + IntegerToString(GetLastError());
return false;
}
return true;
}
```

4. Function Overloading - Same Name, Different Parameters
```mql4
// Overloaded function: CalculateLot - different parameter combinations
double CalculateLot(double riskPercent) {
// Use default stop loss of 50 points
return CalculateLot(riskPercent, 50.0);
}

double CalculateLot(double riskPercent, double stopLossPoints) {
// Use current account balance
return CalculateLot(riskPercent, stopLossPoints, AccountBalance());
}

double CalculateLot(double riskPercent, double stopLossPoints, double accountBalance) {
double riskAmount = accountBalance * riskPercent / 100;
double pointValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotSize = riskAmount / (stopLossPoints * pointValue);

double stepSize = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);

lotSize = MathFloor(lotSize / stepSize) * stepSize;
lotSize = MathMax(minLot, MathMin(maxLot, lotSize));

return NormalizeDouble(lotSize, 2);
}

// Usage examples
void OnTick() {
double lot1 = CalculateLot(2.0); // Uses default stop loss
double lot2 = CalculateLot(2.0, 60.0); // Specifies stop loss
double lot3 = CalculateLot(2.0, 60.0, 50000.0); // Specifies all parameters
}
```

5. Variable Scope in Functions
```mql4
// Global variable - accessible everywhere
double g_globalVariable = 100;
int g_tradeCount = 0;

// Function with local variables
void ProcessTrade() {
// Local variable - only accessible inside this function
double localVariable = 50;
int localCounter = 0;

localCounter++;
g_tradeCount++; // Can access and modify global variable

Print("Local: ", localVariable, " Global: ", g_globalVariable);
// localVariable is destroyed when function exits
}

// Static variable - preserves value between calls
int GetNextTicketID() {
static int nextID = 1000; // Initialized once, retains value
nextID++;
return nextID;
}

void DemonstrateStatic() {
for(int i = 0; i < 5; i++) {
Print("Ticket ID: ", GetNextTicketID()); // Returns 1001,1002,1003,1004,1005
}
}
```

6. Practical EA Function Library
```mql4
// ============ ORDER MANAGEMENT FUNCTIONS ============

// Count orders by type and magic number
int CountOrders(int magic, int orderType = -1) {
int count = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == magic) {
if(orderType == -1 || OrderType() == orderType) {
count++;
}
}
}
}
return count;
}

// Calculate total profit of all positions
double CalculateTotalProfit(int magic) {
double totalProfit = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == magic) {
totalProfit += OrderProfit() + OrderSwap() + OrderCommission();
}
}
}
return totalProfit;
}

// Close all positions
int CloseAllPositions(int magic) {
int closed = 0;
for(int i = OrdersTotal() - 1; i >= 0; i--) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == magic) {
double closePrice = (OrderType() == OP_BUY) ? Bid : Ask;
if(OrderClose(OrderTicket(), OrderLots(), closePrice, 30, clrNONE)) {
closed++;
}
}
}
}
return closed;
}

// ============ RISK MANAGEMENT FUNCTIONS ============

// Calculate position size based on risk percentage
double CalculateRiskPosition(double riskPercent, double stopLossPoints) {
double accountEquity = AccountBalance();
double riskDollars = accountEquity * riskPercent / 100;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double rawLots = riskDollars / (stopLossPoints * tickValue);

double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double stepSize = MarketInfo(Symbol(), MODE_LOTSTEP);

rawLots = MathFloor(rawLots / stepSize) * stepSize;
rawLots = MathMax(minLot, MathMin(maxLot, rawLots));

return NormalizeDouble(rawLots, 2);
}

// Check if daily loss limit reached
bool IsDailyLossLimitReached(double maxLossPercent) {
static double startBalance = 0;
static datetime lastReset = 0;

datetime currentDay = GetMidnight();
if(currentDay != lastReset) {
startBalance = AccountBalance();
lastReset = currentDay;
}

double currentBalance = AccountBalance();
double lossPercent = (startBalance - currentBalance) / startBalance * 100;

return lossPercent >= maxLossPercent;
}

// ============ INDICATOR FUNCTIONS ============

// Check moving average crossover
int CheckMACrossover(int fastPeriod, int slowPeriod) {
double maFastCurrent = iMA(NULL, 0, fastPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double maSlowCurrent = iMA(NULL, 0, slowPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
double maFastPrevious = iMA(NULL, 0, fastPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);
double maSlowPrevious = iMA(NULL, 0, slowPeriod, 0, MODE_SMA, PRICE_CLOSE, 2);

if(maFastCurrent > maSlowCurrent && maFastPrevious <= maSlowPrevious) {
return SIGNAL_BUY; // Bullish crossover
}
else if(maFastCurrent < maSlowCurrent && maFastPrevious >= maSlowPrevious) {
return SIGNAL_SELL; // Bearish crossover
}
return SIGNAL_NONE;
}

// Check RSI divergence
int CheckRSIDivergence(int rsiPeriod) {
double rsiCurrent = iRSI(NULL, 0, rsiPeriod, PRICE_CLOSE, 1);
double rsiPrevious = iRSI(NULL, 0, rsiPeriod, PRICE_CLOSE, 2);
double priceCurrent = iClose(NULL, 0, 1);
double pricePrevious = iClose(NULL, 0, 2);

// Bullish divergence: price makes lower low, RSI makes higher low
if(priceCurrent < pricePrevious && rsiCurrent > rsiPrevious) {
return SIGNAL_BUY;
}
// Bearish divergence: price makes higher high, RSI makes lower high
else if(priceCurrent > pricePrevious && rsiCurrent < rsiPrevious) {
return SIGNAL_SELL;
}
return SIGNAL_NONE;
}

// ============ TIME MANAGEMENT FUNCTIONS ============

// Check if current time is within trading hours
bool IsTradingTime(int startHour, int endHour) {
MqlDateTime timeStruct;
TimeToStruct(TimeCurrent(), timeStruct);
int hour = timeStruct.hour;
int dayOfWeek = timeStruct.day_of_week;

// Skip weekends (Saturday=6, Sunday=0)
if(dayOfWeek == 0 || dayOfWeek == 6) return false;

return (hour >= startHour && hour < endHour);
}

// Get next bar open time
datetime GetNextBarOpen(int timeframe) {
datetime currentTime = TimeCurrent();
datetime barTime = iTime(Symbol(), timeframe, 0);
return barTime + PeriodSeconds(timeframe);
}

// ============ UTILITY FUNCTIONS ============

// Normalize price to broker digits
double NormalizePrice(double price) {
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
return NormalizeDouble(price, digits);
}

// Format number with specified decimals
string FormatNumber(double number, int decimals) {
return DoubleToString(number, decimals);
}

// Send alert with rate limiting
void SendAlert(string message, int cooldownSeconds = 60) {
static datetime lastAlert = 0;
if(TimeCurrent() - lastAlert >= cooldownSeconds) {
Alert(message);
lastAlert = TimeCurrent();
}
}
```

7. Complete Modular EA Using Functions
```mql4
//+------------------------------------------------------------------+
//| ModularEA.mq4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024"
#property version "1.00"
#property strict

// Input parameters
input double InpRiskPercent = 2.0; // Risk per trade (%)
input int InpMagic = 12345; // EA magic number
input int InpMaxSpread = 30; // Maximum spread allowed
input int InpTrailingStop = 30; // Trailing stop in points
input int InpStartHour = 8; // Trading start hour
input int InpEndHour = 17; // Trading end hour
input double InpMaxDailyLoss = 5.0; // Maximum daily loss (%)

// Signal constants
#define SIGNAL_NONE 0
#define SIGNAL_BUY 1
#define SIGNAL_SELL 2

// Global variables
datetime g_lastBarTime = 0;
int g_dailyTradeCount = 0;

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
LogMessage("EA initialized successfully");
LogMessage("Risk per trade: " + DoubleToString(InpRiskPercent, 1) + "%");
LogMessage("Magic number: " + IntegerToString(InpMagic));
return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
// New bar detection
if(Time[0] == g_lastBarTime) return;
g_lastBarTime = Time[0];

// Pre-trade validation
if(!IsTradeAllowed()) {
Comment("Trading not allowed");
return;
}

if(!IsTradingTime(InpStartHour, InpEndHour)) {
Comment("Outside trading hours");
return;
}

if(IsDailyLossLimitReached(InpMaxDailyLoss)) {
Comment("Daily loss limit reached");
return;
}

// Position management
if(CountOrders(InpMagic) > 0) {
ManageAllPositions();
return;
}

// Signal generation and execution
int signal = GenerateTradeSignal();
if(signal != SIGNAL_NONE) {
ExecuteTrade(signal);
}
}

//+------------------------------------------------------------------+
//| Generate trade signal using multiple indicators |
//+------------------------------------------------------------------+
int GenerateTradeSignal() {
double maFast = iMA(NULL, 0, 10, 0, MODE_SMA, PRICE_CLOSE, 1);
double maSlow = iMA(NULL, 0, 30, 0, MODE_SMA, PRICE_CLOSE, 1);
double rsi = iRSI(NULL, 0, 14, PRICE_CLOSE, 1);
double spread = MarketInfo(Symbol(), MODE_SPREAD);

// Spread check
if(spread > InpMaxSpread) {
LogMessage("Spread too high: " + DoubleToString(spread, 1));
return SIGNAL_NONE;
}

// Signal logic
if(maFast > maSlow && rsi < 30) {
LogMessage("Buy signal generated - MA bullish + RSI oversold");
return SIGNAL_BUY;
}
else if(maFast < maSlow && rsi > 70) {
LogMessage("Sell signal generated - MA bearish + RSI overbought");
return SIGNAL_SELL;
}

return SIGNAL_NONE;
}

//+------------------------------------------------------------------+
//| Execute trade based on signal |
//+------------------------------------------------------------------+
void ExecuteTrade(int signal) {
double lotSize = CalculateRiskPosition(InpRiskPercent, 50);
if(lotSize <= 0) {
LogMessage("Invalid lot size: " + DoubleToString(lotSize, 2));
return;
}

int orderType = (signal == SIGNAL_BUY) ? OP_BUY : OP_SELL;
double price = (orderType == OP_BUY) ? Ask : Bid;
double sl = 0, tp = 0;

// Set stop loss and take profit
if(orderType == OP_BUY) {
sl = price - 50 * Point();
tp = price + 100 * Point();
} else {
sl = price + 50 * Point();
tp = price - 100 * Point();
}

int ticket = OrderSend(Symbol(), orderType, lotSize, price, 30, sl, tp, "Modular EA", InpMagic, 0, clrNONE);

if(ticket > 0) {
g_dailyTradeCount++;
LogMessage("Order opened - Ticket: " + IntegerToString(ticket) +
" Type: " + (orderType == OP_BUY ? "BUY" : "SELL") +
" Lot: " + DoubleToString(lotSize, 2));
} else {
LogMessage("Order failed - Error: " + IntegerToString(GetLastError()));
}
}

//+------------------------------------------------------------------+
//| Manage all open positions with trailing stop |
//+------------------------------------------------------------------+
void ManageAllPositions() {
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagic) {
ApplyTrailingStop(OrderTicket());
}
}
}
}

//+------------------------------------------------------------------+
//| Apply trailing stop to a specific order |
//+------------------------------------------------------------------+
void ApplyTrailingStop(int ticket) {
if(!OrderSelect(ticket, SELECT_BY_TICKET)) return;

double currentStop = OrderStopLoss();
double newStop = 0;
double currentPrice = (OrderType() == OP_BUY) ? Bid : Ask;
double openPrice = OrderOpenPrice();
double profitPoints = 0;

if(OrderType() == OP_BUY) {
profitPoints = (currentPrice - openPrice) / Point();
if(profitPoints > InpTrailingStop) {
newStop = NormalizePrice(currentPrice - InpTrailingStop * Point());
}
} else {
profitPoints = (openPrice - currentPrice) / Point();
if(profitPoints > InpTrailingStop) {
newStop = NormalizePrice(currentPrice + InpTrailingStop * Point());
}
}

if(newStop > 0 && newStop != currentStop) {
if(OrderModify(ticket, openPrice, newStop, OrderTakeProfit(), 0, clrNONE)) {
LogMessage("Trailing stop updated - Ticket: " + IntegerToString(ticket) +
" New stop: " + DoubleToString(newStop, 5));
}
}
}

//+------------------------------------------------------------------+
//| Log message with timestamp |
//+------------------------------------------------------------------+
void LogMessage(string message) {
string timestamp = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES|TIME_SECONDS);
Print("[", timestamp, "] ", message);
}

//+------------------------------------------------------------------+
//| Count orders by magic number |
//+------------------------------------------------------------------+
int CountOrders(int magic) {
int count = 0;
for(int i = 0; i < OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if(OrderSymbol() == Symbol() && OrderMagicNumber() == magic) {
count++;
}
}
}
return count;
}

//+------------------------------------------------------------------+
//| Calculate position size based on risk |
//+------------------------------------------------------------------+
double CalculateRiskPosition(double riskPercent, double stopLossPoints) {
double accountEquity = AccountBalance();
double riskDollars = accountEquity * riskPercent / 100;
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double rawLots = riskDollars / (stopLossPoints * tickValue);

double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double stepSize = MarketInfo(Symbol(), MODE_LOTSTEP);

rawLots = MathFloor(rawLots / stepSize) * stepSize;
rawLots = MathMax(minLot, MathMin(maxLot, rawLots));

return NormalizeDouble(rawLots, 2);
}
```

Function Best Practices Checklist
  • [ ] Give functions descriptive names that indicate their purpose

  • [ ] Keep functions small and focused on a single task

  • [ ] Use function declarations (prototypes) at the top of the file

  • [ ] Validate all input parameters at the beginning of functions

  • [ ] Use const or pass-by-reference for large data structures

  • [ ] Document complex functions with comments

  • [ ] Avoid modifying global variables inside functions when possible

  • [ ] Use static variables inside functions for state persistence

  • [ ] Return meaningful error codes or use reference parameters for error messages

  • [ ] Test each function independently before integrating into EA


  • Reference:
  • MetaQuotes Ltd. "MQL4 Documentation - Functions" (2024)

  • McConnell, Steve. "Code Complete: A Practical Handbook of Software Construction" (2004)


  • 9. Next Step
    Part 11 will explain Order Management Functions in MQL4 (OrderSend, OrderModify, OrderSelect) – Complete guide to opening, modifying, and closing orders with practical examples and error handling.