Generate code in 3 minutes
Enter your idea and AI generates runnable code.
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.
OnTester(): This function replaces the default optimization criteria (like Balance or Profit Factor).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;
}
Once you have compiled your EA with the code above:
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).
return 10000.0 / MaximumDrawdown; or return -MaximumDrawdown;OnTester function runs after every single pass. While the math above is fast, avoid extremely heavy calculations (like reading external files) inside OnTester during massive optimizations.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.