English Русский 中文 Deutsch 日本語 Português
preview
Media móvil en MQL5 desde cero: Sencilla y accesible

Media móvil en MQL5 desde cero: Sencilla y accesible

MetaTrader 5Ejemplos |
390 4
Artyom Trishkin
Artyom Trishkin

Contenido



Introducción

Una media móvil representa el precio medio de un par de divisas durante un determinado periodo temporal, expresado en número de barras.  Se trata de uno de los indicadores técnicos más antiguos y, tal vez, el más popular y usado, ya que un gran número de otros indicadores se basan en él.

Las medias móviles se utilizan con gran frecuencia y en todo lugar para resolver diversos problemas de aplicación. Matemáticamente, una media móvil simple es la media aritmética ordinaria de una serie determinada de valores numéricos. A la hora de crear diversos indicadores o scripts y asesores expertos para el terminal MetaTrader 5, las medias móviles se utilizan de una forma u otra en los cálculos. Para los tráders, las medias móviles son una herramienta sencilla y bastante eficaz que sirve para determinar la dirección de las tendencias, suavizar pequeñas fluctuaciones de los precios (ruido) o crear estrategias comerciales basadas en medias móviles y otros indicadores, ampliamente presentados tanto en el terminal de cliente como en el recurso mql5.com.

Curiosamente, las medias móviles se utilizan mucho no solo en los mercados financieros, sino también en otros campos como la meteorología y la economía. Algunos modelos de aprendizaje automático también usan el concepto de medias móviles como una de las características para la predicción de las series temporales.
Las medias móviles son una poderosa herramienta que, si se usa correctamente, puede mejorar sustancialmente los resultados de las operaciones y del análisis de datos.


Simple Moving Average (SMA) - media móvil simple

Una media móvil simple se calcula como la media aritmética de un número determinado de valores numéricos, por ejemplo, los precios de cierre de las barras. En cada punto de cálculo, los valores de la media móvil tienen el mismo peso respecto a los puntos vecinos de la serie numérica calculada. En otras palabras, y en relación con el indicador SMA, en cada barra de la línea del indicador (N) vemos la media de las barras vecinas de la izquierda (N + N-1 + N-2 + N-3 + N-x) / x, donde x supone el periodo de promediación (el número de barras calculadas).

La SMA:

  • Es un método de cálculo sencillo, fácil de calcular y entender.
  • Debido a su simplicidad, la SMA puede ir por detrás de los cambios de precios, especialmente cuando se utilizan periodos de cálculo largos.
  • A menudo se usa en combinación con otros indicadores para confirmar señales.


Figura 1. Media móvil simple. SMA de precios Close, con periodo de cálculo 10

Cálculo

El cálculo del indicador SMA de precios de cierre de la barra puede expresarse de la siguiente manera:

SMA = SUM(CLOSE(i), N) / N 

donde:

  • SUM — suma;
  • CLOSE(i) — precio de cierre del periodo actual;
  • N — número de periodos de cálculo.

En un indicador, el cálculo de una media móvil simple puede ser así:

double result=0;
for(int j=0; j<period; j++)
   result+=close[i-j];
result/=period;

Aquí:

  • result — variable donde se escribe el resultado del cálculo del SMA
  • period — cálculo de la SMA: el número de datos de la serie numérica calculada exactamente aquí son los precios de cierre de las barras
  • i — índice del ciclo principal del indicador
  • j — índice de ciclo para calcular el valor medio de los precios de cierre de la barra a la izquierda de la barra con el índice i

Es decir, al calcular la media para el periodo de suavizado 5, pasaremos en un ciclo de la barra con índice iy sumaremos todos los precios CLose de las barras con el índice i-j (i-0 + i-1 + i-2 + i-3 + i-4).

Tras sumar todos los precios de cierre de todas las barras en la magnitud period, el resultado se dividirá por el valor de period. Como resultado, obtendremos la media aritmética habitual de los precios Close de las barras del periodo.

Veamos el código del indicador:

//+------------------------------------------------------------------+
//|                                           SMAonPriceCloseRAW.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

//--- plot MA
#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod   =  10;   // SMA Period

//--- indicator buffers
double         MABuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- in the loop from the value of InpPeriod to the current history bar
   for(int i=InpPeriod; i<rates_total; i++)
     {
      //--- calculate average close prices for InpPeriod bars
      double result=0;
      for(int j=0; j<InpPeriod; j++)
         result+=close[i-j];
      result/=InpPeriod;
      
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
//--- return value of prev_calculated for the next call
   return(rates_total);
  }

Es el indicador más sencillo que permite ver en el gráfico los valores medios de los precios Close del periodo indicado en los ajustes para cada barra de la historia (InpPeriod). Los valores se muestran como una línea en el gráfico de precios.

Para calcular el valor del precio medio para el número de barras indicado, tendremos que sumar el número requerido de precios Close en un ciclo desde la barra actual y dividir la suma obtenida por el número de barras para las que se está buscando la media aritmética.

Así, yendo en un ciclo desde el principio de la historia, para cada barra del ciclo i calcularemos la media de los precios de cierre de las barras de la izquierda en el ciclo j. Por eso, el ciclo principal no empieza desde cero, sino desde el valor del número de barras para el que calcularemos la media, de modo que el número de barras necesario al principio del cálculo de la SMA estará a la izquierda.

Arriba hemos visto el código más simple y subóptimo que muestra el cálculo de una media móvil simple. Esta construcción del ciclo del indicador hace que este recalcule de nuevo la historia completa, al tiempo que realiza un ciclo más de cálculo de precios medios en cada barra. Con el cálculo presentado anteriormente, tarde o temprano (lo más probable es que temprano) obtendremos este mensaje en el registro:

indicator is too slow, 66141 ms. rewrite the indicator, please

Aquí se dice que el indicador es demasiado lento y necesita ser reescrito. Naturalmente, debemos optimizarlo. Y lo primero es haremos es un cálculo eficiente del indicador.

El archivo del indicador SMAonPriceCloseRAW.mq5 se encuentra en los archivos adjuntos al artículo.

