Emulación de operaciones de depósito y retirada

El probador de MetaTrader 5 le permite emular operaciones de depósito y retirada, lo que le da la posibilidad de experimentar con algunos sistemas de gestión de dinero.

bool TesterDeposit(double money)

La función TesterDeposit repone la cuenta en el proceso de simulación del tamaño de la cantidad depositada en el parámetro de dinero. El importe se indica en la moneda del depósito de prueba.

bool TesterWithdrawal(double money)

La función TesterWithdrawal realiza extracciones iguales a money.

Ambas funciones devuelven true como señal de éxito.

Como ejemplo, consideremos un Asesor Experto basado en la estrategia «carry trade». Para ello, debemos seleccionar un símbolo con grandes swaps positivos en una de las direcciones de trading, por ejemplo, comprar AUDUSD. El Asesor Experto abrirá una o más posiciones en la dirección especificada. Las posiciones no rentables se mantendrán para acumular swaps en ellas. Las posiciones rentables se cerrarán al alcanzar una cantidad predeterminada de beneficios por lote. Los swaps ganados se retirarán de la cuenta. El código fuente está disponible en el archivo CrazyCarryTrade.mq5.

En los parámetros de entrada, el usuario puede seleccionar la dirección de la operación, el tamaño de la misma (0 por defecto, que significa el lote mínimo) y el beneficio mínimo por lote, al que se cerrará una posición rentable.

enum ENUM_ORDER_TYPE_MARKET
{
   MARKET_BUY = ORDER_TYPE_BUY,
   MARKET_SELL = ORDER_TYPE_SELL
};
   
input ENUM_ORDER_TYPE_MARKET Type;
input double Volume;
input double MinProfitPerLot = 1000;

En primer lugar, probemos en el manejador OnInit el rendimiento de las funciones TesterWithdrawal y TesterDeposit. En concreto, el intento de retirar un saldo doble dará lugar al error 10019.

int OnInit()
{
   PRTF(TesterWithdrawal(AccountInfoDouble(ACCOUNT_BALANCE) * 2));
   /*
   not enough money for 20 000.00 withdrawal (free margin: 10 000.00)
   TesterWithdrawal(AccountInfoDouble(ACCOUNT_BALANCE)*2)=false / MQL_ERROR::10019(10019)
   */
   ...

Sin embargo, las siguientes retiradas y acreditaciones de 100 unidades de la divisa de la cuenta se realizarán correctamente.

   PRTF(TesterWithdrawal(100));
   /*
   deal #2 balance -100.00 [withdrawal] done
   TesterWithdrawal(100)=true / ok
   */
   PRTF(TesterDeposit(100)); // return the money 
   /*
   deal #3 balance 100.00 [deposit] done
   TesterDeposit(100)=true / ok
   */
   return INIT_SUCCEEDED;
}

En el manejador OnTick, vamos a comprobar la disponibilidad de las posiciones utilizando PositionFilter y a llenar el array values con sus ganancias/pérdidas actuales y swaps acumulados.

void OnTick()
{
   const double volume = Volume == 0 ?
      SymbolInfoDouble(_SymbolSYMBOL_VOLUME_MIN) : Volume;
   ENUM_POSITION_PROPERTY_DOUBLE props[] = {POSITION_PROFITPOSITION_SWAP};
   double values[][2];
   ulong tickets[];
   PositionFilter pf;
   pf.select(propsticketsvaluestrue);
   ...

Cuando no hay posiciones, abrimos una en una dirección predefinida.

   if(ArraySize(tickets) == 0// no positions 
   {
      MqlTradeRequestSync request1;
      (Type == MARKET_BUY ? request1.buy(volume) : request1.sell(volume));
   }
   else
   {
      ... // there are positions - see the next box
   }

Cuando hay posiciones, las recorremos en un ciclo y cerramos aquellas para las que hay beneficios suficientes (ajustados a los swaps). Al hacerlo, sumamos también los intercambios de posiciones cerradas y las pérdidas totales. Dado que los swaps crecen en proporción al tiempo, los utilizamos como factor amplificador para cerrar posiciones «antiguas». Así, es posible cerrar con pérdidas.

      double loss = 0swaps = 0;
      for(int i = 0i < ArraySize(tickets); ++i)
      {
         if(values[i][0] + values[i][1] * values[i][1] >= MinProfitPerLot * volume)
         {
            MqlTradeRequestSync request0;
            if(request0.close(tickets[i]) && request0.completed())
            {
               swaps += values[i][1];
            }
         }
         else
         {
            loss += values[i][0];
         }
      }
      ...

Si las pérdidas totales aumentan, periódicamente abrimos posiciones adicionales, pero lo hacemos con menos frecuencia cuando hay más posiciones, para controlar de alguna manera los riesgos.

      if(loss / ArraySize(tickets) <= -MinProfitPerLot * volume * sqrt(ArraySize(tickets)))
      {
         MqlTradeRequestSync request1;
         (Type == MARKET_BUY ? request1.buy(volume) : request1.sell(volume));
      }
      ...

Por último, eliminamos los swaps de la cuenta.

      if(swaps >= 0)
      {
         TesterWithdrawal(swaps);
      }

En el manejador OnDeinit, mostramos estadísticas sobre las deducciones.

void OnDeinit(const int)
{
   PrintFormat("Deposit: %.2f Withdrawals: %.2f",
      TesterStatistics(STAT_INITIAL_DEPOSIT),
      TesterStatistics(STAT_WITHDRAWAL));
}

Por ejemplo, al ejecutar el Asesor Experto con la configuración predeterminada para el período comprendido entre 2021 y principios de 2022, obtenemos el siguiente resultado para AUDUSD:

   final balance 10091.19 USD
   Deposit: 10000.00 Withdrawals: 197.42

Este es el aspecto del informe y el gráfico:

Informe pericial con las retiradas de fondos de la cuenta

Informe pericial con las retiradas de fondos de la cuenta

Así, operando con un lote mínimo y cargando un depósito de no más del 1 % durante algo más de un año, conseguimos retirar unos 200 USD.