English Русский 中文 Deutsch 日本語 Português
preview
Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios

Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios

MetaTrader 5Ejemplos | 7 junio 2024, 12:49
715 0
Wanateki Solutions LTD
Kelvin Muturi Muigua

Introducción

En el mundo del desarrollo de software, los paradigmas de programación suponen los principios rectores para escribir y organizar el código. Al igual que elegimos diferentes rutas para llegar a un destino, existen diferentes enfoques o paradigmas de programación para realizar tareas utilizando MQL5.

En una serie de dos artículos, revisaremos los paradigmas básicos de programación necesarios para crear herramientas comerciales utilizando MQL5. Nuestro objetivo será compartir prácticas eficaces y recomendadas que generen grandes resultados con un código breve y eficiente. Además, explicaremos cada estilo de programación y lo demostraremos creando un asesor totalmente funcional.

Tipos de paradigmas de programación

Existen tres paradigmas principales de programación que todo desarrollador MQL5 debe conocer:

  1. Programación procedimental: Este artículo se centrará en este paradigma concreto.
  2. Programación funcional: Este paradigma también se tratará en el artículo, ya que resulta muy similar a la programación procedimental.
  3. Programación orientada a objetos (POO): Este paradigma lo analizaremos en el próximo artículo.
Cada paradigma posee sus propias reglas y propiedades únicas diseñadas para resolver tareas concretas y desarrollar eficazmente diversas herramientas comerciales utilizando MQL5.


Programación procedimental

La programación procedimental supone un enfoque sistemático, paso a paso, de la escritura de código. Consiste en descomponer cualquier problema en una secuencia de instrucciones precisas, como cuando preparamos una comida siguiendo una receta. Los programadores crean un camino determinado para el ordenador, dándole un algoritmo claro paso a paso para lograr el resultado deseado.

Tanto si usted es nuevo en la programación como si solo le interesa la organización del código, la programación procedimental le ofrecerá un punto de entrada sencillo e intuitivo en un tema.

Propiedades básicas de la programación procedimental


  1. Funciones:
    El núcleo de la programación procedimental son las funciones. Son conjuntos de instrucciones agrupadas para realizar una tarea específica. Las funciones encapsulan la funcionalidad, lo cual permite la modularidad y la reutilización del código.
  2. Diseño descendente:
    En la programación procedimental, con frecuencia se utiliza un enfoque descendente para el diseño. Los desarrolladores dividen el problema en subtareas más pequeñas y manejables. Cada subtarea se resuelve individualmente, contribuyendo a la solución global.
  3. Estilo imperativo:
    La naturaleza imperativa, o de órdenes, de la programación procedimental, implica declaraciones explícitas que modifican el estado del programa. Los desarrolladores especifican cómo debe resolver un programa un problema usando una serie de órdenes procedimentales.
  4. Variables y datos:
    Los procedimientos o funciones de la programación procedimental manipulan variables y datos. Estas variables pueden almacenar valores que cambien durante la ejecución de un programa. Los cambios de estado suponen un aspecto fundamental de la programación procedimental.
  5. Rendimiento constante:
    La ejecución del programa tiene lugar de forma secuencial. Las sentencias se ejecutan una tras otra, y las estructuras de control como los ciclos y expresiones condicionales guían el flujo del programa.
  6. Modularidad:
    La programación procedimental fomenta la modularidad, organizando para ello el código en procedimientos o funciones. Cada módulo se ocupa de un aspecto concreto de la funcionalidad del programa, lo que mejora la organización del código y su mantenimiento.
  7. Posibilidad de reutilización:
    La capacidad de reutilización del código es una ventaja clave de la programación procedimental. Una vez escrita y probada una función, podremos utilizarla siempre que necesitemos esa funcionalidad concreta en el programa, lo cual reduce la redundancia y aumenta la eficacia.
  8. Legibilidad:
    El código procedimental suele ser más legible, sobre todo para quienes están acostumbrados al enfoque paso a paso. El flujo lineal de ejecución facilita el seguimiento de la lógica del programa.

Programación funcional

