🚀 AI One-Click Generation of mql4 Strategy Code

How to Write Test-Aware EAs in Mql4 with `IsOptimization()` & `IsVisualMode()`

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

Problem Description

How can I make my expert advisor run faster during optimization and show extra information only in visual backtests?

Solution

To achieve faster optimization speeds while retaining detailed information during visual backtests, you need to selectively execute code based on the environment the Expert Advisor (EA) is running in.

Key Functions to Use

  1. IsOptimization(): Returns true if the Strategy Tester is currently performing an optimization pass. You should skip all graphical object creation, comments, and print statements when this is true.
  2. IsVisualMode(): Returns true only if the "Visual Mode" checkbox is selected in the Strategy Tester. This is where you place your "extra information" logic (arrows, labels, detailed comments).

Strategy Implementation

The following code demonstrates a complete Moving Average Crossover EA. It is structured to:

  1. Perform trading logic on every tick (or new bar).
  2. Skip all logging and graphics if IsOptimization() is true.
  3. Execute graphical updates only if IsVisualMode() is true.
//+------------------------------------------------------------------+
//|                                       OptimizedVisualEA.mq4      |
//|                        Copyright 2023, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//--- Input Parameters
input double Lots          = 0.1;
input int    FastMA        = 10;
input int    SlowMA        = 20;
input int    TakeProfit    = 50;
input int    StopLoss      = 30;
input int    MagicNumber   = 123456;

//--- Global Variables
int ticket = 0;
datetime lastBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // If we are optimizing, we do not need to set up complex indicators 
   // for visualization or load graphical resources.
   if(!IsOptimization())
   {
      Print("EA Started. Visual Mode: ", IsVisualMode());
   }
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Only clean up objects if we are NOT optimizing.
   // Optimization passes don't render objects, so there is nothing to delete.
   if(!IsOptimization())
   {
      ObjectsDeleteAll(0, "MyEA_");
      Comment("");
   }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // --- 1. DATA CALCULATION (REQUIRED FOR TRADING) ---
   // These must run during optimization to generate trade signals.
   
   // Check for New Bar to reduce calculation frequency (Speed optimization)
   if(Time[0] == lastBarTime) return;
   lastBarTime = Time[0];

   double fastMaCurr = iMA(NULL, 0, FastMA, 0, MODE_SMA, PRICE_CLOSE, 1);
   double slowMaCurr = iMA(NULL, 0, SlowMA, 0, MODE_SMA, PRICE_CLOSE, 1);
   double fastMaPrev = iMA(NULL, 0, FastMA, 0, MODE_SMA, PRICE_CLOSE, 2);
   double slowMaPrev = iMA(NULL, 0, SlowMA, 0, MODE_SMA, PRICE_CLOSE, 2);

   // --- 2. TRADING LOGIC (REQUIRED FOR TRADING) ---
   
   // Check for Buy Signal (Crossover Up)
   if(CountOrders(OP_BUY) == 0 && fastMaPrev < slowMaPrev && fastMaCurr > slowMaCurr)
   {
      // Close Sells if any
      CloseOrders(OP_SELL);
      
      // Open Buy
      double sl = (StopLoss > 0) ? Ask - StopLoss * Point : 0;
      double tp = (TakeProfit > 0) ? Ask + TakeProfit * Point : 0;
      
      ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, 3, sl, tp, "Buy Order", MagicNumber, 0, clrBlue);
      
      // VISUAL ONLY: Draw Arrow
      if(ticket > 0 && IsVisualMode()) 
      {
         CreateArrow("MyEA_Buy_" + string(Time[0]), Time[1], Low[1], 233, clrBlue);
      }
   }

   // Check for Sell Signal (Crossover Down)
   if(CountOrders(OP_SELL) == 0 && fastMaPrev > slowMaPrev && fastMaCurr < slowMaCurr)
   {
      // Close Buys if any
      CloseOrders(OP_BUY);
      
      // Open Sell
      double sl = (StopLoss > 0) ? Bid + StopLoss * Point : 0;
      double tp = (TakeProfit > 0) ? Bid - TakeProfit * Point : 0;
      
      ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, 3, sl, tp, "Sell Order", MagicNumber, 0, clrRed);
      
      // VISUAL ONLY: Draw Arrow
      if(ticket > 0 && IsVisualMode()) 
      {
         CreateArrow("MyEA_Sell_" + string(Time[0]), Time[1], High[1], 234, clrRed);
      }
   }

   // --- 3. VISUALIZATION & LOGGING (SKIP DURING OPTIMIZATION) ---
   
   // If we are optimizing, return immediately to save CPU cycles.
   if(IsOptimization()) return;

   // If we are in Visual Mode (Backtest with visuals), show extra info
   if(IsVisualMode())
   {
      string info = "=== Visual Backtest Info ===\n";
      info += "Fast MA: " + DoubleToString(fastMaCurr, Digits) + "\n";
      info += "Slow MA: " + DoubleToString(slowMaCurr, Digits) + "\n";
      info += "Equity: " + DoubleToString(AccountEquity(), 2);
      
      Comment(info);
      
      // Example: Draw a line connecting MA values (Heavy operation, only for visual)
      // Note: Usually indicators handle lines, but this is for demonstration
      string lineName = "MyEA_Line_" + string(Time[0]);
      if(ObjectFind(0, lineName) < 0)
      {
         ObjectCreate(0, lineName, OBJ_TREND, 0, Time[2], fastMaPrev, Time[1], fastMaCurr);
         ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrYellow);
         ObjectSetInteger(0, lineName, OBJPROP_RAY_RIGHT, false);
      }
   }
   // If running Live (Not Optimization, Not Visual Testing), maybe just simple comments
   else 
   {
      Comment("Live Trading Running...");
   }
}