Optimización de los cálculos

El cálculo eficiente del indicador supone esta secuencia de cálculos:

  • En el primer inicio del indicador (o en cualquier cambio en la historia del símbolo) se realizará un cálculo completo del indicador para todo la historia disponible. 
  • Después de completar el cálculo, todos los datos calculados se dibujarán en el gráfico, y ahora será necesario calcular y cambiar los datos en cada nuevo tick solo para la barra actual.

El cálculo del indicador comenzará cuando llegue el evento Calculate. En este caso, además, se llamará al manejador de este evento OnCalculate().

Suele ocurrir cuando se recibe un nuevo tick en el símbolo para el que se calcula el indicador.

Existen dos formas de definir el manejador OnCalculate(). Ambas variantes de la función no pueden utilizarse dentro del mismo indicador.

La primera forma de llamada es para aquellos indicadores que pueden calcularse en un único búfer de datos.

int OnCalculate (const int rates_total,      // price[] array size
                 const int prev_calculated,  // handled bars at the previous call
                 const int begin,            // where significant data starts
                 const double& price[]       // array for calculation
   );

Como array price[] se puede transmitir una de las series temporales de precios o el búfer calculado de algún indicador.

La selección de la serie temporal o el indicador necesarios como un array price[] la realizará el usuario en la pestaña "Parámetros" al iniciar el indicador.

La segunda forma de llamada está destinada a todos los demás indicadores que utilizan más de una serie temporal para su cálculo.

int OnCalculate (const int rates_total,      // size of input time series
                 const int prev_calculated,  // handled bars at the previous call
                 const datetime& time[],     // Time
                 const double& open[],       // Open
                 const double& high[],       // High
                 const double& low[],        // Low
                 const double& close[],      // Close
                 const long& tick_volume[],  // Tick Volume
                 const long& volume[],       // Real Volume
                 const int& spread[]         // Spread
   );
  • Los parámetros open[], high[], low[] y close[] contienen los arrays con los precios de apertura, máximo, mínimo y cierre del marco temporal actual.
  • El parámetro time[] contiene un array con los valores de la hora de apertura.
  • El parámetro spread[] es un array que contiene la historia del spread (si se proporciona el spread para este instrumento comercial).
  • Los parámetros volume[] y tick_volume[] contienen la historia comercial y el volumen de ticks, respectivamente.
  • El parámetro rates_total contiene el número de barras de que dispone el indicador para el cálculo y se corresponde con el número de barras disponibles en el gráfico.
  • El parámetro prev_calculated en la llamada a la función contiene el valor que OnCalculate() ha devuelto en la llamada anterior. Esto permite implementar algoritmos económicos para calcular el indicador personalizado con el fin de evitar cálculos repetidos para aquellas barras que no han cambiado desde el anterior inicio de esta función. Para ello, suele bastar con retornar el valor del parámetro rates_total, que contiene el número de barras en la llamada actual a la función. Si los datos de precios han cambiado desde la última llamada a la función OnCalculate() (se ha descargado una historia más profunda o se han rellenado huecos de la historia), el valor del parámetro de entrada prev_calculated será puesto a cero por el propio terminal.

Como podemos ver, necesitaremos el valor de la variable prev_calculated para organizar el cálculo eficiente. En la primera ejecución o cambio de datos históricos, esta variable contendrá un valor cero, controlando el cual, podremos organizar el cálculo completo del indicador en la primera ejecución y el cálculo solo de la barra actual después de que se calcule todo el indicador y se registren en su búfer los valores de la historia completa.

Como el ciclo del indicador se ejecuta sobre datos históricos, a partir de un número determinado y hasta el valor rates_total-1, entonces, sabiendo cuántas barras se han calculado en la última ejecución del indicador (el valor se almacena en la variable prev_calculated), podremos controlar el valor a partir del cual deberá iniciarse el ciclo de cálculo.

Supongamos que se trata de la variable start que debe contener el número de barra a partir del cual deberá iniciarse el ciclo del indicador. En la primera ejecución, la variable prev_calculated almacenará cero. Así, podremos iniciar el ciclo desde cero y hasta el número de barras del instrumento en el que se esté ejecutando el indicador.

Pero hay un matiz a considerar: para calcular el valor de la media móvil, deberemos retroceder en el ciclo hasta el principio de la historia desde la barra actual del ciclo y calcular el valor del precio medio. Pero si el ciclo principal empieza desde cero, no habrá nada a la izquierda de la barra cero (el principio de los datos históricos), y no podremos obtener los valores de las barras que faltan de la historia. Por lo tanto, el ciclo principal deberá iniciarse con una separación desde el principio de la historia en el número de barras necesarias para calcular la media. Y este será el periodo de cálculo del indicador (digamos Period).

El establecimiento del número de barras a contar en la variable de start debería tener este aspecto:

//--- If this is the first run or a change in historical data
   if(prev_calculated==0)
     {
      //--- initialize the indicator buffer with an empty value and
      //--- set the start of the calculation to the Period value
      ArrayInitialize(MABuffer,EMPTY_VALUE);
      start=Period;
     }
//--- If this is not the first run, the calculation will start at the current bar
   else
      start=prev_calculated-1;

Cuando el indicador se pone en marcha por primera vez, el valor de inicialización se escribirá primero en su búfer. Aquí será EMPTY_VALUE, un valor vacío del búfer del indicador que no participará en el dibujo. A continuación, el índice de la barra a partir del cual comienza el ciclo del indicador principal, se escribirá en la variable de inicio. Si no es la primera vez que se ejecuta el indicador, la variable prev_calculated contendrá el número de barras ya calculadas en la llamada anterior del manejador OnCalculate(). Por lo tanto, en este caso, estableceremos el índice de la barra actual como la barra a partir de la cual comenzará el ciclo, que es prev_calculated-1 en la gran mayoría de los casos.