La programación funcional se apoya en el concepto de las funciones como ciudadanos de primera clase y sobre la inmutabilidad. El paradigma es similar a la programación procedimental, excepto por el principio básico de procesamiento de datos y ejecución de tareas.

A diferencia de la programación procedimental, en la que los datos pueden cambiar de aspecto y función durante la ejecución de un programa, la programación funcional prefiere un entorno más estable. Una vez creados los datos, estos permanecen inalterados. El compromiso con la inmutabilidad ofrece un cierto nivel de previsibilidad y ayuda a evitar efectos secundarios imprevistos en el código.

Propiedades básicas de la programación funcional



  1. Inmutabilidad:
    Es el principio básico de la programación funcional. Una vez creados los datos, estos permanecen inalterados. En lugar de modificar los datos existentes, se crean nuevos datos con los cambios necesarios. Esto posibilita una mayor previsibilidad y ayuda a evitar efectos secundarios imprevistos.
  2. Funciones como ciudadanos de primera clase:
    Las funciones se tratan como elementos de primera clase, lo cual significa que pueden asignarse a variables, transmitirse como argumentos a otras funciones y retornarse como resultados de otras funciones. Esta flexibilidad permite crear funciones de orden superior y fomenta un estilo de codificación más modular y expresivo.
  3. Estilo declarativo:
    La programación funcional favorece un estilo de programación declarativo, centrado en lo que debe conseguir el programa y no en cómo conseguirlo. Esto redunda en un código más conciso y legible.
  4. Prevención de estados volátiles:
    La condición a cambiar se minimiza o elimina. Los datos se consideran inmutables y las funciones evitan los cambios de estado externos. Esta característica simplifica el razonamiento sobre el comportamiento de las funciones.
  5. Recursión y funciones de orden superior:
    La recursión y las funciones de orden superior se usan habitualmente en la programación funcional, cuando las funciones toman otras funciones como argumentos o las devuelven como resultados. Así se logra un código más modular y reutilizable.


Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios (Price Action)

Ahora que nos hemos familiarizado con la esencia de los paradigmas de la programación procedimental y funcional, podremos en práctica la teoría: vamos a crear un asesor basado en la dinámica de precios. En primer lugar, hablaremos de la estrategia comercial que vamos a automatizar. Más adelante, recorreremos los distintos componentes del código, descubriendo su funcionalidad y cómo trabajan juntos a la perfección.


Estrategia de dinámica de precios con el indicador EMA


Nuestra estrategia comercial se basa en un único indicador conocido como Media Móvil Exponencial (EMA). El indicador se usa ampliamente en el análisis técnico y ayuda a determinar la dirección del mercado según la configuración comercial seleccionada. La media móvil es un indicador estándar en MQL5, lo cual simplifica su inclusión en nuestro código.


Compra:
Abrimos una posición de Compra cuando la última vela cerrada sea alcista y sus precios mínimo y máximo estén por encima de la media móvil exponencial (EMA).

Señal de compra en la estrategia de dinámica de precios EMA


Venta:
Abrimos una posición de Venta cuando la última vela cerrada sea bajista y sus precios mínimo y máximo estén por debajo de la media móvil exponencial (EMA).

Señal de venta en la estrategia de dinámica de precios EMA


Salida:
Cerramos automáticamente todas las posiciones abiertas y obtenemos los beneficios o pérdidas correspondientes cuando se alcance el porcentaje de beneficios o pérdidas especificado por el usuario para la cuenta, o utilicemos órdenes tradicionales de stop loss o take profit.


Ajustes Condiciones generales
Compra La última vela cerrada es alcista y sus precios mínimo y máximo están por encima de la media móvil exponencial (EMA).
Venta La última vela cerrada es bajista y sus precios mínimo y máximo están por debajo de la media móvil exponencial (EMA).
Salida  cerramos todas las posiciones abiertas y obtenemos los beneficios o pérdidas correspondientes cuando se alcance el porcentaje de beneficios/pérdidas especificado por el usuario para la cuenta o se active un stop loss/take profit.


Aplicación de una estrategia comercial en el código

