Utilización de los indicadores integrados

Como sencillo ejemplo introductorio del uso del indicador integrado, vamos a utilizar una llamada a iStochastic. El prototipo de esta función indicadora es el siguiente:

int iStochastic(const string symbol, ENUM_TIMEFRAMES timeframe,
int Kperiod, int Dperiod, int slowing,
ENUM_MA_METHOD method, ENUM_STO_PRICE price)

Como vemos, además de los parámetros estándar symbol y time frame, el estocástico tiene varios parámetros específicos:

  • Kperiod : número de barras para calcular la línea %K
  • Dperiod : periodo de suavizado primario para la línea %D
  • slowing : periodo de suavizado secundario (deceleración)
  • method : método de promediado (suavizado)
  • price : método de cálculo estocástico

Vamos a intentar crear nuestro propio indicador UseStochastic.mq5, que copiará los valores del estocástico en sus búferes. Como hay dos búferes en el estocástico, reservaremos también dos: son las líneas «principal» y «señal».

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2
   
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_width1  1
#property indicator_label1  "St'Main"
   
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrChocolate
#property indicator_width2  1
#property indicator_label2  "St'Signal"
#property indicator_style2  STYLE_DOT

En las variables de entrada proporcionamos todos los parámetros necesarios.

input int KPeriod = 5;
input int DPeriod = 3;
input int Slowing = 3;
input ENUM_MA_METHOD Method = MODE_SMA;
input ENUM_STO_PRICE StochasticPrice = STO_LOWHIGH;

A continuación, describimos arrays para los búferes de indicadores y una variable global para el descriptor.

double MainBuffer[];
double SignalBuffer[];
   
int Handle;

Inicializaremos en OnInit.

int OnInit()
{
   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("Stochastic(%d,%d,%d)"KPeriodDPeriodSlowing));
   // binding of arrays as buffers
   SetIndexBuffer(0MainBuffer);
   SetIndexBuffer(1SignalBuffer);
   // getting the descriptor Stochastic
   Handle = iStochastic(_Symbol_Period,
      KPeriodDPeriodSlowingMethodStochasticPrice);
   return Handle == INVALID_HANDLE ? INIT_FAILED : INIT_SUCCEEDED;
}

Ahora, en OnCalculate, necesitamos leer los datos usando la función CopyBuffer tan pronto como el manejador esté listo.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &data[])
{
   // waiting for the calculation of the stochastic on all bars
   if(BarsCalculated(Handle) != rates_total)
   {
      return prev_calculated;
   }
   
   // copy data to our two buffers
   const int n = CopyBuffer(Handle00rates_total - prev_calculated + 1,
      MainBuffer);
   const int m = CopyBuffer(Handle10rates_total - prev_calculated + 1,
      SignalBuffer);
   
   return n > -1 && m > -1 ? rates_total : 0;
}

Tenga en cuenta que estamos llamando a CopyBuffer dos veces: para cada búfer por separado (0 y 1 en el segundo parámetro). Un intento de leer un búfer con un índice inexistente, por ejemplo, 2, generaría un error y no recibiríamos ningún dato.

Nuestro indicador no es especialmente útil, ya que no añade nada al estocástico original y no analiza sus lecturas. Por otro lado, podemos asegurarnos de que las líneas del indicador terminal estándar y las creadas en MQL5 coinciden (también se podrían añadir fácilmente niveles y ajustes de precisión, como hacíamos con los indicadores completamente personalizados, pero entonces sería difícil distinguir una copia del original).

Estocástico estándar y personalizado basado en la función iStochastic

Estocástico estándar y personalizado basado en la función iStochastic

Para demostrar el almacenamiento en caché de los indicadores por el terminal, añada a la función OnInit un par de líneas.

   double array[];
   Print("This is very first copy of iStochastic with such settings=",
      !(CopyBuffer(Handle0010array) > 0));

