Introduction: Why Custom Functions Matter in MQL4 and MQL5
Every MetaTrader developer eventually hits a wall: you write the same position-sizing logic in five different EAs, or copy-paste the same trailing stop code across indicators. This wastes time, introduces bugs, and makes maintenance a nightmare. Custom functions solve this by letting you write code once and call it anywhere.
In this guide, you'll learn how to create, organize, and reuse custom functions in both MQL4 and MQL5. You'll understand function syntax, parameter passing, return types, and scope rules. By the end, you'll be writing cleaner, faster, and more maintainable MQL code that works seamlessly across multiple projects.
What Makes a Good Custom Function?
A well-designed custom function should:
- Do one thing well – avoid monolithic functions that handle position sizing, trailing stops, and money management all at once.
- Be testable – you can call it with known inputs and verify the output.
- Handle edge cases – return safe defaults when inputs are invalid.
- Be self-documenting – function names and parameters should make the purpose clear without reading the body.
Prerequisites
- MetaTrader 4 or 5 (any build from 2018 or later; the interface is nearly identical)
- MetaEditor (comes with the platform; open via Tools > MetaQuotes Language Editor or press F4)
- Basic familiarity with the MQL interface: you know how to create a new script, indicator, or EA file
- A demo account for testing (optional but recommended to avoid real-money mistakes)
Step-by-Step: Writing and Using Custom Functions in MQL4/MQL5
Step 1: Understand Function Syntax
A function in MQL has a specific structure. Here's the general form:
return_type FunctionName(parameter_type parameter_name, ...)
{
// function body
// optional: return value;
}
Key elements explained:
- return_type – the data type the function sends back. Common types:
int,double,bool,string,datetime, orvoid(returns nothing). - FunctionName – descriptive name using camelCase or PascalCase, no spaces, can't start with a digit. Example:
calculateLotSizeorCalculateLotSize. - Parameters – inputs the function accepts, each with a type and name. You can have zero or more parameters.
- Body – code inside curly braces that executes when the function is called.
- return – sends a value back to the caller. Required for non-void functions; optional for void.
Step 2: Create a Simple Utility Function
Let's write a function that calculates lot size based on account balance and risk percentage. Open MetaEditor (F4), create a new Script (or add this to an existing EA's code).
//+------------------------------------------------------------------+
//| Calculate lot size based on risk percentage |
//+------------------------------------------------------------------+
double CalculateLotSize(double riskPercent, double stopLossPoints)
{
double accountBalance = AccountBalance();
double riskAmount = accountBalance * (riskPercent / 100.0);
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotSize = riskAmount / (stopLossPoints * tickValue);
lotSize = MathFloor(lotSize / lotStep) * lotStep;
if(lotSize < minLot) lotSize = minLot;
if(lotSize > maxLot) lotSize = maxLot;
return(lotSize);
}
This function takes two inputs – risk percentage and stop loss in points – and returns a properly rounded lot size that respects broker limits. The MathFloor() call ensures the lot size is a multiple of the lot step, which is critical for brokers that don't accept arbitrary lot sizes.
Step 3: Call the Function in Your Code
Now, inside your EA's OnTick() or an indicator's OnCalculate(), call it like this:
double myLot = CalculateLotSize(2.0, 200); // 2% risk, 200-point stop
if(myLot > 0)
{
OrderSend(Symbol(), OP_BUY, myLot, Ask, 3, 0, 0, "Buy", 0, 0, Green);
}
Notice how the function abstracts away all the complex math. You don't need to rewrite the lot-size logic every time you place a trade.
Step 4: Use Function Overloading (MQL5 Only)
MQL5 supports function overloading – multiple functions with the same name but different parameters. This is useful for handling different input types or providing default behaviors.
//+------------------------------------------------------------------+
//| Overloaded: CalculateLotSize with different inputs |
//+------------------------------------------------------------------+
double CalculateLotSize(double riskPercent, double stopLossPoints)
{
// same as above
}
double CalculateLotSize(double fixedLot)
{
// simple fixed lot version
double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
if(fixedLot < minLot) return(minLot);
if(fixedLot > maxLot) return(maxLot);
return(fixedLot);
}
MQL4 does not support overloading – each function must have a unique name. In MQL4, you'd need to name the second function something like CalculateFixedLotSize().
Step 5: Manage Scope and Include Files
To reuse functions across multiple files, create an include file (a .mqh file). In MetaEditor, go to File > New > Include File. Save it as LotSizeFunctions.mqh and paste your function there. Then in any EA or indicator, add at the top:
#include <LotSizeFunctions.mqh>
Now CalculateLotSize() is available everywhere. This is the professional way to build a personal code library. You can create separate .mqh files for different categories: RiskManagement.mqh, TradeManagement.mqh, Indicators.mqh.
Step 6: Forward Declarations for Complex Projects
If you have many functions that call each other, you may need forward declarations. These tell the compiler about a function before its full definition appears:
// Forward declaration at the top of the file
double CalculateLotSize(double riskPercent, double stopLossPoints);
// Later in the file, the full definition
double CalculateLotSize(double riskPercent, double stopLossPoints)
{
// implementation
}
Forward declarations are essential when Function A calls Function B, and Function B calls Function A (circular dependency). Without them, the compiler would report an undefined function error.
Key Differences Between MQL4 and MQL5 Function Handling
| Feature | MQL4 | MQL5 |
|---|---|---|
| Function overloading | Not supported | Supported |
| Default parameter values | Not supported | Supported |
| Const parameters | Not supported | Supported |
| Function pointers | Not supported | Supported |
| Market info functions | MarketInfo() |
SymbolInfoDouble(), SymbolInfoInteger() |
Advanced Techniques for Custom Functions
Using Default Parameters (MQL5 Only)
In MQL5, you can assign default values to parameters, making them optional:
double CalculateLotSize(double riskPercent = 2.0, double stopLossPoints = 150)
{
// implementation
}
// Can call with no arguments:
double lot1 = CalculateLotSize(); // Uses 2% risk, 150-point stop
// Or override one:
double lot2 = CalculateLotSize(3.0); // Uses 3% risk, 150-point stop
// Or override both:
double lot3 = CalculateLotSize(1.5, 100); // Uses 1.5% risk, 100-point stop
Passing Arrays to Functions
You can pass arrays to functions for processing large datasets:
double CalculateAveragePrice(double &priceArray[], int arraySize)
{
double sum = 0;
for(int i = 0; i < arraySize; i++)
{
sum += priceArray[i];
}
return(arraySize > 0 ? sum / arraySize : 0);
}
// Usage:
double prices[5] = {1.1000, 1.1010, 1.0995, 1.1020, 1.1005};
double avg = CalculateAveragePrice(prices, 5);
Note the & symbol before the array parameter – this passes the array by reference, which is more efficient for large arrays.
Recursive Functions
Recursive functions call themselves. They're useful for calculations like Fibonacci retracement levels:
int Fibonacci(int n)
{
if(n <= 1) return(n);
return(Fibonacci(n - 1) + Fibonacci(n - 2));
}
// Usage: generates the 10th Fibonacci number
int fib10 = Fibonacci(10); // Returns 55
Be careful with recursion depth – MQL has stack limits. For deep recursion (over 1000 levels), consider iterative approaches instead.
Tips and Best Practices from Experience
- Use descriptive names – CalculateLotSize is better than CalcLot. You'll thank yourself six months later when revisiting old code.
- Validate inputs – always check if parameters are within sensible ranges. Return -1 or 0 for invalid inputs, and let the caller handle errors.
- Keep functions focused – one function should do one thing. A 50-line function likely needs splitting into smaller helper functions.
- Comment the purpose – add a brief comment above each function explaining what it does, what it returns, and any side effects.
- Use const for read-only parameters – in MQL5, you can add const to prevent accidental modification:
double CalculateLotSize(const double riskPercent, const double stopLossPoints). - Test in the Strategy Tester – before deploying, run your EA with the new function in the tester with visual mode on to verify lot sizes are calculated correctly across different market conditions.
- Use local variables – avoid global variables inside functions. Pass what you need as parameters to keep functions self-contained and testable.
- Handle division by zero – always check denominators before division. In our lot-size function, ensure
stopLossPointsandtickValueare not zero.
Common Mistakes and Troubleshooting
| Mistake |
|---|