Ahora que hemos establecido nuestras reglas y nuestro plan de trading, vamos a poner en práctica nuestra estrategia comercial escribiendo un código MQL5 en el entorno del MetaEditor. Siga los pasos que indicamos a continuación para empezar con una plantilla de asesor limpia que contenga únicamente las características obligatorias requeridas:


Paso 1: Abra el MetaEditor e inicie el Wizard pulsando el botón "Crear".

Creamos un nuevo asesor en el Wizard MQL5


Segundo paso: Seleccione "Asesor (plantilla)" y haga clic en "Siguiente".

Creamos un nuevo asesor en el Wizard MQL5


Paso 3: En "Parámetros generales", introduzca el nombre del asesor y clique en "Siguiente".

Creamos un nuevo asesor en el Wizard MQL5


Paso 4: Asegúrese de que no hay ninguna opción seleccionada en el apartado "Controladores de eventos". Desactive las casillas de verificación, si las hay, y clique en Siguiente.

Creamos un nuevo asesor en el Wizard MQL5


Paso 5: Asegúrese de que no hay ninguna opción seleccionada en el apartado "Controladores de eventos de prueba". Desmarque todas las opciones si están seleccionadas, y luego haga clic en "Listo" para crear una plantilla de asesor MQL5.

Creamos un nuevo asesor en el Wizard MQL5


Ahora tenemos una plantilla de asesor MQL5 limpia con las funciones obligatorias (OnInit, OnDeinit y OnTick). No olvide guardar el nuevo archivo antes de continuar.

Este es el aspecto de nuestro código de asesor recién generado:

//+------------------------------------------------------------------+
//|                                               PriceActionEMA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+


En primer lugar, escribimos una breve descripción del asesor y declaramos e inicializamos las variables de usuario y, a continuación, declaramos todas las variables globales. Luego colocamos este código justo antes de la función OnInit().

#property description "A price action EA to demonstrate how to "
#property description "implement the procedural programming paradigm."

//--User input variables
input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable

input group ""
input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe
input int emaPeriod = 20;//Moving Average Period
input int emaShift = 0;//Moving Average Shift

input group ""
input bool enableTrading = true;//Enable Trading
input bool enableAlerts = false;//Enable Alerts

input group ""
input double accountPercentageProfitTarget = 10.0;//Account Percentage (%) Profit Target
input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target

input group ""
input int maxPositions = 3;//Max Positions (Max open positions in one direction)
input int tp = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable])
input int sl = 10000;//SL (Stop Loss Points/Pips [Zero (0) to diasable])


Como ya sabemos, "en el corazón de la programación procedimental se encuentran las funciones. Son conjuntos de instrucciones agrupadas para realizar una tarea específica. Las funciones encapsulan la funcionalidad, lo cual posibilita la modularidad y la reutilización del código". Vamos a crear funciones personalizadas.

Función GetInit:

Esta función es responsable de inicializar todas las variables globales y realizar cualquier otra tarea al cargar o inicializar el asesor.

int GetInit() //Function to initialize the robot and all the variables
  {
   int returnVal = 1;
//create the iMA indicator
   emaHandle = iMA(Symbol(), tradingTimeframe, emaPeriod, emaShift, MODE_EMA, PRICE_CLOSE);
   if(emaHandle < 0)
     {
      Print("Error creating emaHandle = ", INVALID_HANDLE);
      Print("Handle creation: Runtime error = ", GetLastError());
      //force program termination if the handle is not properly set
      return(-1);
     }
   ArraySetAsSeries(movingAverage, true);

//reset the count for positions
   totalOpenBuyPositions = 0;
   totalOpenSellPositions = 0;
   buyPositionsProfit = 0.0;
   sellPositionsProfit = 0.0;
   buyPositionsVol = 0.0;
   sellPositionsVol = 0.0;

   closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);

   startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);//used to calculate the account percentage profit

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been LOADED in the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }

//structure our comment string
   commentString = "\n\n" +
                   "Account No: " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) +
                   "\nAccount Type: " + EnumToString((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)) +
                   "\nAccount Leverage: " + IntegerToString(AccountInfoInteger(ACCOUNT_LEVERAGE)) +
                   "\n-----------------------------------------------------------------------------------------------------";
   return(returnVal);
  }