Dado que el manejador OnCalculate() retorna un cierto valor (normalmente rates_total), podemos ajustar el número de barras a calcular en la siguiente llamada del manejador. Si devolvemos el número de barras calculadas almacenadas en rates_tota, este valor se escribirá en la variable prev_calculated en la siguiente llamada a OnCalculate. Como consecuencia, si especificamos el número de barra para iniciar el ciclo como prev_calculated-1, entonces en la barra actual será el índice de la barra actual, y cuando se abra una nueva barra, será el índice de la barra anterior. Así especificaremos el número de barras para iniciar el ciclo:

  • en el primer inicio, a partir del valor del periodo de cálculo del indicador
  • en la apertura de una nueva barra a partir de la barra anterior (se calculan la barra anterior y la actual)
  • en la barra actual, en cada nuevo tick calculamos una barra actual

Veamos el código completo del indicador:

//+------------------------------------------------------------------+
//|                                           SMAonPriceCloseECO.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

//--- plot MA
#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod   =  10;   // SMA Period

//--- indicator buffers
double         MABuffer[];

//--- global variables
int   ExtPeriod;  // SMA calculation period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Adjust the entered value of the SMA calculation period
   ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod);
   
//--- If there is not enough historical data for calculation, return 0
   if(rates_total<ExtPeriod)
      return 0;
      
   int start=0;   // calculation start bar
   
//--- If this is the first run or a change in historical data
   if(prev_calculated==0)
     {
      //--- initialize the indicator buffer with an empty value and
      //--- set the start of the calculation to ExtPeriod
      ArrayInitialize(MABuffer,EMPTY_VALUE);
      start=ExtPeriod;
     }
//--- If this is not the first run, the calculation will start at the current bar
   else
      start=prev_calculated-1;
      
//--- in the loop from the 'start' value to the current history bar
   for(int i=start; i<rates_total; i++)
     {
      //--- calculate average close prices for ExtPeriod bars
      double result=0;
      for(int j=0; j<ExtPeriod; j++)
         result+=close[i-j];
      result/=ExtPeriod;
      
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
     
//--- return the number of bars calculated for the next OnCalculate call
   return(rates_total);
  }

El archivo del indicador SMAonPriceCloseECO.mq5 se encuentra en los archivos adjuntos al artículo.

Ahora, al ejecutar el indicador en el gráfico de símbolos, ya no aparecerá el mensaje de que es demasiado lento. Pero existe otro problema de optimización: cuando se calcula el indicador en la barra actual, se sigue ejecutando el ciclo de cálculo del precio medio de las barras del periodo. Como una media móvil simple es la suma ordinaria de un cierto número de barras dividida por el mismo número de barras, podremos deshacernos de este ciclo en la barra actual.

Veamos cómo se calcula el indicador SMA en la ejecución actual sobre la historia. Por ejemplo, vamos a mostrar el inicio del cálculo de una media móvil simple con un periodo de 5 barras.

Al inicio, el cálculo del indicador no comienza desde la barra cero, sino desde la barra igual a - 1 (el número de barras del periodo de cálculo de la SMA):

El ciclo principal comenzará en la barra con el índice 4. Para calcular el valor de la SMA en esta barra, se realizará un ciclo en las barras con los índices 4, 3, 2, 1 y 0, se sumarán los valores de los precios en estas barras, y luego la suma obtenida se dividirá por el número de periodos de cálculo de la SMA, que aquí es 5. Para simplificar los cálculos, la figura muestra valores enteros simples de 2, 4, 6, 8 y 10 en las barras correspondientes como valores del precio de la barra. Como resultado, en la barra con el índice 4, el valor de la SMA será 6 = (2+4+6+8+10)/5.

Tras desplazar el índice del ciclo principal de cálculo de la SMA, se volverá a calcular en el ciclo el valor de la SMA para la siguiente barra con el índice 5:

En la siguiente iteración del ciclo principal, de nuevo en un ciclo interno, se calculará la SMA de la barra con el índice 6:

Y luego todo seguirá el mismo esquema: en cada siguiente cambio del índice del ciclo principal, se calcularán los valores de SMA para cada barra subsiguiente en los ciclos internos.
Y así sucesivamente hasta que el ciclo principal alcance la barra actual:


Entonces, al calcular la SMA en la barra actual, con la llegada de cada siguiente tick, se ejecutará un ciclo interno para calcular la SMA en la barra actual.

¿Qué es la SMA? Es la media aritmética de los precios durante un periodo concreto. Ya sabemos que para calcular el valor de la SMA en una barra, debemos dividir la suma de los precios del Period anterior por el periodo de cálculo Period. Podemos calcular el primer valor de la SMA sumando los precios del Period anterior en el ciclo y dividiendo esta suma por Period. Y ahora, al desplazar la ventana de cálculo de la SMA hacia la derecha, ya no será necesario sumar los precios anteriores del ciclo y dividirlos por su número Period, porque ya tenemos el valor de la SMA calculado en la barra anterior.

Bastará con restar a este valor el primer precio de los que intervienen en el cálculo del valor medio y añadir al resultado el precio actual, sin olvidar dividirlos por Period. Y eso es todo. Es decir, la primera barra SMA se calcula sumando los precios en el ciclo Periodo y dividiendo el resultado de la suma por el valor de Periodo, y luego solo se darán operaciones aritméticas de suma, resta y división:

Cálculo del primer valor de la SMA:


A continuación, cuando el índice del ciclo principal se desplaza, simplemente haremos el cálculo:


Y luego siguiendo el mismo escenario, pero con los nuevos datos:


Y en cada desplazamiento del índice del ciclo principal


Cada vez que obtengamos un nuevo valor de SMA a partir del calculado anteriormente


Vamos a analizar un indicador de este tipo:

//+------------------------------------------------------------------+
//|                                              SMAonPriceClose.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

//--- plot MA
#property indicator_label1  "SMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod   =  10;   // SMA Period

//--- indicator buffers
double         MABuffer[];

//--- global variables
int   ExtPeriod;  // SMA calculation period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Adjust the entered value of the SMA calculation period
   ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod);
   
//--- If there is not enough historical data for calculation, return 0
   if(rates_total<ExtPeriod)
      return 0;
      
   int start=0;   // calculation start bar
   