En este caso, hemos utilizado un truco relacionado con las características conocidas: inmediatamente después de que se cree el indicador, tarda algún tiempo en calcularse, y es imposible leer datos del búfer inmediatamente después de recibir el manejador. Esto es válido para el caso del arranque «en frío», cuando el indicador con los parámetros especificados aún no existe en la caché, en la memoria del terminal. Si hay un análogo preparado, entonces podemos acceder instantáneamente al búfer.

Después de compilar un nuevo indicador, debe colocar dos copias del mismo en dos gráficos del mismo símbolo y marco temporal. La primera vez se mostrará un mensaje con la bandera true en el registro (ésta es la primera copia), y la segunda vez (y las siguientes, si hay muchos gráficos) será false. También puede añadir primero manualmente un indicador «Stochastic Oscillator» estándar al gráfico (con los ajustes predeterminados o los que se aplicarán después en Use Stochastic) y después ejecutar Use Stochastic: necesitamos obtener también false.

Ahora vamos a intentar inventar algo original basado en un indicador estándar. El siguiente indicador UseM1MA.mq5 está diseñado para calcular precios medios por barra en M5 y marcos temporales superiores (principalmente, intradía). Acumula los precios de las barras M1 que caen dentro del rango de marcas de tiempo de cada barra específica en el marco temporal de trabajo (superior). Esto le permite estimar el precio efectivo de una barra con mucha más precisión que los tipos de precio estándar (Close, Open, Median, Typical, Weighted, etc.). Además, preveremos la posibilidad de promediar dichos precios a lo largo de un período determinado, pero aquí debe estar preparado para que no funcione una línea especialmente suave.

El indicador se mostrará en la ventana principal y contendrá un único búfer. Los ajustes pueden modificarse con ayuda de 3 parámetros:

input uint _BarLimit = 100// BarLimit
input uint BarPeriod = 1;
input ENUM_APPLIED_PRICE M1Price = PRICE_CLOSE;

BarLimit establece el número de barras del historial más cercano para el cálculo. Esto es importante porque los gráficos de marco temporal alto pueden requerir un número muy grande de barras en comparación con el minuto M1 (por ejemplo, se sabe que un día D1 en el trading 24/7 contiene 1440 barras M1). Esto puede dar lugar a que se descarguen datos adicionales en espera de sincronización. Experimente con la configuración de ahorro por defecto (100 barras del marco temporal de trabajo) antes de ajustar este parámetro a 0, lo que significa procesamiento sin límite.

Sin embargo, incluso cuando se establece BarLimit en 0, es probable que el indicador no se calcule para todo el historial visible del marco temporal más antiguo: si el terminal tiene un límite en el número de barras del gráfico, entonces también afectará a las solicitudes de barras M1. En otras palabras, la profundidad del análisis está determinada por el tiempo durante el cual el número máximo permitido de barras M1 entra en la historia.

BarPeriod establece el número de barras del marco temporal superior para el que se realiza el promedio. El valor por defecto aquí es 1, lo que le permite ver el precio efectivo de cada barra por separado.

El parámetro M1Price especifica el tipo de precio utilizado para los cálculos de las barras M1.

En el contexto global, un array se describe para un búfer, un descriptor y una bandera de autoactualización, que necesitamos para esperar la construcción de una serie temporal del marco temporal M1 «ajeno».

double Buffer[];
   
int Handle;
int BarLimit;
bool PendingRefresh;
   
const string MyName = "M1MA (" + StringSubstr(EnumToString(M1Price), 6)
   + "," + (string)BarPeriod + "[" + (string)(PeriodSeconds() / 60) + "])";
const uint P = PeriodSeconds() / 60 * BarPeriod;

Además, aquí se forman el nombre del indicador y el periodo de promediación P. La función PeriodSeconds, que devuelve el número de segundos dentro de una barra del marco temporal actual, le permite calcular el número de barras M1 dentro de una barra actual: PeriodSeconds() / 60 (60 segundos es la duración de la barra M1).