Función GetDeinit:

Esta función se encarga de liberar toda la memoria usada, borrar los comentarios del gráfico y realizar otras tareas de desinicialización antes de borrar el asesor.

void GetDeinit()  //De-initialize the robot on shutdown and clean everything up
  {
   IndicatorRelease(emaHandle); //delete the moving average handle and deallocate the memory spaces occupied
   ArrayFree(movingAverage); //free the dynamic arrays containing the moving average buffer data

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been REMOVED from the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }
//delete and clear all chart displayed messages
   Comment("");
  }

Función GetEma:

Esta función extrae el valor de la media móvil exponencial y determina la dirección del mercado comparando el valor de la EMA con los precios de apertura, cierre, máximo y mínimo de una vela cerrada recientemente. Luego almacena el valor de la dirección del mercado en variables globales para su posterior procesamiento por otras funciones.

void GetEma()
  {
//Get moving average direction
   if(CopyBuffer(emaHandle, 0, 0, 100, movingAverage) <= 0)
     {
      return;
     }
   movingAverageTrend = "FLAT";
   buyOk = false;
   sellOk = false;
   if(movingAverage[1] > iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] > iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "SELL/SHORT";
      if(iClose(_Symbol, tradingTimeframe, 1) < iOpen(_Symbol, tradingTimeframe, 1))
        {
         sellOk = true;
         buyOk = false;
        }
     }
   if(movingAverage[1] < iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] < iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "BUY/LONG";
      if(iClose(_Symbol, tradingTimeframe, 1) > iOpen(_Symbol, tradingTimeframe, 1))
        {
         buyOk = true;
         sellOk = false;
        }
     }
  }


Función GetPositionsData:

Esta función explora todas las posiciones abiertas, guardando sus propiedades, como el importe de los beneficios, el número total de posiciones abiertas, el número total de posiciones abiertas de compra y venta, y el volumen/lote total de cada tipo de posición. Asimismo, excluye los datos de las posiciones no abiertas por nuestro asesor o aquellas que no coinciden con el número mágico del asesor.

void GetPositionsData()
  {
//get the total number of all open positions and their status
   if(PositionsTotal() > 0)
     {
      //variables for storing position properties values
      ulong positionTicket;
      long positionMagic, positionType;
      string positionSymbol;
      int totalPositions = PositionsTotal();

      //reset the count
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
      buyPositionsProfit = 0.0;
      sellPositionsProfit = 0.0;
      buyPositionsVol = 0.0;
      sellPositionsVol = 0.0;

      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         positionMagic = PositionGetInteger(POSITION_MAGIC);
         positionSymbol = PositionGetString(POSITION_SYMBOL);
         positionType = PositionGetInteger(POSITION_TYPE);

         if(positionMagic == magicNumber && positionSymbol == _Symbol)
           {
            if(positionType == POSITION_TYPE_BUY)
              {
               ++totalOpenBuyPositions;
               buyPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               buyPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
            if(positionType == POSITION_TYPE_SELL)
              {
               ++totalOpenSellPositions;
               sellPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               sellPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
           }
        }
      //Get and save the account percentage profit
      accountPercentageProfit = ((buyPositionsProfit + sellPositionsProfit) * 100) / startingCapital;
     }
   else  //if no positions are open then the account percentage profit should be zero
     {
      startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);
      accountPercentageProfit = 0.0;

      //reset position counters too
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
     }
  }

Función TradingIsAllowed:

Esta función comprueba si el asesor tiene permiso para comerciar por parte del usuario, el terminal y el bróker.

bool TradingIsAllowed()
  {
//check if trading is enabled
   if(enableTrading &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
     )
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS FULLY ENABLED! *** SCANNING FOR ENTRY ***";
      return(true);
     }
   else  //trading is disabled
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS NOT FULLY ENABLED! *** GIVE EA PERMISSION TO TRADE ***";
      return(false);
     }
  }

Función TradeNow:

Esta función es la responsable de abrir nuevas posiciones cuando todas las comprobaciones y señales necesarias indiquen que puede iniciarse una nueva operación.

void TradeNow()
  {
//Detect new candle formation and open a new position
   if(closedCandleTime != iTime(_Symbol, tradingTimeframe, 1))  //-- New candle found
     {
      //use the candle time as the position comment to prevent opening dublicate trades on one candle
      string positionComment = IntegerToString(iTime(_Symbol, tradingTimeframe, 1));

      //open a buy position
      if(buyOk && totalOpenBuyPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_BUY, positionComment)) //no position has been openend on this candle, open a buy position now
           {
            BuySellPosition(POSITION_TYPE_BUY, positionComment);
           }
        }

      //open a sell position
      if(sellOk && totalOpenSellPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_SELL, positionComment)) //no position has been openend on this candle, open a sell position now
           {
            BuySellPosition(POSITION_TYPE_SELL, positionComment);
           }
        }

      //reset closedCandleTime value to prevent new entry orders from opening before a new candle is formed
      closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);
     }
  }

Función ManageProfitAndLoss:

Esta función comprueba si se han alcanzado los umbrales de pérdidas y ganancias establecidos por el usuario. Si se cumplen las condiciones, liquidará todos los beneficios o pérdidas cerrando todas las posiciones abiertas.

void ManageProfitAndLoss()
  {
//if the account percentage profit or loss target is hit, delete all positions
   double lossLevel = -accountPercentageLossTarget;
   if(
      (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) ||
      ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0)
   )
     {
      //delete all open positions
      if(PositionsTotal() > 0)
        {
         //variables for storing position properties values
         ulong positionTicket;
         long positionMagic, positionType;
         string positionSymbol;
         int totalPositions = PositionsTotal();

         //scan all the open positions
         for(int x = totalPositions - 1; x >= 0; x--)
           {
            positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
            positionMagic = PositionGetInteger(POSITION_MAGIC);
            positionSymbol = PositionGetString(POSITION_SYMBOL);
            positionType = PositionGetInteger(POSITION_TYPE);
            int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS);
            double positionVolume = PositionGetDouble(POSITION_VOLUME);
            ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(positionMagic == magicNumber && positionSymbol == _Symbol)
              {
               //print the position details
               Print("*********************************************************************");
               PrintFormat(
                  "#%I64u %s  %s  %.2f  %s [%I64d]",
                  positionTicket, positionSymbol, EnumToString(positionType), positionVolume,
                  DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic
               );

               //reset the the tradeRequest and tradeResult values by zeroing them
               ZeroMemory(tradeRequest);
               ZeroMemory(tradeResult);
               //set the operation parameters
               tradeRequest.action = TRADE_ACTION_DEAL;//type of trade operation
               tradeRequest.position = positionTicket;//ticket of the position
               tradeRequest.symbol = positionSymbol;//symbol
               tradeRequest.volume = positionVolume;//volume of the position
               tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);//allowed deviation from the price
               tradeRequest.magic = magicNumber;//MagicNumber of the position

               //set the price and order type depending on the position type
               if(positionType == POSITION_TYPE_BUY)
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
                  tradeRequest.type = ORDER_TYPE_SELL;
                 }
               else
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
                  tradeRequest.type = ORDER_TYPE_BUY;
                 }

               //print the position close details
               PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
               //send the tradeRequest
               if(OrderSend(tradeRequest, tradeResult)) //trade tradeRequest success, position has been closed
                 {
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
                 }
               else  //trade tradeRequest failed
                 {
                  //print the information about the operation
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("OrderSend error %d", GetLastError());//print the error code
                 }
              }
           }
        }
     }
  }

Función PrintOnChart:

Esta función formatea y muestra el estado del asesor en el gráfico, ofreciendo al usuario una representación visual textual del estado de la cuenta y del asesor.