//--- If this is the first run or a change in historical data
   if(prev_calculated==0)
     {
      //--- initialize the indicator buffer with an empty value,
      //--- set the start of the calculation to ExtPeriod and
      //--- calculate average close prices for ExtPeriod bars for the first SMA value on the start-1 bar
      ArrayInitialize(MABuffer,EMPTY_VALUE);
      start=ExtPeriod;
      double value=0;
      for(int i=0; i<start; i++)
         value+=close[i];
      MABuffer[start-1]=value/ExtPeriod;
     }
//--- If this is not the first run, the calculation will start at the current bar
   else
      start=prev_calculated-1;
      
//--- in the loop from the 'start' value to the current history bar
   for(int i=start; i<rates_total; i++)
     {
      //--- define prices for calculating SMA
      double ma_prev=MABuffer[i-1];             // Previous calculated SMA value
      double price_first=close[i-ExtPeriod];    // Close price ExtPeriod bars ago
      //--- Set the current SMA value to the buffer calculated as 
      //--- (past SMA value + (current close price - close price ExtPeriod bars ago) / SMA ExtPeriod calculation period)
      MABuffer[i]=ma_prev+(close[i]-price_first)/ExtPeriod;
     }
     
//--- return the number of bars calculated for the next OnCalculate call
   return(rates_total);
  }

El indicador puede ejecutarse en el gráfico y compararse con el SMA estándar, estableciendo previamente los mismos periodos de cálculo para ambos. Este indicador usa el ciclo de duración de cálculo de MA Period en el inicio y, a continuación, en el primer inicio del ciclo principal, se calculan los valores SMA usando base el primer valor SMA calculado para toda la historia del instrumento. En la barra actual, se calcula el valor de la SMA para cada tick actual.

Encontrará el archivo del indicador SMAonPriceClose.mq5 en los archivos adjuntos al artículo.


Exponential Moving Average (EMA) - media móvil exponencial

La media móvil suavizada exponencialmente se determina añadiendo una determinada fracción del precio de cierre actual al valor anterior de la media móvil calculada. Al utilizar medias móviles exponenciales, se da más importancia a los precios de cierre recientes. Al calcular la EMA, las ponderaciones de los precios de las barras que intervienen en el cálculo disminuyen exponencialmente a un ritmo que viene dado por la constante de suavizado k (1 > k > 0). Lo más frecuente es que k se calcule según el periodo de cálculo de la media móvil: k = 2,0 / (N + 1), donde N es el número de periodos de cálculo.

La EMA:

  • Reacciona con mayor rapidez a los cambios de precios, por lo que resulta preferible para las operaciones a corto plazo.
  • Es más eficaz a la hora de identificar tendencias y señales de compra y venta, ya que tiene en cuenta datos recientes.
  • A menudo se utiliza para su intersección con otras EMA (por ejemplo, 50 y 200) para determinar puntos de compra o venta.

Figura 2. Media móvil exponencial. EMA sobre precios Close, con periodo de cálculo 10

Cálculo

La media móvil exponencial p-porcentual tendrá el aspecto siguiente:

EMA = (CLOSE(i) * P) + (EMA(i - 1) * (1 - P))

donde:

  • CLOSE(i) — precio de cierre del periodo actual;
  • EMA(i - 1) — valor de la media móvil del periodo anterior;
  • P — fracción de uso del valor del precio (constante de suavizado).

Vamos a escribir un indicador sencillo que muestre el cálculo de la media móvil exponencial:

//+------------------------------------------------------------------+
//|                                           EMAonPriceCloseRAW.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot MA
#property indicator_label1  "EMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod   =  10;   // EMA Period

//--- indicator buffers
double         MABuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- in the loop from zero (the beginning of history) to the current bar
   for(int i=0; i<rates_total; i++)
     {
      double result=0;
      double smf=2.0/(InpPeriod+1.0);                       // smoothing factor
      double prev_value=(i>=1 ? MABuffer[i-1] : close[i]);  // previous calculated EMA value
      
      //--- calculate EMA based on the Close price of the bar, the previous EMA value and the smoothing factor
      result=close[i]*smf+prev_value*(1-smf);
      
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
//--- return value of prev_calculated for the next call
   return(rates_total);
  }

Al igual que para la media móvil simple, aquí mostraremos el cálculo de la EMA sin comprobar los valores de entrada como periodo de cálculo y organizar el cálculo eficiente del indicador. En la primera ejecución, el indicador calculará toda la historia disponible y, a continuación, en cada tick recalculará la historia completa. Y así constantemente, lo que sin duda generará un mensaje informando de que el indicador es demasiado lento.

Encontrará el indicador EMAonPriceCloseRAW.mq5 en los archivos adjuntos.

Optimización de los cálculos

Hagamos un cálculo eficiente del indicador. En la primera ejecución calculará toda la historia, y luego en cada nuevo tick recalculará solo la barra actual:

//+------------------------------------------------------------------+
//|                                              EMAonPriceClose.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot MA
#property indicator_label1  "EMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod   =  10;   // EMA Period

//--- indicator buffers
double         MABuffer[];

//--- global variables
int   ExtPeriod;  // EMA calculation period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Adjust the entered value of the EMA calculation period
   ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod);
   
//--- If there is not enough historical data for calculation, return 0
   if(rates_total<ExtPeriod)
      return 0;
      
   int start=0;   // calculation start bar
   
//--- If this is the first run or a change in historical data
   if(prev_calculated==0)
     {
      //--- initialize the indicator buffer with an empty value and set the start of the calculation to 0
      ArrayInitialize(MABuffer,EMPTY_VALUE);
      start=0;
     }
//--- If this is not the first run, the calculation will start at the current bar
   else
      start=prev_calculated-1;
      
//--- in the loop from the 'start' value to the current history bar
   for(int i=start; i<rates_total; i++)
     {
      double result=0;
      double smf=2.0/(ExtPeriod+1.0);                       // smoothing factor
      double prev_value=(i>=1 ? MABuffer[i-1] : close[i]);  // previous calculated EMA value
      
      //--- calculate the EMA value for the current loop bar
      result=close[i]*smf+prev_value*(1-smf);
      
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
     
//--- return the number of bars calculated for the next OnCalculate call
   return(rates_total);
  }