La inicialización habitual se realiza en OnInit.

int OnInit()
{
   IndicatorSetString(INDICATOR_SHORTNAMEMyName);
   IndicatorSetInteger(INDICATOR_DIGITS_Digits);
   
   SetIndexBuffer(0Buffer);
   
   Handle = iMA(_SymbolPERIOD_M1P0MODE_SMAM1Price);
   
   return Handle != INVALID_HANDLE ? INIT_SUCCEEDED : INIT_FAILED;
}

Para obtener el precio medio en una barra de marco temporal superior, aplicamos una media móvil simple, llamando a iMA con el modo MODE_SMA.

La función OnCalculate que se muestra a continuación está simplificada. En la primera ejecución o cambio de historial, borramos el búfer y rellenamos la variable BarLimit (esto es necesario porque las variables de entrada no se pueden editar, y queremos interpretar el valor 0 como el número máximo de barras disponibles para el cálculo). Durante las siguientes llamadas, los elementos del búfer se borran sólo en los últimos compases, a partir de prev_calculated y no más allá de BarLimit.

int OnCalculate(ON_CALCULATE_STD_FULL_PARAM_LIST)
{
   if(prev_calculated == 0)
   {
      ArrayInitialize(BufferEMPTY_VALUE);
      if(_BarLimit == 0
      || _BarLimit > (uint)rates_total)
      {
         BarLimit = rates_total;
      }
      else
      {
         BarLimit = (int)_BarLimit;
      }
   }
   else
   {
      for(int i = fmax(prev_calculated - 1, (int)(rates_total - BarLimit));
         i < rates_total; ++i)
      {
         Buffer[i] = EMPTY_VALUE;
      }
   }

Antes de leer los datos del indicador iMA creado, hay que esperar a que estén listos: para ello comparamos BarsCalculated con el número de barras M1.

   if(BarsCalculated(Handle) != iBars(_SymbolPERIOD_M1))
   {
      if(prev_calculated == 0)
      {
         EventSetTimer(1);
         PendingRefresh = true;
      }
      return prev_calculated;
   }
   ...

Si los datos no están listos, iniciamos un temporizador para intentar leerlos de nuevo en un segundo.

A continuación, entramos en la parte principal de cálculo del algoritmo y, por lo tanto, debemos detener el temporizador si todavía está en marcha. Esto puede suceder si el siguiente evento de tic llegó más rápido, antes de 1 segundo, e iMA M1 ya dio resultados. Lo lógico sería simplemente llamar a la función EventKillTimeradecuada. Sin embargo, hay un matiz en su comportamiento: no borra la cola de eventos para un programa MQL de tipo indicador, y si un evento de temporizador ya está colocado en la cola, entonces se llamará una vez al manejador OnTimer. Para evitar la actualización innecesaria del gráfico, controlamos el proceso utilizando nuestra propia variable Pending Refresh, y aquí le asignamos false.

   ...
   Pending Refresh =false;// data is ready, the timer will idle
   ...

Así es como se organiza todo en el manejador OnTimer:

void OnTimer()
{
   EventKillTimer();
   if(PendingRefresh)
   {
      ChartSetSymbolPeriod(0_Symbol_Period);
   }
}

Volvamos a OnCalculate y presentemos el flujo de trabajo principal.

   for(int i = fmax(prev_calculated - 1, (int)(rates_total - BarLimit));
      i < rates_total; ++i)
   {
      static double result[1];
      
      // get the last bar M1 corresponding to the i-th bar of the current timeframe
      const datetime dt = time[i] + PeriodSeconds() - 60;
      const int bar = iBarShift(_SymbolPERIOD_M1dt);
      
      if(bar > -1)
      {
         // request MA value on M1
         if(CopyBuffer(Handle0bar1result) == 1)
         {
            Buffer[i] = result[0];
         }
         else
         {
            Print("CopyBuffer failed: "_LastError);
            return prev_calculated;
         }
      }
   }
   
   return rates_total;
}

