English Русский 中文 Deutsch 日本語 Português
preview
Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor

Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor

MetaTrader 5Sistemas comerciales | 6 junio 2024, 16:19
234 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Qué es el análisis cuantitativo en el mercado financiero

Este tipo de análisis surgió como una especie de precursor del aprendizaje automático, siendo de hecho un subconjunto del aprendizaje estadístico. En la época en que los ordenadores solo empezaban a utilizarse, estos ocupaban una habitación entera y funcionaban con tarjetas perforadas, pero los científicos más innovadores ya intentaban adaptarlos para analizar grandes datos y estadísticas. Sí, en aquella época el conjunto de operaciones y funciones estadísticas que podían aplicarse a los precios era extremadamente pequeño, las propias funciones eran bastante sencillas y los patrones encontrados no se distinguían por su complejidad.

Estos estudios se reducían a simples cálculos para identificar alguna relación en los datos, en su mayoría lineal.

La forma más sencilla y fácil de aprender el análisis cuantitativo en los mercados financieros es analizar el spread entre activos relacionados. Por ejemplo, construimos el spread entre dos activos correlacionados y utilizamos el análisis cuantitativo para identificar la desviación media, máxima y mediana de este spread. Una vez cuantificados los datos, ya podemos entender cuánto se ha desviado un activo del otro y comprender aproximadamente el estado de equilibrio de los dos activos al que ambos volverán con toda seguridad cuando la divergencia entre ellos "se cierre" (los activos se acerquen el uno al otro). En general, la aplicación del análisis cuantitativo en el trading de pares es un tema muy interesante, y sin duda lo abordaremos en un futuro artículo. 


Cómo utilizan los fondos de cobertura el análisis cuantitativo

El primer intento de utilizar el análisis cuantitativo fue la práctica de Edward Thorpe, que en la década de 1970 aprendió a analizar el spread entre una acción y la garantía (warrant) de esa acción calculando lo sobrevalorado o infravalorado que está un activo en relación con su garantía. El ordenador de Edward Thorpe ocupaba una habitación entera y aún funcionaba con tarjetas perforadas. Thorpe fue, en general, el primero en aplicar el análisis cuantitativo informático a los mercados financieros. Fue un gran avance de la época, digno del reconocimiento del mundo entero. Thorpe creó el primer fondo de cobertura "cuantitativo" en el mundo. 

Como habrá notado, el primer ejemplo de análisis cuantitativo en el mercado bursátil que nos viene a la mente es su aplicación en el trading por pares, o basket-trading. Sin duda consideraremos estas opciones, pero hoy nuestro algoritmo de análisis cuantitativo se basará en otros principios.

¿De qué otra forma usan el análisis cuantitativo los mayores jugadores en el mercado? 

El arbitraje estadístico permite detectar diferencias en los precios de los instrumentos financieros en distintos mercados o en distintos momentos del tiempo. Esto permite a los fondos identificar y capitalizar oportunidades para negociar de forma rentable en una serie de mercados vinculados. Además, los modelos cuantitativos ayudan a los fondos de cobertura a predecir los futuros movimientos del mercado basándose en datos estadísticos, lo cual ayuda a tomar decisiones comerciales con conocimiento de causa.

La gestión del riesgo es otra aplicación esencial del análisis cuantitativo. Los fondos de cobertura utilizan modelos para evaluar y gestionar el riesgo de sus portafolios. Estos optimizan su combinación de activos ajustada al riesgo para minimizar las pérdidas potenciales. Existen muchos ejemplos de ello, tanto la optimización de portafolios mediante la teoría de portafolios de Markowitz (en este caso jugaremos con el riesgo para que la desviación del portafolio no supere el beneficio potencial), como la gestión del riesgo mediante el sistema VaR; este modelo único nos permite calcular la reducción que no superaremos con una probabilidad del 99%.

