Problem Description
Solution
To create a custom metric like the Sharpe Ratio for optimization in MetaTrader 4, you must use the OnTester() event handler.
This function is automatically called by the Strategy Tester at the end of a backtest pass. The double value returned by this function is what the optimizer will attempt to maximize.
Implementation Steps
- Define
OnTester(): This function replaces the default optimization criteria (like Balance or Profit Factor). - Access History: Loop through the account history to retrieve trade results.
- Calculate Logic: Implement the mathematical formula for your metric (e.g., Sharpe Ratio).
- Return Value: Return the calculated value.
MQL4 Code Example: Custom Sharpe Ratio
The following code implements a Trade-based Sharpe Ratio (Average Profit per Trade / Standard Deviation of Trades). This is a robust metric for determining the consistency of a strategy.
Add this code to the bottom of your Expert Advisor file.
//+------------------------------------------------------------------+
//| Expert Advisor Inputs |
//+------------------------------------------------------------------+
input int MagicNumber = 123456; // Magic Number to filter trades
//+------------------------------------------------------------------+
//| OnTester: Main function for Custom Optimization |
//| Returns: The value the optimizer will try to MAXIMIZE |
//+------------------------------------------------------------------+
double OnTester()
{
// 1. Retrieve the history of closed trades
int totalHistory = OrdersHistoryTotal();
int tradeCount = 0;
// Dynamic array to store profit of each trade
double profits[];
ArrayResize(profits, totalHistory);
// 2. Loop through history to populate profits array
for(int i = 0; i < totalHistory; i++)
{
if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
{
// Filter by Symbol and Magic Number
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
{
// Filter only Buy and Sell orders (exclude balance operations)
if(OrderType() == OP_BUY || OrderType() == OP_SELL)
{
profits[tradeCount] = OrderProfit() + OrderSwap() + OrderCommission();
tradeCount++;
}
}
}
}
// Resize array to actual number of trades found
ArrayResize(profits, tradeCount);
// 3. Safety check: Need at least 2 trades to calculate StdDev
if(tradeCount < 2)
return 0.0;
// 4. Calculate Mean (Average Return)
double sum = 0.0;
for(int i = 0; i < tradeCount; i++)
{
sum += profits[i];
}
double mean = sum / tradeCount;
// 5. Calculate Standard Deviation
double sqDiffSum = 0.0;
for(int i = 0; i < tradeCount; i++)
{
sqDiffSum += MathPow(profits[i] - mean, 2);
}
double stdDev = MathSqrt(sqDiffSum / (tradeCount - 1));
// 6. Calculate Sharpe Ratio
// Formula: (Mean Return - RiskFreeRate) / StdDev
// Assuming Risk Free Rate is 0 for this specific calculation
if(stdDev == 0.0) return 0.0; // Avoid division by zero
double sharpe = mean / stdDev;
// Optional: Print result to Journal for debugging single tests
// Print("Custom Sharpe: ", sharpe);
return sharpe;
}
How to Use This in the Strategy Tester
Once you have compiled your EA with the code above:
- Open the Strategy Tester (Ctrl+R).
- Select your Expert Advisor.
- Click Expert properties.
- Go to the Testing tab.
- In the Optimization block, look for the Optimized parameter dropdown.
- Select Custom.
Now, when you run an optimization, MT4 will ignore Balance or Profit Factor and instead choose the parameter set that produces the highest value returned by your OnTester() function (the highest Sharpe Ratio).
Key Considerations
- Maximization Only: The MT4 optimizer always tries to maximize the custom value. If you want to optimize for a metric where "lower is better" (like Drawdown), you must invert the return value.
- Example:
return 10000.0 / MaximumDrawdown;orreturn -MaximumDrawdown;
- Example:
- Trade vs. Time Sharpe: The code above calculates the Sharpe Ratio based on per-trade returns. This is standard for high-frequency or algorithmic trading optimization. Traditional financial Sharpe Ratios are often calculated based on monthly or daily equity changes. Calculating time-based Sharpe in MQL4 is significantly more complex as it requires reconstructing the equity curve bar by bar.
- Performance: The
OnTesterfunction runs after every single pass. While the math above is fast, avoid extremely heavy calculations (like reading external files) insideOnTesterduring massive optimizations.
Q&A
Q: Can I combine multiple metrics?
A: Yes. You can create a "Score" variable. For example, if you want high profit but low drawdown:
return NetProfit / (MaxDrawdown + 1); (The +1 prevents division by zero).
Q: Why does the optimization result show "0" for the custom result?
A: This usually happens if the EA made no trades, or fewer than 2 trades (triggering the safety check in the code), or if the Standard Deviation was 0 (all trades had the exact same profit).
Q: Does this work in MT5?
A: Yes, OnTester works in MQL5 as well, though the syntax for accessing history (HistorySelect, HistoryDealGetDouble) is different. The logic remains the same.