void PrintOnChart()
  {
//update account status strings and display them on the chart
   accountStatus = "\nAccount Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + accountCurrency +
                   "\nAccount Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + accountCurrency +
                   "\nAccount Profit: " + DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT), 2) + accountCurrency +
                   "\nAccount Percentage Profit: " + DoubleToString(accountPercentageProfit, 2) + "%" +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nTotal Buy Positions Open: " + IntegerToString(totalOpenBuyPositions) +
                   "        Total Vol/Lots: " + DoubleToString(buyPositionsVol, 2) +
                   "        Profit: " + DoubleToString(buyPositionsProfit, 2) + accountCurrency +
                   "\nTotal Sell Positions Open: " + IntegerToString(totalOpenSellPositions) +
                   "        Total Vol/Lots: " + DoubleToString(sellPositionsVol, 2) +
                   "        Profit: " + DoubleToString(sellPositionsProfit, 2) + accountCurrency +
                   "\nPositionsTotal(): " + IntegerToString(PositionsTotal()) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nJust Closed Candle:     Open: " + DoubleToString(iOpen(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Close: " + DoubleToString(iClose(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     High: " + DoubleToString(iHigh(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Low: " + DoubleToString(iLow(_Symbol, tradingTimeframe, 1), _Digits) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nMovingAverage (EMA): " + DoubleToString(movingAverage[1], _Digits) +
                   "     movingAverageTrend = " + movingAverageTrend +
                   "\nsellOk: " + IntegerToString(sellOk) +
                   "\nbuyOk: " + IntegerToString(buyOk);

//show comments on the chart
   Comment(commentString + accountStatus + tradingStatus);
  }


Función BuySellPosition:

Esta función abre nuevas posiciones de compra y venta.

bool BuySellPosition(int positionType, string positionComment)
  {
//reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);
//initialize the parameters to open a position
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.symbol = Symbol();
   tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   tradeRequest.magic = magicNumber;
   tradeRequest.comment = positionComment;
   double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2);

   if(positionType == POSITION_TYPE_BUY)
     {
      if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_BUY;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }

   if(positionType == POSITION_TYPE_SELL)
     {
      if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_SELL;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }
   return(false);
  }

Función PositionFound:

Esta función comprueba si la posición especificada existe, para que el asesor no abra múltiples posiciones repetidas en la misma vela.

bool PositionFound(string symbol, int positionType, string positionComment)
  {
   if(PositionsTotal() > 0)
     {
      ulong positionTicket;
      int totalPositions = PositionsTotal();
      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         if(
            PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == symbol &&
            PositionGetInteger(POSITION_TYPE) == positionType && PositionGetString(POSITION_COMMENT) == positionComment
         )
           {
            return(true);//a similar position exists, don't open another position on this candle
            break;
           }
        }
     }
   return(false);
  }


Ahora que hemos definido nuestras funciones personalizadas, vamos a llamarlas para que ejecuten las tareas previstas.

  • Primero colocaremos y llamaremos a la función GetInit en la función OnInit. 