Obviamente, el mercado real es a veces bastante difícil de describir con matemáticas; aquí hay ejemplos negativos. Por ejemplo, el fondo de cobertura LTCM calculó en 1998 que sus posiciones no registrarían grandes pérdidas y entró en un juego de convergencia del spread entre bonos estadounidenses lejanos y cercanos basado en un análisis cuantitativo. El impago de Rusia desencadenó la "crisis de los tigres asiáticos" y, como consecuencia, provocó un pánico de efecto mariposa en el mercado de deuda pública estadounidense. Los modelos indicaban al fondo LTCM que el spread era anormalmente alto, que el precio estaba destinado a "retroceder" en la dirección opuesta y que las posiciones del fondo se cerrarían a buen seguro con beneficios.

Como resultado, el fondo aplicó promedios, se apalancó de forma extremadamente agresiva, se cargó de activos en deuda y simplemente reventó, a pesar de que los premios Nobel que formaban parte del personal de la empresa decían que tal resultado resultaría imposible. Por aquel entonces, el modelo de análisis cuantitativo VaR estuvo a punto de arruinar todo el mercado estadounidense, y el Presidente de la Reserva Federal, Alan Greenspan, tuvo que convocar urgentemente a los directores de los principales bancos estadounidenses para que compraran las posiciones de margen del fondo, ya que, de lo contrario, la venta de un exceso tan grande de activos "en el mercado" habría provocado una anulación instantánea de la bolsa estadounidense y un pánico peor que el de la Gran Depresión.

Por lo tanto, resulta importante considerar las colas de distribución normal de probabilidades al aplicar análisis cuantitativos y promediar cualquier métrica. La curva de probabilidad en forma de campana en el caso de los mercados financieros tiene "colas" que reflejan acontecimientos en forma de diversos tipos de cisnes negros. Por un lado, desde un punto de vista estadístico, son extremadamente improbables; por otro, la fuerza de estos acontecimientos destruye los portafolios de los inversores, los portafolios de los fondos de cobertura, liquida las posiciones de margen, colapsa los mercados y cambia los mercados en cada nuevo ciclo. Lo vimos en 1998, lo vimos en 2008, lo vimos en 2020, lo vimos en 2022 y lo veremos aún muchas veces más.

La conclusión es que el análisis cuantitativo aporta bastante a los fondos de cobertura y que éstos lo aplican constantemente en su trabajo diario. Sin embargo, es importante recordar que ninguna función calculará jamás las decisiones de millones de personas, su pánico y sus reacciones ante determinados acontecimientos. También es importante tener en cuenta las colas de la distribución normal, que pueden destruir nuestros depósitos si utilizamos tácticas comerciales agresivas. 


Base del algoritmo: calculamos las ondas de los movimientos

La base de nuestra idea la expresó por primera vez el tráder Artem Zvezdin: en su práctica, considera el tamaño de las ondas de movimiento de precio para comprender lo sobrevalorado o infravalorado que está un activo en relación a sí mismo. Por ejemplo, contamos las ondas alcistas y bajistas de las últimas 500-5000 barras para ver cuánto ha recorrido el precio en cada uno de sus pequeños ciclos. Cada ciclo de movimiento de los precios supone la posición de alguien, el dinero de alguien, las decisiones de compra y venta de alguien. Cada nuevo ciclo es un nuevo nacimiento y muerte del mercado, y usaremos la idea del análisis de los movimientos de precio sin retrocesos, de arriba abajo. Se trata de un conjunto distinto de participantes que actúan más o menos de la misma forma, por lo que partiremos de la hipótesis de que la duración de los ciclos también será siempre más o menos la misma. Vamos a calcular el movimiento medio del precio usando el indicador ZigZag, incluido en el paquete estándar de cualquier terminal MetaTrader 5.

