🚀 AI One-Click Generation of mql4 Strategy Code

Mql4 Strategy Tester: Creating Custom Optimization Criteria with OnTester()

mql4 | Published on: 2025/12/11
The following content is generated by EasyQuant .

Problem Description

How can I create my own custom metric, like a Sharpe Ratio, for optimizing my trading strategy in the backtester?

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

  1. Define OnTester(): This function replaces the default optimization criteria (like Balance or Profit Factor).
  2. Access History: Loop through the account history to retrieve trade results.
  3. Calculate Logic: Implement the mathematical formula for your metric (e.g., Sharpe Ratio).
  4. 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:

  1. Open the Strategy Tester (Ctrl+R).
  2. Select your Expert Advisor.
  3. Click Expert properties.
  4. Go to the Testing tab.
  5. In the Optimization block, look for the Optimized parameter dropdown.
  6. 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

  1. 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; or return -MaximumDrawdown;
  2. 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.
  3. Performance: The 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&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.