Si el valor del periodo calculado es inferior a 1, fijaremos el valor por defecto de las medias móviles en 10.
El cálculo de la media móvil exponencial usará el valor de la EMA calculado previamente en la barra anterior.

En la primera ejecución, este cálculo aún no se ha realizado, por lo que utilizamos el valor del precio Close como dato principal.

Este indicador implementa el cálculo eficiente de los datos: en el primer inicio se calcula toda la historia disponible, y en las siguientes llamadas a OnCalculate() solo se calculará la barra actual.

Encontrará el archivo del indicador EMAonPriceClose.mq5 en los archivos adjuntos al artículo.


Smoothed Moving Average (SMMA) - media móvil suavizada

La media móvil suavizada, a diferencia de la media móvil simple, resulta menos sensible a las fluctuaciones de los precios y ofrece mayor importancia a la dirección general del movimiento de los precios.

La SMMA:

  • Resulta menos propensa a las fluctuaciones que la SMA y la EMA, por lo que es muy útil para analizar tendencias más estables.
  • Es útil para el análisis a largo plazo, ya que suaviza las fluctuaciones bruscas de los precios.
  • Puede usarse para generar señales comerciales más fiables.

Fig. 3. Media móvil suavizada. SMMA sobre precios Close, con periodo de cálculo 10

Cálculo

El primer valor de la media móvil suavizada se calcula como media móvil simple (SMA):

SUM1  = SUM(CLOSE(i), N)
SMMA1 = SUM1 / N 

El segundo valor se calcula usando la siguiente fórmula:

SMMA(i) = (SMMA1*(N-1) + CLOSE(i)) / N

Todos los valores sucesivos de la media móvil se calculan mediante la siguiente fórmula:

PREVSUM = SMMA(i - 1) * N
SMMA(i) =(PREVSUM - SMMA(i - 1) + CLOSE(i)) / N 

donde:

  • SUM — suma;
  • SUM1 — suma de los precios de cierre de N periodos, contados a partir de la barra anterior;
  • PREVSUM — suma suavizada de la barra anterior;
  • SMMA(i-1) — media móvil suavizada de la barra anterior;
  • SMMA(i) — media móvil suavizada de la barra actual (excepto la primera);
  • CLOSE(i) — precio de cierre actual;
  • N — periodo de suavizado.

Como resultado de las transformaciones aritméticas, la fórmula puede simplificarse:

SMMA(i) = (SMMA(i - 1) * (N - 1) + CLOSE(i)) / N 

Vamos a escribir un indicador sencillo que calcule una media móvil suavizada:

//+------------------------------------------------------------------+
//|                                          SMMAonPriceCloseRAW.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

//--- plot MA
#property indicator_label1  "SMMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod=10;  // SMMA Period

//--- indicator buffers
double         MABuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   double result=0.0;
   for(int i=InpPeriod-1; i<rates_total; i++)
     {
      //--- first calculation
      if(i==InpPeriod-1)
        {
         //--- calculate a simple moving average for the InpPeriod first bars
         for(int j=0; j<InpPeriod; j++)
           {
            double price=close[i-j];
            result+=price;
           }
         result/=InpPeriod;
         //--- write the first displayed SMMA value calculated as SMA
         MABuffer[InpPeriod-1]=result;
        }
      //--- all subsequent calculations
      else
         result=(MABuffer[i-1]*(InpPeriod-1)+close[i])/InpPeriod;
      
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
//--- return value of prev_calculated for the next call
   return(rates_total);
  }

El indicador solo mostrará el cálculo de la SMMA en la primera ejecución y en cada nuevo tick, calculando completamente todas las barras de toda la historia disponible. Esto no resulta ni óptimo ni correcto, ya que el indicador será demasiado lento. Así que deberemos optimizar los cálculos.

Optimización de los cálculos

Deberemos implementar el control del valor de entrada del periodo de cálculo SMMA, el cálculo completo de toda la historia en la primera ejecución, y después en cada nuevo tick para realizar el cálculo solo en la barra actual:

//+------------------------------------------------------------------+
//|                                             SMMAonPriceClose.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot MA
#property indicator_label1  "SMMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod=10;

//--- indicator buffers
double         MABuffer[];

//--- global variables
int   ExtPeriod;  // SMMA calculation period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization 
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Adjust the entered value of the SMMA calculation period
   ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod);
   
//--- If there is not enough historical data for calculation, return 0
   if(rates_total<ExtPeriod)
      return 0;
      
   double result=0;
   int    start =0;
   
//--- If this is the first run or a change in historical data
   if(prev_calculated==0)
     {
      //--- initialize the indicator buffer with an empty value and set the start of the calculation to ExtPeriod
      ArrayInitialize(MABuffer,EMPTY_VALUE);
      start=ExtPeriod;

      //--- calculate a simple moving average for the ExtPeriod first bars
      for(int i=0; i<start; i++)
         result+=close[i];
      result/=ExtPeriod;
      //--- write the first displayed SMMA value calculated as SMA
      MABuffer[start-1]=result;
     }
   else
      start=prev_calculated-1;
      
//--- in the loop from the 'start' value to the current history bar
//--- calculate the SMMA value for the loop current bar
   for(int i=start; i<rates_total; i++)
      MABuffer[i]=(MABuffer[i-1]*(ExtPeriod-1)+close[i])/ExtPeriod;
      
//--- return the number of bars calculated for the next OnCalculate call
   return(rates_total);
  }

Ahora el indicador calculará eficientemente la historia y la barra actual. Encontrará el archivo del indicador SMMAonPriceClose.mq5 en los archivos adjuntos al artículo.


Linear Weighted Moving Average (LWMA) - media móvil lineal-ponderada

En la media móvil ponderada, los datos más recientes tienen más peso y los anteriores, menos. Esto permite que la LWMA resulte más sensible a los movimientos recientes de los precios. En esta media móvil, el peso de los precios de las barras disminuye linealmente: para la barra cero (actual) será el máximo, mientras que para la barra con el número N-1 será igual a 0.