Por lo tanto, vamos a analizar el asesor que hemos creado en este artículo. En primer lugar, echaremos un vistazo al encabezado del asesor. Los ajustes en general no deberían ser cuestionables. Usaremos la biblioteca interna Trade para comerciar. Como ajustes de lotes podremos especificar o bien el lote (entonces el comercio se llevará a cabo con un lote fijo), o bien el riesgo en el cálculo de lote por balance. Si especificamos un beneficio al cierre mayor que 0, entonces el asesor cerrará las operaciones con el beneficio total. El Stop y el Take se calculan partiendo del valor ATR, es decir, "oscilan" en función de la volatilidad actual del instrumento. Los ajustes de ZigZag para los cálculos del asesor son generalmente estándar, no nos detendremos en ellos. También nos gustaría señalar que nuestro asesor es multidivisa, capaz de trabajar con una gran variedad de activos. Necesitaremos esto para reducir el riesgo global comerciando con cestas de activos relacionados en futuras versiones del asesor. La versión actual 0.90 solo funciona en un símbolo.

//+------------------------------------------------------------------+
//|                                          QuantAnalysisSample.mq5 |
//|                                                   Copyright 2023 |
//|                                                Evgeniy Koshtenko |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2023, Evgeniy Koshtenko"
#property link        "https://www.mql5.com"
#property version     "0.90"
#property strict

#include <Trade\Trade.mqh>
#include <Graphics\Graphic.mqh>
#include <Math\Stat\Normal.mqh>
#include <Math\Stat\Math.mqh>
CTrade trade;
//--- Inputs
input double Lots       = 0.1;      // lot
input double Risk       = 0.1;     // risk
input double Profit     = 0;     // profit
input int StopLoss      = 0;        // ATR stop loss
input int TakeProfit    = 0;        // ATR take profit
input string Symbol1    = "EURUSD";
input int    Magic      = 777;    // magic number
//--- Indicator inputs
input uint   InpDepth       =  120;   // ZigZag Depth
input uint   InpDeviation   =  50;    // ZigZag Deviation
input uint   InpBackstep    =  30;    // ZigZag Backstep
input uchar  InpPivotPoint  =  1;    // ZigZag pivot point
datetime t=0;
double last=0;
double countMovements;
double currentMovement;
// Global variable for storing the indicator descriptor
int zigzagHandle;

Ahora veremos el resto de las funciones del asesor. Las funciones de inicialización y desinicialización son generalmente simples y directas: cargaremos el número mágico del asesor, un identificador único que permitirá al asesor distinguir sus órdenes de las órdenes de otras personas. Cargaremos el manejador en una función adicional autodescrita, porque si cargamos el manejador multidivisa directamente a través de OnInit, el asesor generará un error. Por eso utilizaremos una solución tan sencilla y fácil.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(Magic);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert initialization function custom                            |
//+------------------------------------------------------------------+
int OnIniti(string symb)
  {// Loading the ZigZag indicator
   zigzagHandle = iCustom(symb, _Period, "ZigZag", InpDepth, InpDeviation, InpBackstep, InpPivotPoint);
   if (zigzagHandle == INVALID_HANDLE)
     {
      Print("Error loading the ZigZag indicator: ", GetLastError());
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Comment("");
  }

Vamos a analizar las demás funciones de un experto. Iremos hacia abajo por el código.  A continuación viene la función de cálculo del beneficio total de todas las posiciones y la función de cierre de todas las órdenes:

//+------------------------------------------------------------------+
//|  Position Profit                                                 |
//+------------------------------------------------------------------+
double AllProfit(int type=-1)
  {
   double p=0;

    for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetInteger(POSITION_MAGIC)==Magic)
           {
            if(PositionGetInteger(POSITION_TYPE)==type || type==-1)
               p+=PositionGetDouble(POSITION_PROFIT);
           }
        }
     }

   return(p);
  }
//+------------------------------------------------------------------+
//|   CloseAll                                                       |
//+------------------------------------------------------------------+
void CloseAll(int type=-1)
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetInteger(POSITION_MAGIC)==Magic)
           {
            if(PositionGetInteger(POSITION_TYPE)==type || type==-1)
               trade.PositionClose(PositionGetTicket(i));
           }
        }
     }
  }