//+------------------------------------------------------------------+
//| Helper: Count specific order types                               |
//+------------------------------------------------------------------+
int CountOrders(int type)
{
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
            count++;
      }
   }
   return(count);
}

//+------------------------------------------------------------------+
//| Helper: Close specific order types                               |
//+------------------------------------------------------------------+
void CloseOrders(int type)
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber && OrderType() == type)
         {
            bool res = false;
            if(type == OP_BUY)  res = OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrGray);
            if(type == OP_SELL) res = OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrGray);
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Helper: Create Visual Arrow                                      |
//+------------------------------------------------------------------+
void CreateArrow(string name, datetime time, double price, int arrowCode, color clr)
{
   // Double check to ensure we never create objects during optimization
   if(IsOptimization()) return;

   if(ObjectCreate(0, name, OBJ_ARROW, 0, time, price))
   {
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER);
   }
}
//+------------------------------------------------------------------+

Explanation of Optimization Techniques

  1. if(IsOptimization()) return;:
    At the end of OnTick, specifically before the visualization block, this check ensures that the CPU does not waste cycles calculating strings for comments or managing graphical objects when the Strategy Tester is running in non-visual optimization mode.

  2. if(IsVisualMode()) { ... }:
    This block wraps the Comment function and ObjectCreate calls. This ensures that you only see the dashboard, text, or specific debug lines when you are actually watching the test run.

  3. Print() Management:
    Print() writes to the hard drive (log files). During optimization involving thousands of passes, excessive printing is the #1 cause of slowness and massive hard drive usage. The code above avoids Print() inside the main loop entirely, or wraps it in !IsOptimization().

  4. New Bar Logic:
    if(Time[0] == lastBarTime) return;
    While not specific to visual mode, calculating logic only once per bar (instead of every tick) drastically reduces the time required for optimization, as the math is performed significantly fewer times.

Q&A: Optimization & Visual Mode

Q: Does IsVisualMode() return true during live trading?
A: No. IsVisualMode() returns true only when running in the Strategy Tester with the "Visual Mode" box checked. If you attach the EA to a live chart, it returns false.

Q: Why is ObjectCreate bad for optimization?
A: Creating graphical objects requires the terminal to allocate memory and manage chart properties. In optimization, the chart is not rendered, but the internal calculations for object positioning still consume CPU cycles if not explicitly skipped.

Q: Can I use DebugBreak() with this logic?
A: Yes. You can use if(IsVisualMode()) DebugBreak(); to pause the visual tester at specific points to inspect variables, which is very useful for debugging without slowing down the optimization passes.

Q: Will Comment() slow down optimization?
A: Yes. Even though you don't see the chart, the terminal processes the string concatenation and the command to update the chart comment. Always wrap Comment() in if(!IsOptimization()).