Diferencias entre script y resultado de backtest

 

He estado trabajando en un código y llegue a un punto donde me estoy dando cuenta que el backtest se comporta de manera errática realizando acciones y modificando variables que no se ven en el "Journal" del backtest pero si puedo verlas reflejadas al imprimir estas variables. Principalmente algo que no llego a entender es como el test trabaja sobre el balance. En mi código cada vez que se envía una orden de compra uso la "funcionAccountInfoDouble(ACCOUNT_MARGIN_FREE)" para obtener el margin disponible en ese momento y calcular el volumen acorde y asi evitar problemas de falta de dinero o un volumen grande en la compra. Al mismo tiempo me aseguro de cerrar la orden antes de abrir otra de forma que no queden ordenes abiertas que pudieran estar tomando parte del free margin. A manera de controlar imprimo cada vez que se ejecuta una compra el valor actual del balance, free margin, precio, volumen, etc. Y lo que estoy viendo es que durante el backtest el valor de free margin, equity y balance se ven disminuidos drasticamente como si deliberadamente el test apalancara la orden por 10 en momento donde hay perdidas con porcentajes altos, aún cuando las opciones de tester no están habilitando el apalancamiento. Lo que es peor al correr el mismo EA con diferentes depositos iniciales veo que a partir de determinado valor el algoritmo pasa de realizar casi 60 trades como deberñia a realizar 1 solo. Por ejemplo si el balance es 4000 hace 60 trades pero si el balance es 3000 hace 1 solo, este limite se da en torno al valor en divisa del volumen maximo permitido por el broker pero no estoy seguro si esta relacionado con esto.
Quisiera saber primero porque el backtest tiene este comportamiento y que cosas se pueden mejorar el código para evitarlo, es decir si existe variable ocultas que no se ven o mejores formas para trabajar con el balance disponible o si existe una cantidad de variables internas que no se ven pero que afectan el comportamiento y por tanto más allá de la información en la cuenta tenemos que trabajar con ellas por ejemplo he notado que  si imprimo el resultado de "ACCOUNT_MARGIN_FREE" obtengo un valor distinto a funcionAccountInfoDouble(ACCOUNT_MARGIN_FREE), es como si hubiera un valor de free margin distinto al que vemos en la cuenta, esto me hace sospechar que mas alla del volumen que pongo en la compra puede haber variables ocultas de apalancamiento u otras que estan teniendo efectos inesperados en el test. Les dejo el código:

//+------------------------------------------------------------------+
//|                                                     RobotSMA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//---
#include <Trade\Trade.mqh>
//#include <Trade\AccountInfo.mqh>


// Input Variables

input int ma1_period = 8; // MA Slow
input int ma2_period = 50; // MA Fast
input int rsi_period = 14;  // RSI period
input int rsi_bz = 30;  // RSI Buy Zone
input int rsi_sz = 70;  // RSI Sell Zone
input int macd_fast = 12;   // MACD fast EMA period
input int macd_slow = 26;   // MACD slow EMA period
input int macd_signal = 12;  // MACD signal line period
input double pips_sl = 10; // Stop Loss
input double pips_tp = 0.0112; // Take Profit

// Internal Variables
datetime date = 0;  // Declaración de la variable 'date'
double entry = 0;
int handle_ma1 = 0;
int handle_ma2 = 0;
int handle_rsi = 0;  // Declaration of 'handle_rsi'
int handle_macd = 0;
double array_ma_fast[];
double array_ma_slow[];
double array_rsi[];
double array_macd_main[];
double array_macd_signal[];
double array_macd_histogram[];
double perc_sl = 1-pips_sl/100;
double perc_tp = 1+pips_tp/100;
double balance = 0;
double balance2 = 0;
double balance3 = 0;
double balance4 = 0;
double balance5 = 0;
double vol=0;
int ticket = 0;
bool result;
double last_buy =0;
bool buy_context = false;
int retryAttempts = 3;
int retryCount = 0;
CTrade         trade; 
//CAccountInfo      account;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   handle_ma1 = iMA(Symbol(),Period(),ma1_period, 0, MODE_EMA,PRICE_CLOSE);
   handle_ma2 = iMA(Symbol(),Period(),ma2_period, 0, MODE_EMA,PRICE_CLOSE);
   handle_rsi = iRSI(Symbol(), Period(), rsi_period, PRICE_CLOSE);
   handle_macd = iMACD(Symbol(), Period(), macd_fast, macd_slow, macd_signal,PRICE_CLOSE);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+