Luego tenemos la función de cálculo de lotes y la función de cálculo del número de posiciones abiertas:

//+------------------------------------------------------------------+
//|     CountTrades                                                  |
//+------------------------------------------------------------------+
int CountTrades(string symb)
  {
   int count=0;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            count++;
           }
        }
     }
   return(count);
  }
//+------------------------------------------------------------------+
//|     Lot                                                          |
//+------------------------------------------------------------------+  
double Lot()
  {
   double lot=Lots;

   if(Risk>0)
      lot=AccountInfoDouble(ACCOUNT_BALANCE)*Risk/100000;

   return(NormalizeDouble(lot,2));
  }

Mencionaremos también las funciones de cálculo del precio de la última transacción para compras y ventas (la utilizaremos más adelante) y la función de determinación de la dirección de la posición.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double FindLastBuyPrice(string symb)
  {
   double pr=0;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)) && PositionGetInteger(POSITION_TYPE)==0)
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            pr=PositionGetDouble(POSITION_PRICE_OPEN);
            break;
           }
        }
     }
   return(pr);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double FindLastSellPrice(string symb)
  {
   double pr=0;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)) && PositionGetInteger(POSITION_TYPE)==1)
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            pr=PositionGetDouble(POSITION_PRICE_OPEN);
            break;
           }
        }
     }
   return(pr);
  }
//+------------------------------------------------------------------+
//|  PositionType                                                    |
//+------------------------------------------------------------------+
int PositionType(string symb)
  {
   int type=8;

   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetString(POSITION_SYMBOL)==symb)
           {
            type=(int)PositionGetInteger(POSITION_TYPE);
            break;
           }
        }
     }
   return(type);
  }

Y, por supuesto, nuestra función más importante será la función de cálculo del movimiento medio y actual. No se calculará en puntos, sino en la magnitud del movimiento de la unidad de precio, por comodidad. Todo es sencillo, llamaremos a nuestra "inicialización" casera, copiaremos los búferes y en el ciclo for calcularemos la magnitud de movimiento del precio desde el tope del ZigZag hasta su último extremo. En la salida de la función mostraremos el movimiento actual en unidades de movimiento del precio y el movimiento medio. 

//+------------------------------------------------------------------+
//|     CalculateAverageMovement                                     |
//+------------------------------------------------------------------+ 
void CalculateAverageMovement(string symb, double &averageMovement, double &currentMovement) {
    const int lookback = 500; // Number of bars for analysis
    double sumMovements = 0.0;
    int countMovements = 0;
    double lastExtremePrice = 0.0;
    double zigzagArray[500]; // Array to store ZigZag values
    OnIniti(symb);
    // Copy ZigZag values to array
    if (CopyBuffer(zigzagHandle, 0, 0, lookback, zigzagArray) <= 0) {
        Print("Error copying indicator data");
        averageMovement = -1;
        currentMovement = -1;
        return;
    }

    // Copy ZigZag values to array
    if (CopyBuffer(zigzagHandle, 0, 0, lookback, zigzagArray) <= 0) {
        Print("Error copying indicator data");
        averageMovement = -1;
        currentMovement = -1;
        return;
    }

    for (int i = 0; i < lookback; i++) {
        if (zigzagArray[i] != 0 && zigzagArray[i] != lastExtremePrice) {
            if (lastExtremePrice != 0) {
                // Determine the movement direction
                double movement = zigzagArray[i] - lastExtremePrice;
                sumMovements += movement;
                countMovements++;
            }
            lastExtremePrice = zigzagArray[i];
        }
    }

    // Calculate the current movement
    double lastMovement = iClose(symb, _Period, 0) - lastExtremePrice;
    currentMovement = lastMovement;

    // Calculate the average movement
    averageMovement = countMovements > 0 ? sumMovements / countMovements : 0.0;

    // Print the result
    Print("Average movement: ", averageMovement);
    Print("Current movement: ", currentMovement);

    // Release resources
    IndicatorRelease(zigzagHandle);
}