La LWMA:

  • Reacciona más activamente a los cambios de precios en comparación con las SMA tradicionales,
  • Permite analizar los datos para que los cambios recientes tengan el mayor influencia en los cálculos,
  • Puede utilizarse para crear diversas estrategias comerciales, especialmente en entornos de alta volatilidad.

Fig.4. Media móvil lineal-ponderada. LWMA sobre precios Close, con un periodo de cálculo de 10

Cálculo

La media móvil ponderada se calcula multiplicando cada uno de los precios de cierre de la serie considerada por un factor de ponderación específico:

LWMA = SUM(CLOSE(i) * i, N) / SUM(i, N) 

donde:

  • SUM — suma;
  • CLOSE(i) — precio de cierre actual;
  • SUM(i, N) — suma de los coeficientes de ponderación;
  • N — periodo de suavizado.

Vamos a escribir un indicador sencillo que muestre el cálculo de la media móvil lineal-ponderada:

//+------------------------------------------------------------------+
//|                                          LWMAonPriceCloseRAW.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot MA
#property indicator_label1  "LWMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- input parameters
input int      InpPeriod=10;  // LWMA Period
//--- indicator buffers
double         MABuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- in the loop from InpPeriod-1 to the current bar
   double result=0.0;
   for(int i=InpPeriod-1; i<rates_total; i++)
     {
      //--- calculate weights (wsum) and the sum of weight coefficients (sum) of InpPeriod bars
      double sum =0.0;
      int    wsum=0;
      for(int j=InpPeriod; j>0; j--)
        {
         wsum+=j;
         sum +=close[i-j+1]*(InpPeriod-j+1);
        }
      //--- get the LWMA value for the current bar of the loop
      result=sum/wsum;
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
//--- return value of prev_calculated for the next call
   return(rates_total);
  }

El indicador muestra en el gráfico el resultado del cálculo de la LWMA para cada barra de la historia. En cada nuevo tick se produce un recálculo completo de todos los datos históricos. Se trata de un cálculo muy lento y subóptimo del indicador, cuyo cálculo optimizaremos aún más realizando un cálculo eficiente del indicador y añadiendo una comprobación de los parámetros de entrada.

El archivo del indicador LWMAonPriceCloseRAW.mq5 se encuentra en los archivos adjuntos al artículo.

Optimización de los cálculos

Optimizaremos el cálculo del indicador para evitar el recálculo de toda la historia en cada tick, y añadiremos una comprobación del valor introducido del periodo de cálculo de la LWMA:

//+------------------------------------------------------------------+
//|                                             LWMAonPriceClose.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"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot MA
#property indicator_label1  "LWMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- input parameters
input int      InpPeriod   =  10;   // LWMA Period

//--- indicator buffers
double         MABuffer[];

//--- global variables
int   ExtPeriod;  // LWMA calculation period
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MABuffer,INDICATOR_DATA);
   
//--- successful initialization
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Adjust the entered value of the LWMA calculation period
   ExtPeriod=(InpPeriod<1 ? 10 : InpPeriod);
   
//--- If there is not enough historical data for calculation, return 0
   if(rates_total<ExtPeriod)
      return 0;
      
   double result=0;
   int    start =0;
   
//--- If this is the first run or a change in historical data
   if(prev_calculated==0)
     {
      //--- initialize the indicator buffer with an empty value and set the start of the calculation to ExtPeriod-1
      ArrayInitialize(MABuffer,EMPTY_VALUE);
      start=ExtPeriod-1;
     }
//--- If this is not the first run, the calculation will start at the current bar
   else
      start=prev_calculated-1;
      
//--- in the loop from the 'start' value to the current history bar
//--- calculate the LWMA value for the loop current bar
   for(int i=start; i<rates_total; i++)
     {
      //--- calculate weights (wsum) and the sum of weight coefficients (sum) of ExtPeriod bars
      double sum =0.0;
      int    wsum=0;
      for(int j=ExtPeriod; j>0; j--)
        {
         wsum+=j;
         sum +=close[i-j+1]*(ExtPeriod-j+1);
        }
      //--- get the LWMA value for the current bar of the loop
      result=sum/wsum;
      //--- Write the calculation result to the buffer
      MABuffer[i]=result;
     }
      
//--- return the number of bars calculated for the next OnCalculate call
   return(rates_total);
  }

Los principios del cálculo eficiente de los indicadores se describen detalladamente en la sección optimización de la media móvil simple.

Encontrará el archivo del indicador en los archivos adjuntos al artículo.


Ventajas e inconvenientes de los cuatro tipos de medias

Con el tiempo, han surgido diferentes tipos de medias móviles, como la simple, la exponencial, la ponderada y muchas otras medias móviles que utilizan sus propios cálculos exclusivos, así como las basadas en las SMA, cada una de las cuales tiene fines diferentes y puede ofrecer perspectivas nuevas e interesantes sobre los datos analizados.

