Multi Symbol EA to display metrics results for each symbol per pass

 

Hello, 

I coded a multi symbol EA that works properly. By default, the strategy tester optimization results show unified metrics for all symbols combined per pass. 

I'm trying to modify it, so that, per pass, it generates the result metrics for each individual symbol per pass. I've been trying it for so long but stuck at the point where the results csv gets created with its metrics header but doesn't get populated with data, and I couldn't figure out what's wrong. 

Any help would be much appreciated, and thanks in advance. 

   //################
   // Input Variables 
   //################

   input string                         TradeSymbols           = "EURGBP|USDCHF|";       //Symbol(s)

   //################
   //Global Variables
   //################
    
   int      NumberOfTradeableSymbols;                    //Set in OnInit()
   string   SymbolArray[];                               //Set in OnInit()
   string   CsvFileName = "OptimizationResults.csv"; // Name of the CSV file
   long     CurrentPassNumber = 0;               // Global counter for optimization pass number

// Structure to hold symbol-specific statistics
   struct SymbolStats
{
   double complex_criterion;
   double profit;        // Total profit for the symbol
   int trades;           // Total number of trades for the symbol
   double profit_factor; // Profit factor for the symbol
   double drawdown;      // Maximum drawdown for the symbol
   double recovery_factor;
};

// Array to hold statistics for all symbols
   SymbolStats SymbolMetrics[];

   int OnInit()
{
      //Populate SymbolArray and determine the number of symbols being traded
      NumberOfTradeableSymbols = StringSplit(TradeSymbols, '|', SymbolArray);
      
      ArrayResize(SymbolMetrics, NumberOfTradeableSymbols);

      for (int i = 0; i < NumberOfTradeableSymbols; i++)
     {
      SymbolMetrics[i].complex_criterion = 0.0;
      SymbolMetrics[i].profit = 0.0;
      SymbolMetrics[i].trades = 0;
      SymbolMetrics[i].profit_factor = 0.0;
      SymbolMetrics[i].drawdown = 0.0;
      SymbolMetrics[i].recovery_factor = 0.0;
     }
return(INIT_SUCCEEDED);
}

//############################
// Trade Handling and Updates
//############################
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
   if (trans.type == TRADE_TRANSACTION_DEAL_ADD) // New deal added to history
   {
      ulong ticket = trans.deal;
      string deal_symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
      double deal_profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);

      // Update metrics for the corresponding symbol
      for (int i = 0; i < NumberOfTradeableSymbols; i++)
      {
         if (SymbolArray[i] == deal_symbol)
         {
            SymbolMetrics[i].profit += deal_profit;
            SymbolMetrics[i].trades++;
            break; // Exit the loop once the symbol is found
         }
      }
   }
}

//############################
// Metric Calculations
//############################
double CalculateProfitFactor(int symbol_index)
{
   double gross_profit = 0.0;
   double gross_loss = 0.0;

   HistorySelect(0, TimeCurrent());
   for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = HistoryDealGetTicket(i);
      string deal_symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);

      if (deal_symbol == SymbolArray[symbol_index])
      {
         double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
         if (profit > 0)
            gross_profit += profit;
         else
            gross_loss += -profit;
      }
   }

   // Avoid division by zero
   if (gross_loss == 0.0)
      return 0.0;

   return gross_profit / gross_loss; // Profit factor = Gross Profit / Gross Loss
}

double CalculateComplexCriterion(int symbol_index)
{
   double profit = SymbolMetrics[symbol_index].profit;
   double drawdown = SymbolMetrics[symbol_index].drawdown;
   int trades = SymbolMetrics[symbol_index].trades;

   // Avoid division by zero
   if (drawdown <= 0.0 || trades == 0)
      return 0.0;

   return (profit / drawdown) * trades;
}