Y una más de nuestras funciones clave será una función comercial multidivisa basada en las señales de que el movimiento actual del precio supera su valor medio. El Take y el Stop están en valores ATR, el paso de cuadrícula (promediado) también está en ATR. Las operaciones se abrirán en barras nuevas: esto es importante para nosotros para que el asesor no "dibuje como Picasso" las pruebas en el simulador. Esta función se llama en OnTick y trabaja con uno o más símbolos. Todavía no he sido capaz de ejecutar con éxito el robot en varios símbolos, como ya hemos dicho, utilizaremos solo un símbolo con el que se iniciará el robot, y que también deberemos especificar en la configuración del asesor. 

//+------------------------------------------------------------------+
//| Expert Trade unction                                             |
//+------------------------------------------------------------------+
void Trade(string symb)
  {
   double averageMovement = 0;
   double currentMovement = 0;
   double pr=0,sl=0,tp=0,hi=0,lo=0;
// Call function for calculation
   CalculateAverageMovement(symb, averageMovement, currentMovement);

// Use results
   double Ask = SymbolInfoDouble(symb, SYMBOL_ASK);
   double Bid = SymbolInfoDouble(symb, SYMBOL_BID);
   int dg=(int)SymbolInfoInteger(symb,SYMBOL_DIGITS);
   double pp=SymbolInfoDouble(symb,SYMBOL_POINT);
  
   double atr = iATR(symb, PERIOD_CURRENT, 3);
     
// Here define your logic for buying and selling
   bool sell  = currentMovement > -averageMovement; // Buy condition
   bool buy = -currentMovement > averageMovement; // Sell condition
  
   if(AllProfit()>Profit && Profit>0)
      CloseAll();

   if(t!=iTime(symb,PERIOD_CURRENT,0))
     {
      if(buy && CountTrades(symb)<1)
        {
         if(StopLoss>0)
            sl=NormalizeDouble(Bid-(atr*StopLoss)*Point(),_Digits);
         if(TakeProfit>0)
            tp=NormalizeDouble(Bid+(atr*TakeProfit)*Point(),_Digits);
         pr=NormalizeDouble(Bid,dg);
         trade.Buy(Lot(),symb,pr,sl,tp,"");
         last=pr;
        }
      if(sell && CountTrades(symb)<1)
        {
         if(StopLoss>0)
            sl=NormalizeDouble(Ask+(atr*StopLoss)*Point(),_Digits);
         if(TakeProfit>0)
            tp=NormalizeDouble(Ask-(atr*TakeProfit)*Point(),_Digits);
         pr=NormalizeDouble(Ask,dg);
         trade.Sell(Lot(),symb,Ask,sl,tp,"");
         last=pr;
        }
      if(CountTrades(symb)>0)
        {
         if(PositionType(symb)==0 && (FindLastBuyPrice(symb)-Ask)/pp>=atr*30)
           {
            if(StopLoss>0)
               sl=NormalizeDouble(Bid-(atr*StopLoss)*Point(),_Digits);
            if(TakeProfit>0)
               tp=NormalizeDouble(Bid+(atr*TakeProfit)*Point(),_Digits);
            trade.Buy(Lot(),symb,Ask,sl,tp);
           }
         if(PositionType(symb)==1 && (Bid-FindLastSellPrice(symb))/pp>=atr*30)
           {
            if(StopLoss>0)
               sl=NormalizeDouble(Ask+(atr*StopLoss)*Point(),_Digits);
            if(TakeProfit>0)
               tp=NormalizeDouble(Ask-(atr*TakeProfit)*Point(),_Digits);
            trade.Sell(Lot(),symb,Bid,sl,tp);
           }
        }
      t=iTime(symb,0,0);
     }
  }