void OnTick()
  {
   datetime LocalTime=TimeLocal();
   string HoursAndMinutes=TimeToString(LocalTime,TIME_MINUTES);
   string YearAndDate=TimeToString(LocalTime,TIME_DATE);
   MqlDateTime DateTimeStructure;
   TimeToStruct(LocalTime, DateTimeStructure);
   int day = DateTimeStructure.day_of_week;
   int hour = DateTimeStructure.hour;
   
   
   
   if (iTime(Symbol(),Period(),0) != date){
      
      entry = SymbolInfoDouble(Symbol(), SYMBOL_BID);
      
      if (Buy_Signal() && buy_context == false){
         
         entry = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
         balance = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
         balance2 = AccountInfoDouble(ACCOUNT_EQUITY);
         balance3 = AccountInfoDouble(ACCOUNT_BALANCE);
         balance4 = AccountInfoDouble(ACCOUNT_CREDIT);
         balance5 = AccountInfoDouble(ACCOUNT_PROFIT);
         vol = balance/entry;
         vol = MathFloor(vol * MathPow(10, 1)) / MathPow(10, 1);
         double min_volume = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),_Digits);
         double max_volume = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX),_Digits);
         
         if(vol > max_volume){vol = max_volume;}
         Print("Max Volume=",max_volume,"Min Volume=",min_volume);
         Print("Volume=",NormalizeDouble(vol,_Digits), " Price=", NormalizeDouble(entry, _Digits), " Free Margin=", balance, " Equity=", balance2, " Balance=", balance3, " Credit=", balance4);
         result = trade.Buy(NormalizeDouble(vol,_Digits), Symbol(), NormalizeDouble(entry, _Digits), "Buy Order");
         if(result){
         Print("Buy Order Executed correctly");
         }else{
         Print("Buy Order Failed");
         ExpertRemove();
         }
         
         buy_context = true;
         last_buy = entry;
         
      }else if (Sell_Signal() && buy_context == true){
            //ticket = GetOpenPositionTicket();
            ticket = PositionGetTicket(PositionsTotal()-1);
            Print("Open position ticket: ", ticket);
            result = trade.PositionClose(ticket);
            
            // Check the result of the closing operation
            if (result){
               Print("Order with ticket ", ticket, " closed successfully.");
               buy_context = false;
            }
            else{
            Print("Error closing order with ticket ", ticket, ". Error code: ", GetLastError());
    
            }
            
      }
      
      else if  (buy_context == true && entry < last_buy * perc_sl){
            //ticket = GetOpenPositionTicket();
            ticket = PositionGetTicket(PositionsTotal()-1);
            Print("Open position ticket: ", ticket);
            result = trade.PositionClose(ticket);
            // Check the result of the closing operation
            if (result){
               Print("Order with ticket ", ticket, " closed successfully.");
               buy_context = false;
            }
            else{
               Print("Error closing order with ticket ", ticket, ". Error code: ", GetLastError());
            }
        } 
      else if (buy_context == true && entry > last_buy * perc_tp){ 
            //ticket = GetOpenPositionTicket();
            ticket = PositionGetTicket(PositionsTotal()-1);
            Print("Open position ticket: ", ticket);
            result = trade.PositionClose(ticket);
            // Check the result of the closing operation
            if (result){
            Print("Order with ticket ", ticket, " closed successfully.");
               buy_context = false;
            }
            else{
               Print("Error closing order with ticket ", ticket, ". Error code: ", GetLastError());
            }
        }
       else if (buy_context == true && day == 5 && hour == 22){
         Print("Closing open orders because is the end of Friday.");
         //ticket = GetOpenPositionTicket();
         ticket = PositionGetTicket(PositionsTotal()-1);
         Print("Open position ticket: ", ticket);
         result = trade.PositionClose(ticket);
         // Check the result of the closing operation
         if (result){
            Print("Order with ticket ", ticket, " closed successfully.");
            buy_context = false;
            }
         else{
            Print("Error closing order with ticket ", ticket, ". Error code: ", GetLastError());
            }
        }    
        

      

  
      date = iTime(Symbol(),Period(),0);
   }
   
  }