double CalculateMaxDrawdownPercent(int symbol_index)
{
   double equity_peak = -DBL_MAX;
   double equity_trough = DBL_MAX;

   HistorySelect(0, TimeCurrent());
   for (int i = HistoryDealsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = HistoryDealGetTicket(i);
      string deal_symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);

      if (deal_symbol == SymbolArray[symbol_index])
      {
         double cumulative_profit = SymbolMetrics[symbol_index].profit;

         // Update equity peak and trough
         if (cumulative_profit > equity_peak)
            equity_peak = cumulative_profit;
         if (cumulative_profit < equity_trough)
            equity_trough = cumulative_profit;
      }
   }

   if (equity_peak <= 0.0)
      return 0.0;

   double max_drawdown = equity_peak - equity_trough;
   return (max_drawdown / equity_peak) * 100.0;
}

double CalculateRecoveryFactor(int symbol_index)
{
   double profit = SymbolMetrics[symbol_index].profit;
   double max_drawdown = SymbolMetrics[symbol_index].drawdown;

   if (max_drawdown <= 0.0)
      return 0.0;

   return profit / max_drawdown;
}

double OnTester()
{
    return 1.0; // Return dummy result
}

//############################
// Optimization Metrics Output
//############################
void OnTesterPass()
{
   CurrentPassNumber++;

   for (int i = 0; i < NumberOfTradeableSymbols; i++)
   {
      string current_symbol = SymbolArray[i];
      double complex_criterion = CalculateComplexCriterion(i);
      double profit = SymbolMetrics[i].profit;
      int trades = SymbolMetrics[i].trades;
      double profit_factor = CalculateProfitFactor(i);
      double equity_dd_percent = CalculateMaxDrawdownPercent(i);
      double recovery_factor = CalculateRecoveryFactor(i);

      FrameAdd(current_symbol + "_ComplexCriterion", CurrentPassNumber, complex_criterion, "Complex Criterion");
      FrameAdd(current_symbol + "_Profit", CurrentPassNumber, profit, "Profit");
      FrameAdd(current_symbol + "_Trades", CurrentPassNumber, trades, "Trades");
      FrameAdd(current_symbol + "_ProfitFactor", CurrentPassNumber, profit_factor, "Profit Factor");
      FrameAdd(current_symbol + "_EquityDDPercent", CurrentPassNumber, equity_dd_percent, "Equity Drawdown Percent");
      FrameAdd(current_symbol + "_RecoveryFactor", CurrentPassNumber, recovery_factor, "Recovery Factor");
   }
}

//############################
// Frame Processing and CSV Output
//############################
void OnTesterDeinit()
{
   int file_handle = FileOpen(CsvFileName, FILE_WRITE | FILE_CSV, ';');
   if (file_handle == INVALID_HANDLE)
   {
      Print("Error: Unable to open CSV file for writing!");
      return;
   }

   // Write the CSV header
   FileWrite(file_handle, "Pass", "Symbol", "ComplexCriterion", "Profit", "Trades", "ProfitFactor", "EquityDD%", "RecoveryFactor");

   ulong frame_id;
   string frame_name;
   long pass_number;
   double frame_value;

   if (FrameFirst())
   {
      do
      {
         if (FrameNext(frame_id, frame_name, pass_number, frame_value))
         {
            // Extract symbol and metric name from frame_name
            int separator_pos = StringFind(frame_name, "_");
            if (separator_pos != -1)
            {
               string symbol_name = StringSubstr(frame_name, 0, separator_pos);
               string metric_name = StringSubstr(frame_name, separator_pos + 1);

               FileWrite(file_handle, pass_number, symbol_name, metric_name, frame_value);
            }
         }
      } while (FrameNext(frame_id, frame_name, pass_number, frame_value));
   }

   FileClose(file_handle);
}
 
You should call FrameAdd in the OnTester. You can find all details in the algotrading book. Especially you should clarify for yourself, which event handlers are fired on which part of the distributed system "MT5 <-> tester agents".
MQL5 Book: Trading automation / Testing and optimization of Expert Advisors / Getting data frames in terminal
MQL5 Book: Trading automation / Testing and optimization of Expert Advisors / Getting data frames in terminal
  • www.mql5.com
Frames sent from testing agents by the FrameAdd function are delivered into the terminal and written in the order of receipt to an mqd file having...