Probamos el modelo en MQL5

Ha llegado la parte más interesante: probar nuestro modelo en el mercado real. Me gustaría señalar que el cálculo con ayuda de un ciclo es bastante intensivo en la CPU, por lo que será más razonable ejecutar el asesor solo con los precios de apertura. Haremos una única prueba con el par EUR/USD en los precios de apertura del marco temporal H1, desde el 1 de enero de 2020 hasta la fecha de hoy, 6 de diciembre de 2023:


La prueba simple es rentable, pero la reducción es alta. Asumir riesgos adicionales en el comercio interesa a poca gente. Pero entonces debemos recordar que aún tenemos el cierre por beneficios, y podemos hacer una prueba en la cuenta de compensación

Para realizar una prueba con un cierre por beneficios, estableceremos un cierre por beneficios por encima de 0 . Hagamos la prueba. Quizá consigamos una prueba estable. Ejecutaremos el asesor en el mismo activo usando los precios de apertura. El tipo de cuenta que tenemos será Hedging. Y esto es lo que veremos:


El asesor ha resultado extremadamente arriesgado, y el promedio tiene la culpa. Intentaremos realizar la misma prueba en una cuenta de compensación.


Una vez más vemos una reducción importante, el beneficio no vale en absoluto su riesgo. Ahora intentaremos rehacer el código de nuevo: esta vez implementaremos el cierre por señal (cuando una señal alcista cambie a bajista, se cerrarán las posiciones anteriores). Implementaremos el cierre de beneficios con este código:

if (CloseSig)
   {
      if (buy)
         CloseAll(1);
      if (sell)
         CloseAll(0);
   }

Y ese tipo de configuración:

input bool CloseSig     = 1;        // close by signal

Tras realizar algunas pruebas, vemos que todo ha salido nuevamente mal:


Las pruebas en su conjunto no pueden calificarse de ideales. La reducción es enorme, tanto en las cuentas de compensación como en las de cobertura. Y el trabajo con el cierre en la señal no trae ningún resultado en absoluto y no resulta rentable. No puede evitar uno disgustarse.


Conclusión

Hoy hemos visto un ejemplo sencillo de creación de un algoritmo de análisis cuantitativo básico y simple en MQL5. Para ello, calculamos las ondas de movimiento del precio, las comparamos con los valores medios y, basándonos en estos datos, tomamos la decisión de comprar o vender. Obviamente, el algoritmo no ha funcionado, pero la base de la idea era bastante buena. En próximos artículos seguiremos investigando el análisis cuantitativo.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/13835

Archivos adjuntos |
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.
Redes neuronales: así de sencillo (Parte 67): Utilizamos la experiencia adquirida para afrontar nuevos retos Redes neuronales: así de sencillo (Parte 67): Utilizamos la experiencia adquirida para afrontar nuevos retos
En este artículo, seguiremos hablando de los métodos de recopilación de datos en una muestra de entrenamiento. Obviamente, en el proceso de entrenamiento será necesaria una interacción constante con el entorno, aunque con frecuencia se dan situaciones diferentes.
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
Conozca los paradigmas de programación y su aplicación en el código MQL5. En este artículo, analizaremos las características de la programación procedimental y ofreceremos ejemplos prácticos. Asimismo, aprenderemos a desarrollar un asesor basado en la acción del precio (Action Price) utilizando el indicador EMA y datos de velas. Además, el artículo introduce el paradigma de la programación funcional.
Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (II) Desarrollo de un sistema de repetición (Parte 39): Pavimentando el terreno (II)
Antes de comenzar la segunda fase del desarrollo, es necesario reforzar algunas ideas. Entonces, ¿sabes cómo forzar al MQL5 a hacer lo que es necesario? ¿Has intentado ir más allá de lo que informa la documentación? Si no, prepárate. Porque empezaré a hacer cosas mucho más allá de lo que la mayoría hace normalmente.