//+------------------------------------------------------------------+


// Funciones 

bool Buy_Signal (){
   
//   if (array_ma_fast[0] > array_ma_slow[0] && array_ma_fast[1] < array_ma_slow[1] &&
   ArraySetAsSeries(array_ma_fast,true);
   ArraySetAsSeries(array_ma_slow,true);
   CopyBuffer(handle_ma1,0,1,2,array_ma_fast);
   CopyBuffer(handle_ma2,0,1,2,array_ma_slow);
   
   ArraySetAsSeries(array_rsi, true);
   CopyBuffer(handle_rsi, 0, 1, 2, array_rsi);

   ArraySetAsSeries(array_macd_main, true);
   ArraySetAsSeries(array_macd_signal, true);
   ArraySetAsSeries(array_macd_histogram, true);
   CopyBuffer(handle_macd, 0, 1, 2, array_macd_main);
   CopyBuffer(handle_macd, 1, 1, 2, array_macd_signal);
   CopyBuffer(handle_macd, 2, 1, 2, array_macd_histogram);
   
   if(     array_rsi[0] < rsi_bz &&  // RSI is below 30 (oversold condition)
        array_macd_main[0] > array_macd_signal[0] && array_macd_main[1] < array_macd_signal[1]){
      return true;
   } else {
      return false;
   }    
}

bool Sell_Signal(){


     ArraySetAsSeries(array_ma_fast,true);
     ArraySetAsSeries(array_ma_slow,true);
     CopyBuffer(handle_ma1,0,1,2,array_ma_fast);
     CopyBuffer(handle_ma2,0,1,2,array_ma_slow);
   
     ArraySetAsSeries(array_rsi, true);
     CopyBuffer(handle_rsi, 0, 1, 2, array_rsi);

     ArraySetAsSeries(array_macd_main, true);
     ArraySetAsSeries(array_macd_signal, true);
     ArraySetAsSeries(array_macd_histogram, true);
     CopyBuffer(handle_macd, 0, 1, 2, array_macd_main);
     CopyBuffer(handle_macd, 1, 1, 2, array_macd_signal);
     CopyBuffer(handle_macd, 2, 1, 2, array_macd_histogram);
     
     
//   if (array_ma_fast[0] < array_ma_slow[0] && array_ma_fast[1] > array_ma_slow[1] &&
    if (array_rsi[0] > rsi_sz){  // RSI is above 70 (overbought condition)
//      if (array_macd_main[0] < array_macd_signal[0] && array_macd_main[1] > array_macd_signal[1]){
      return true;
   } else {
      return false;
   }    
}

ulong GetOpenPositionTicket()
{
    for (int i = 0; i < PositionsTotal(); i++)
    {
        ulong ticket = PositionGetTicket(i);
        
        // Check if the position is open
        if (PositionSelectByTicket(ticket) && PositionGetSymbol(i) == Symbol() && PositionGetInteger(POSITION_TYPE) <= ORDER_TYPE_BUY)

        {
            return ticket;
        }
    }

    return 0;  // No open position found
}