int OnInit()
  {
//---
   if(GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

  • Luego colocaremos y llamaremos a la función GetDeinit en la función OnDeinit. 

void OnDeinit(const int reason)
  {
//---
   GetDeinit();
  }

  • Y colocaremos y llamaremos a las siguientes funciones en la función OnTick en el orden apropiado. Como algunas funciones modifican variables globales de las que dependen otras funciones para tomar decisiones clave, nos aseguraremos de que sean llamadas en primer lugar, garantizando así que los datos se procesen y actualicen antes de que otras funciones puedan acceder a ellos. 

void OnTick()
  {
//---
   GetEma();
   GetPositionsData();
   if(TradingIsAllowed())
     {
      TradeNow();
      ManageProfitAndLoss();
     }
   PrintOnChart();
  }

Cuando el código del asesor esté listo, lo guardaremos y compilaremos. Esto nos permitirá acceder a nuestro asesor directamente desde nuestro terminal comercial. El código completo se adjunta a continuación.


Probando nuestro asesor en el simulador de estrategias

Debemos asegurarnos de que el asesor funciona según nuestro plan. Podemos hacerlo, o bien cargándolo en el gráfico del símbolo activo y comerciando con él en una cuenta demo, o bien utilizando el simulador de estrategias para realizar una evaluación exhaustiva. Aunque podemos probarlo en una cuenta demo, aquí utilizaremos el simulador de estrategias para evaluar su eficacia.

Estos son los ajustes que aplicaremos en el simulador de estrategias:

  • Bróker: MetaQuotes-Demo (se crea automáticamente al instalar MetaTrader 5)

  • Símbolo: EURUSD

  • Periodo de prueba (intervalo): Último año (de noviembre de 2022 a noviembre de 2023)

  • Modelado: Cada tick basado en ticks reales

  • Depósito inicial: 10.000 USD

  • Apalancamiento: 1:100

Configuración del simulador de estrategias para PriceActionEA


Configuración del simulador de estrategias para PriceActionEA


Analizando los resultados de nuestras pruebas en la historia, vemos que nuestro asesor no solo ha obtenido beneficios, sino que también ha mantenido una reducción sorprendentemente baja. Esta estrategia es prometedora y puede seguir modificándose y optimizándose para lograr resultados aún mejores, sobre todo al aplicarse a varios símbolos simultáneamente.

Resultados de PriceActionEMA

Resultados de PriceActionEMA

Resultados de PriceActionEMA


Conclusión

Incluso los programadores novatos de MQL5 pueden entender fácilmente el código procedimental del asesor que acabamos de crear. Esta simplicidad surge de la naturaleza clara y directa de la programación procedimental, sobre todo al utilizar funciones para organizar el código según las tareas específicas y las variables globales para transmitir los datos modificados a distintas funciones.

Quizá haya notado que una desventaja del código procedimental es su tendencia a ampliarse significativamente a medida que el asesor se hace más complejo, lo cual lo hace adecuado sobre todo para proyectos menos complejos. Cuando el proyecto es muy complejo, será mejor optar por un enfoque orientado a objetos.

En nuestro próximo artículo, aprenderemos más sobre la programación orientada a objetos y convertiremos en un código orientado a objetos nuestro código recién creado de asesor procedimental basado en el impulso de precio. Esto permitirá comparar claramente estos paradigmas y comprender mejor las diferencias.

¡Gracias por su atención! Le deseo éxito en el desarrollo de MQL5 y el trading.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13771

Archivos adjuntos |
PriceActionEMA.mq5 (23.33 KB)
Asesor Experto Grid-Hedge Modificado en MQL5 (Parte I): Creamos un sencillo asesor de cobertura Asesor Experto Grid-Hedge Modificado en MQL5 (Parte I): Creamos un sencillo asesor de cobertura
Hoy crearemos un sencillo asesor de cobertura como base para nuestro asesor Grid-Hedge más avanzado, que será una mezcla de estrategias de rejilla y cobertura clásicas. Al final de este artículo, usted sabrá cómo crear una estrategia de cobertura simple y lo que la gente opina sobre la rentabilidad de esta estrategia.
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 5): Bandas de Bollinger en el Canal de Keltner - Señales de Indicador Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 5): Bandas de Bollinger en el Canal de Keltner - Señales de Indicador
En este artículo, entenderemos por asesor multidivisa un asesor o robot comercial que puede comerciar (abrir/cerrar órdenes, gestionar órdenes, por ejemplo, trailing-stop y trailing-profit, etc.) con más de un par de símbolos de un gráfico. En este artículo, usaremos las señales de dos indicadores, las Bandas de Bollinger® y el Canal de Keltner.
Algoritmos de optimización de la población: Algoritmo de recocido isotrópico simulado (Simulated Isotropic Annealing, SIA). Parte II Algoritmos de optimización de la población: Algoritmo de recocido isotrópico simulado (Simulated Isotropic Annealing, SIA). Parte II
En la primera parte del artículo, hablamos del conocido y popular algoritmo del recocido simulado, analizamos sus ventajas y describimos detalladamente sus desventajas. La segunda parte del artículo se dedicará a la transformación cardinal del algoritmo y su renacimiento en un nuevo algoritmo de optimización, el "recocido isotrópico simulado, SIA".
Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor
Hoy veremos qué es el análisis cuantitativo, cómo lo utilizan los grandes jugadores y crearemos uno de los algoritmos de análisis cuantitativo en MQL5.