Aquí tiene un resumen de las ventajas e inconvenientes de las cuatro medias móviles básicas:

  1. La SMA (Media Móvil Simple) se usa a menudo para determinar la dirección general de una tendencia y para identificar los niveles de apoyo y resistencia. Es popular entre los tráders e inversores a largo plazo porque suaviza las fluctuaciones de los precios, permitiendo una mejor visión de la tendencia general.

    Ventajas:
    • Facilidad de cálculo e interpretación.
    • Suaviza las fluctuaciones de precios proporcionando una visión limpia de las tendencias.
    • Útil para identificar tendencias a largo plazo.

    Desventajas:
    • Reacciona a los cambios con lentitud, lo que puede retrasar las señales.
    • Resulta igualmente sensible a las fluctuaciones de precios en toda la gama del periodo de cálculo (las ponderaciones de todos los precios son las mismas).

  2. La EMA (media móvil exponencial) da más peso a los precios recientes, por lo que resulta más sensible a los nuevos cambios de precios. Se utiliza para señales más rápidas de apertura y cierre de posiciones. Es la preferida para los tráders a corto plazo y los revendedores que buscan entradas y salidas más rápidas en el mercado.

    Ventajas:
    • Su objetivo consiste en responder más rápidamente a las variaciones de precios gracias a la mayor importancia asignada a los datos más recientes.
    • Suele usarse en combinación con otros indicadores para obtener señales más precisas.
    • Ofrece información más actualizada sobre la tendencia actual.

    Desventajas:
    • Puede dar señales falsas en caso de fluctuaciones bruscas del mercado.
    • Es más difícil de calcular que el SMA.

  3. La SMMA (Media Móvil Suavizada) se considera una versión más suave de la EMA y la SMA, que evita las fluctuaciones bruscas manteniendo la información relevante. Resulta adecuada para los tráders que desean evitar las señales falsas, además de para analizar y confirmar las tendencias.

    Ventajas:
    • Ofrece un mayor suavizado de los datos en comparación con la SMA y la EMA.
    • Es menos propensa a las fluctuaciones, lo que puede ser útil en mercados volátiles.
    • Almacena información sobre valores anteriores, lo cual permite un mejor análisis de tendencias.

    Desventajas:
    • Puede resultar lenta para reaccionar a los cambios del mercado, es decir, se asemeja a la SMA.
    • Podría mostrar retrasos en la detección de tendencias.

  4. La LWMA (Media Móvil Lineal-Ponderada) confiere un impacto diferente a cada valor de precio dependiendo de su antigüedad, añadiendo peso a los datos más recientes. Se usa para análisis más detallados (por ejemplo, en estrategias que utilizan múltiples marcos temporales) cuando los tráders desean reaccionar más rápidamente a los cambios.

    Ventajas:
    • Asigna más peso a los precios más recientes para reflejar con mayor precisión los cambios recientes.
    • Es más flexible que la SMA y la EMA, lo que permite responder mejor a los nuevos datos,
    • Puede resultar útil para estrategias a corto plazo.

    Desventajas:
    • Es más difícil de calcular, especialmente en comparación con la SMA y la EMA.
    • También puede ofrece señales falsas en mercados inestables.



Conclusión

Hoy hemos analizado los principios de cálculo de los principales tipos de medias móviles disponibles en los ajustes del indicador estándar Media móvil en el terminal de cliente. Los cálculos presentados en el artículo pueden utilizarse en indicadores con cálculos optimizados (lo cual también se muestra en el artículo); asimismo, podemos usar los códigos presentados como un cálculo independiente de los valores medios de un conjunto de datos consecutivos en nuestros propios programas.

La figura anterior muestra la diferencia entre medias móviles idénticas en cuanto al periodo de cálculo (10) pero diferentes en cuanto al tipo:

Roja - SMA, verde - EMA, dorada - SMMA, azul - LWMA.

Podemos observar que la media móvil suavizada se ve menos afectada por las pequeñas fluctuaciones de los precios y refleja con mayor claridad la tendencia general del movimiento de los precios.
Las medias móviles exponenciales y lineales ponderadas reaccionan con mayor fuerza a las fluctuaciones del mercado, ya que sus cálculos dan la mayor importancia a los datos actuales.

En resumen:

La media móvil (SMA) es un método estadístico usado para analizar series temporales y datos suaves con el fin de identificar tendencias y movimientos direccionales a corto plazo, niveles dinámicos de apoyo/resistencia, límites de canales, etc. Las primeras aplicaciones del concepto de media móvil se remontan a principios del siglo XX, cuando economistas e ingenieros empezaron a usarla para analizar datos y predecir valores futuros. Lo interesante es que en la Segunda Guerra Mundial, los operadores de los cañones antiaéreos utilizaban métodos de cálculo de SMA para la designación de objetivos.

Luego las medias móviles se generalizaron en el análisis financiero, especialmente con el desarrollo del comercio bursátil, a mediados del siglo XX. En esa época, los inversores empezaron a buscar métodos para suavizar las fluctuaciones de las cotizaciones bursátiles e identificar tendencias a largo plazo. Desde entonces, las medias móviles se han convertido en una herramienta estándar en el análisis técnico, considerada tanto por tráders como por analistas.

En el mismo gráfico de precios, dos medias móviles iguales con el mismo periodo de cálculo pero distintos tipos mostrarán los datos de forma distinta:

  • La SMA será más suave y mostrará un nivel de apoyo o resistencia, pero con retraso.
  • La EMA seguirá a los precios al encontrarse más cerca del precio y reaccionar más rápido a los cambios de tendencia.
  • La SMMA supondrá una línea más suave que la SMA, con menos reacción a los cambios bruscos.
  • La LWMA también reaccionará con rapidez, pero su comportamiento será más volátil en comparación con la EMA y la SMMA.

La elección entre estos tipos de medias móviles dependerá de la estrategia específica y de las condiciones del mercado. Así, deberemos considerar los objetivos comerciales, el marco temporal y la volatilidad del activo a la hora de utilizar medias móviles en la negociación. A menudo, los tráders combinan distintos tipos de medias móviles para maximizar la eficacia del análisis.


Tabla-lista de archivos adjuntos:

#
Tipo
Nombre 
 Descripción
1
Indicador
SMAonPriceCloseRAW.mq5
Indicador con un ejemplo de cálculo de SMA. Supone solo un cálculo aproximado, sin organizar un cálculo eficiente del indicador y sin optimizar los cálculos
2
Indicador
SMAonPriceCloseECO.mq5
Indicador que calcula la SMA. El cálculo eficiente del indicador se organiza sin optimizar los cálculos
3
Indicador
SMAonPriceClose.mq5
Indicador que calcula la SMA. Cálculo eficiente organizado del indicador con optimización de los cálculos del SMA
4
Indicador
EMAonPriceCloseRAW.mq5
Indicador con un ejemplo de cálculo de la EMA. Solo cálculo grueso, sin organizar un cálculo eficiente del indicador
5
Indicador
EMAonPriceClose.mq5
Indicador que calcula la EMA. Organiza el cálculo eficiente del indicador
6
Indicador
SMMAonPriceCloseRAW.mq5
Indicador con un ejemplo de cálculo de SMMA. Solo cálculo grueso, sin organizar un cálculo eficiente del indicador
7
Indicador
SMMAonPriceClose.mq5
Indicador que calcula el SMMA. Organiza el cálculo eficiente del indicador
8
Indicador
LWMAonPriceCloseRAW.mq5
Indicador con un ejemplo de cálculo de LWMA. Solo cálculo grueso, sin organizar un cálculo eficiente del indicador
9
Indicador
LWMAonPriceClose.mq5
Indicador que calcula la LWMA. Organiza el cálculo eficiente del indicador

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