El funcionamiento del indicador se ilustra con la siguiente imagen en EURUSD,H1. La línea azul corresponde a la configuración por defecto. Cada valor se obtiene promediando PRICE_CLOSE sobre 60 barras M1. La línea naranja incluye además el suavizado en 5 barras H1, con precios M1 PRICE_TYPICAL.

Dos instancias del indicador UseM1MA en EURUSD,H1

Dos instancias del indicador UseM1MA en EURUSD,H1

El libro presenta una versión simplificada de UseM1MASimple.mq5. Dejamos entre bastidores las particularidades del cálculo de la media de la última barra (incompleta), el tratamiento de las barras vacías (para las que no hay datos en M1) y la configuración correcta de la propiedad PLOT_DRAW_BEGIN, así como el control de la aparición de desfases a corto plazo en el cálculo de la media cuando aparecen nuevas barras. La versión completa está disponible en el archivo UseM1MA.mq5.

Como último ejemplo de construcción de indicadores a partir de indicadores estándar, vamos a analizar la mejora del indicador IndUnityPercent.mq5, que se presentó en la sección Indicadores multidivisa y de marco temporal múltiple. La primera versión utilizaba los precios de Close para los cálculos, obteniéndolos con CopyBuffer. En la nueva versión UseUnityPercentPro.mq5, vamos a sustituir este método por la lectura de los datos del indicador iMA. Esto nos permitirá implementar nuevas funciones:

  • Precios medios durante un periodo determinado
  • Elija el método de cálculo de medias
  • Elija el tipo de precio para el cálculo

Los cambios en el código fuente son mínimos. Añadimos 3 nuevos parámetros y un array global para los manejadores iMA:

input ENUM_APPLIED_PRICE PriceType = PRICE_CLOSE;
input ENUM_MA_METHOD PriceMethod = MODE_EMA;
input int PricePeriod = 1;
...   
int Handles[];

En la función de ayuda InitSymbols, que se llama desde OnInit para analizar una cadena con una lista de símbolos de trabajo, añadimos la asignación de memoria para un nuevo array (su tamaño SymbolCount se determina a partir de la lista).

string InitSymbols()
{
   SymbolCount = StringSplit(Instruments, ',', Symbols);
   ...
   ArrayResize(HandlesSymbolCount);
   ArrayInitialize(HandlesINVALID_HANDLE);
   ...
   for(int i = 0i < SymbolCounti++)
   {
      ...
      Handles[i] = iMA(Symbols[i], PERIOD_CURRENTPricePeriod0,
         PriceMethodPriceType);
   }
}

Al final de la misma función, crearemos los descriptores de los indicadores subordinados necesarios.

En la función Calculate, donde se realiza el cálculo principal, sustituimos las llamadas de la forma:

CopyClose(Symbols[j], _Periodtime0time1w);

por llamadas:

CopyBuffer(Handles[j], 0time0time1w); // j-th handle, 0-th buffer

Para mayor claridad, también hemos completado el nombre abreviado del indicador con tres nuevos parámetros:

   IndicatorSetString(INDICATOR_SHORTNAME,
      StringFormat("Unity [%d] %s(%d,%s)"workCurrencies.getSize(),
      StringSubstr(EnumToString(PriceMethod), 5), PricePeriod,
      StringSubstr(EnumToString(PriceType), 6)));

Esto es lo que ha ocurrido como resultado:

Indicador multisímbolo UseUnityPercentPro con los principales pares Forex

Indicador multisímbolo UseUnityPercentPro con los principales pares Forex

Aquí se muestra una cesta de las 8 principales monedas Forex (configuración por defecto) promediada sobre 11 barras y calculada en base al precio de typical. Dos líneas gruesas corresponden al valor relativo de las divisas del gráfico actual: EUR está marcado en azul y USD, en verde.