Alexey Viktorov
Alexey Viktorov | 18 nov 2024 en 14:33

Gracias por el buen artículo. Pero no puedo evitar criticarlo. Hoy estoy de mal humor.

El artículo presta especial atención no tanto a la codificación como a las fórmulas y la optimización de los cálculos. Pero creo que será más útil para los programadores principiantes y para los que han empezado hace poco a prestar atención a la posibilidad de utilizar distintos operadores de bucle. Espero que los próximos artículos lo reflejen. Después de todo, hay al menos 3 operadores de bucle en MQL5. Y cualquiera de ellos se puede utilizar para construir un indicador.

lynxntech
lynxntech | 18 nov 2024 en 15:11

información útil, todo a la vez en un solo lugar,

y finalmente el bucle i++

CapeCoddah
CapeCoddah | 3 jul 2025 en 10:50

Un buen artículo para principiantes para mostrar el código detrás de los promedios estándar de 4 MQ.

También debe discutir la importancia de la optimización simple y su impacto ya que el 99% de los ticks ocurren entre los cambios de barra. Minimizar los cálculos entre cada cambio de barra proporcionará una eficiencia mucho mayor a costa de un poco de complejidad. Así, calculando los valores base una vez en el cambio de barra y guardando los valores producirá reducciones significativas en el tiempo de cálculo. por ejemplo con un SAM el cálculo estándar para N periodos es:

Consideremos

double sm=0;

for(int bar=0;bar<N;bar++) suma+=Cierre[BarraActual];

SMA=suma/N;


frente a

doble estático partialsum;

double sum=0;


En el cambio de barra{

partialsum=0;

for(int bar=0;bar<N-1;bar++) partialsum+=Close[CurrentBar-bar];

partialsum/=(N-1);

}

SMA =partialsum +Cierre[BarraActual]/N;

Si hay 1000 ticks en un periodo de barra y N=10 entonces esta optimización ahorra unos 90.000 cálculos de suma+=Cierre[BarraFin] para cada barra. Si tu gráfico contiene 1.000 barras, entonces el ahorro es de más de 90 millones de cálculos innecesarios. Con los cpus modernos este ahorro generado por este ejemplo es trivial y probablemente no se note, con el tiempo se sumarán a medida que tus programas se vuelvan más complejos en Asesores Expertos.

La importancia de la optimización manual es que usted está desarrollando mejores técnicas de programación que se convertirá en una segunda naturaleza para usted en futuros proyectos.

1120965101
1120965101 | 13 ago 2025 en 23:58

Gracias por su artículo, pero no está claro por qué, después de utilizar la función OnCalculate(), ya no puedo utilizar la función de transacción OrderSend(), no sé cómo el autor resolvió este problema, no tengo más remedio que utilizar los indicadores de la biblioteca estándar de la siguiente manera:

#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Indicators\Trend.mqh>
CiMA ma;

//+------------------------------------------------------------------+
//| Función de inicio de programa &nbsp Función de inicio de programa
//+------------------------------------------------------------------+
int OnInit(){ 
    ma.DeleteFromChart(ChartID(), 0);
    ma.Create(_Symbol, PERIOD_CURRENT, 14, 0, MODE_SMA, PRICE_CLOSE);
    ma.AddToChart(PERIOD_CURRENT, 0);
    
    return INIT_SUCCEEDED; 
}

void OnTick(){ 
    ma.Refresh();
    double curMA = ma.Main(0);
    
    //Print("Valor actual de la MA:", maValue);
}
Selección de características y reducción de dimensionalidad mediante componentes principales Selección de características y reducción de dimensionalidad mediante componentes principales
El artículo profundiza en la implementación de un algoritmo modificado de análisis de componentes por selección ascendente, inspirándose en la investigación presentada en «Forward Selection Component Analysis: Algorithms and Applications» (Análisis de componentes por selección ascendente: algoritmos y aplicaciones), de Luca Puggini y Sean McLoone.
Creación de un asesor experto MQL5 basado en la estrategia de ruptura del rango diario (Daily Range Breakout) Creación de un asesor experto MQL5 basado en la estrategia de ruptura del rango diario (Daily Range Breakout)
En este artículo, creamos un Asesor Experto MQL5 basado en la estrategia de ruptura del rango diario (Daily Range Breakout). Cubrimos los conceptos clave de la estrategia, diseñamos el plano del EA e implementamos la lógica de ruptura en MQL5. Al final, exploramos técnicas para realizar pruebas retrospectivas y optimizar el EA con el fin de maximizar su eficacia.
Indicador de perfil de mercado — Market Profile (Parte 2): Optimización y dibujado en canvas Indicador de perfil de mercado — Market Profile (Parte 2): Optimización y dibujado en canvas
En este artículo analizaremos una versión optimizada del indicador de Perfil de mercado Market Profile, donde el dibujado por parte de un conjunto de objetos gráficos se sustituye por el dibujado en un lienzo: un objeto de la clase CCanvas.
Redes neuronales en el trading: Modelo adaptativo multiagente (Final) Redes neuronales en el trading: Modelo adaptativo multiagente (Final)
En el artículo anterior, nos familiarizamos con el framework MASA, un framework adaptativo multiagente que combina enfoques de aprendizaje por refuerzo y estrategias adaptativas para ofrecer un equilibrio armonioso entre rentabilidad y riesgo en condiciones de mercado turbulentas. Asimismo, construimos la funcionalidad de los agentes individuales de este framework. En este artículo continuaremos el trabajo empezado, llevándolo a su conclusión lógica.