![Desarrollando un EA comercial desde cero (Parte 10): Acceso a los indicadores personalizados](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_from_scratch_001_600x314.jpg)
Desarrollando un EA comercial desde cero (Parte 10): Acceso a los indicadores personalizados
Introducción
A un EA comercial solo se le sacará partido realmente si se puede usar indicadores personalizados en él, de lo contrario, será solo un conjunto de códigos e instrucciones, más o menos bien elaboradas, que nos ayudan de alguna manera a fijar nuestras posiciones y ejecutar las operaciones de mercado, pero no van más allá de eso.
Pero añadir indicadores al gráfico de MetaTrader 5 no es la parte más compleja, acceder a los datos calculados por estos indicadores directamente en un EA, sin una planificación adecuada, se convierte en una tarea casi imposible, y si no sabemos cómo hacerlo, estaremos limitados a los indicadores que están disponibles en el lenguaje MQL5, y éstos pueden no ser exactamente lo que necesitamos. Un ejemplo de esto es el VWAP (Volume Weighted Average Price) que es una media muy importante para los que operan con futuros en la bolsa de valores de BRASIL y aunque esta media no está disponible por defecto entre los indicadores de MetaTrader, nada impide crear un indicador personalizado que realice el cálculo y muestre el VWAP en la pantalla, pero la cosa se complica mucho si se quiere utilizar este mismo indicador en un sistema que será analizado en un EA, sin los conocimientos adecuados no se podrá realmente utilizar un indicador personalizado dentro de un EA, pero aquí explicaré cómo sortear esto y resolver esta tarea.
Planificación
Lo primero que hay que hacer es intentar crear el cálculo que utilizaremos en el indicador personalizado, por suerte el cálculo de VWAP que utilizaré como ejemplo es algo bastante sencillo.
Traduciendo esto a lenguaje de programación, en el caso de MQL5 tenemos lo siguiente:
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 Price = 0; ulong Volume = 0; static int siPos = 0; if (macroGetDate(time[rates_total - 1]) != macroGetDate(time[siPos])) { for (int c0 = rates_total - 1; macroGetDate(time[siPos]) != macroGetDate(time[c0]); siPos++); ArrayInitialize(VWAP_Buff, EMPTY_VALUE); } for (int c0 = siPos; c0 < rates_total; c0++) { Price += ((high[c0] + low[c0] + close[c0]) / 3) * volume[c0]; Volume += volume[c0]; VWAP_Buff[c0] = Price / Volume; } return rates_total; }
El cálculo en sí está resaltado, ya el resto de la función se utiliza para inicializar correctamente el VWAP DIARIO. Bien, pero a pesar de esto nuestro indicador todavía no puede ser colocado en el gráfico, tenemos que añadir algunas pequeñas cosas más al código, y el resto del código se puede ver a continuación:
#property copyright "Daniel Jose - Indicador VWAP ( IntraDay )" #property version "1.01" #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_width1 2 #property indicator_type1 DRAW_LINE #property indicator_color1 clrBlack //+------------------------------------------------------------------+ #define macroGetDate(A) (A - (A % 86400)) //+------------------------------------------------------------------+ double VWAP_Buff[]; //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA); return INIT_SUCCEEDED; }
Con esto tenemos la opción del VWAP en el gráfico como se puede ver a continuación:
Bueno esta fue la parte fácil, ahora tenemos que encontrar la manera de hacer que el EA vea el VWAP para que lo analice, de alguna manera específica, para sacarle provecho y que nos ayude durante las operaciones.
Para facilitarnos la vida, vamos a dejar el VWAP en un lugar de fácil acceso, así que decidí dejarlo en el lugar indicado.
Una vez que se haya hecho esto, nos aventuraremos en una nueva forma de diseñar las cosas. Aunque nuestro indicador VWAP esté correcto en esencia, no está debidamente programado para ser utilizado en un EA. Quizá pensemos: ¿Pero cómo así? El problema es que el EA no podrá saber si el indicador está en el gráfico o no, y sin saberlo no podrá leer el indicador.
El problema es que el nombre del archivo importa poco al sistema, se puede poner cualquier nombre en el archivo, pero el nombre del indicador debe reflejar lo que está calculando, y nuestro indicador aún no tiene un nombre que lo refleje, aunque se llame VWAP, para el sistema esto es irrelevante, por esta razón el EA no podrá saber si está o no presente en el gráfico.
Para que el indicador refleje lo que está calculando, tenemos que indicarlo en su código, de esta manera crearemos un nombre único que no estará necesariamente ligado al nombre del archivo. En nuestro caso, el código de inicialización del indicador debería tener este aspecto:
int OnInit() { SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA); IndicatorSetString(INDICATOR_SHORTNAME, "VWAP"); return INIT_SUCCEEDED; }
Añadiendo sólo la línea resaltada ya se soluciona nuestro problema, hay casos en los que la cosa puede ser un poco más complicada, lo veremos más adelante en este artículo, pero tomemos por ejemplo el código del indicador CUSTOM MOVING AVERAGE presente en la librería de MetaTrader , que se puede ver debajo de su fragmento de inicialización:
void OnInit() { SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1); PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod); PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift); string short_name; switch(InpMAMethod) { case MODE_EMA : short_name="EMA"; break; case MODE_LWMA : short_name="LWMA"; break; case MODE_SMA : short_name="SMA"; break; case MODE_SMMA : short_name="SMMA"; break; default : short_name="unknown ma"; } IndicatorSetString(INDICATOR_SHORTNAME, short_name + "(" + string(InpMAPeriod) + ")"); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); }
La parte que indica este nombre está marcada como resaltada, nótese que es algo que no tiene nada que ver con el nombre del fichero, pero es muy importante que se haga dentro de un indicador personalizado.
Ahora que lo hemos hecho, y nos hemos asegurado de que el EA será capaz de verificar si el indicador personalizado está en el gráfico o no, podemos pasar al siguiente paso.
Acceso al indicador a través del EA
Aunque puedo hacer las cosas como las he estado haciendo, lo ideal para entender realmente lo que está pasando es crear un código completamente nuevo, pero como la idea es desarrollar un EA comercial desde cero, y tenemos que pasar por este punto para continuar el viaje, voy a crear un EA aislado, podremos incorporar o no esto en el futuro en nuestro código final. Bueno, ya está hecha esta salvedad, vamos a codificar. Nuestro EA comienza con un código LIMPIO, que se ve a continuación:
//+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick(){} //+------------------------------------------------------------------+ void OnTimer(){} //+------------------------------------------------------------------+
Hagamos lo siguiente: En un primer momento vamos a suponer que el indicador VWAP está en el gráfico, y vamos a cargar el último valor calculado por el indicador en el EA, esto se hará cada un segundo. ¿Pero cómo lo haremos? Es bastante sencillo, veamos cómo queda el código del EA después de ser modificado para reflejar esto:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int handle; double Buff[]; //+------------------------------------------------------------------+ int OnInit() { handle = ChartIndicatorGet(ChartID(), 0, "VWAP"); SetIndexBuffer(0, Buff, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i; if (handle != INVALID_HANDLE) { i = CopyBuffer(handle, 0, 0, 1, Buff); Print(Buff[0]); } } //+------------------------------------------------------------------+
Las partes resaltadas son las que se añadieron al código limpio, y el resultado se ve a continuación:
Entonces uno piensa: ¡¿Qué ha pasado, por qué funcionó esto?! La razón es que MQL5 nos proporciona un medio para leer y escribir datos entre sistemas, una de las formas de leerlo es utilizando la función CopyBuffer y funciona como se muestra a continuación:
Esto nos permite leer cualquier información de cualquier indicador personalizado, es decir, no se limita a los indicadores de la biblioteca estándar de MetaTrader 5, se puede crear cualquier indicador y la cosa funcionará siempre que se sigan los pasos que he mostrado hasta ahora.
Ahora analicemos otro escenario, esta vez el indicador VWAP no está en el gráfico, pero el EA lo necesita así que tendremos que cargarlo en el gráfico, pero ¿cómo hacerlo? Esto también, es bastante sencillo, e incluso ya se utilizó esto en otro momento anterior, pero para otro fin que era el de crear una subventana por EA, lo que haremos es utilizar la función iCustom, pero esta vez la intención es cargar un indicador personalizado, entonces el código del EA es el que se muestra a continuación:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int handle; double Buff[]; //+------------------------------------------------------------------+ int OnInit() { handle = ChartIndicatorGet(ChartID(), 0, "VWAP"); SetIndexBuffer(0, Buff, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i; if (handle == INVALID_HANDLE) handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5");else { i = CopyBuffer(handle, 0, 0, 1, Buff); Print(Buff[0]); } } //+------------------------------------------------------------------+
El código resaltado es la única adición que hicimos en el sistema original, y al ejecutar el EA anterior obtenemos el siguiente resultado:
Lo que hicimos se puede ver en la imagen de abajo:
Esto es todo lo que se necesita en el nivel más básico de la cuestión, pero si nos damos cuenta no es posible ver el VWAP en el gráfico, aunque el EA lo esté usando realmente, el usuario no sabe lo que está pasando, de todas maneras podemos arreglar esto fácilmente, y el código final es el que se muestra a continuación, recuerden una cosa, siempre es bueno poder analizar y observar lo que el EA está haciendo, dejarlo suelto no es algo muy seguro ni recomendable:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int handle; long id; double Buff[]; string szCmd; //+------------------------------------------------------------------+ int OnInit() { szCmd = "VWAP"; handle = ChartIndicatorGet(id = ChartID(), 0, szCmd); SetIndexBuffer(0, Buff, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ChartIndicatorDelete(id, 0, szCmd); IndicatorRelease(handle); EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i; if (handle == INVALID_HANDLE) { if ((handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5")) != INVALID_HANDLE) ChartIndicatorAdd(id, 0, handle); }else { i = CopyBuffer(handle, 0, 0, 1, Buff); Print(Buff[0]); } } //+------------------------------------------------------------------+
Este código del EA de arriba leerá el último valor calculado por el indicador VWAP y lo mostrará en la pantalla, si no está en el gráfico cargará el indicador al EA, y lo mostrará en la pantalla. Al quitar el EA, eliminará el VWAP de la pantalla, es decir, mantendremos el EA siempre con lo que necesita para realizar sus cálculos y así asistirnos de la mejor manera posible. El resultado de lo que he explicado puede verse a continuación:
A pesar de todo, se puede pensar que esto no es muy factible, ya que aparentemente no hicimos ningún ajuste al indicador, pero usando solo lo que mostré es posible hacer cualquier cosa relacionada con indicadores personalizados, para ejemplificar esto y para un punto final de aclaración mostraré una cosa diferente, aplicaremos una media móvil y usaremos el EA de la misma manera que hicimos con el VWAP, solo que indicaremos parámetros para la media.
Un segundo caso: uso de medias móviles
El cálculo de la media móvil no viene al caso, quiero que nos centremos en lo que realmente importa, en cómo pasar los parámetros a un indicador personalizado, entonces nuestro nuevo indicador personalizado se vería así:
#property copyright "Daniel Jose 16.05.2021" #property description "Medias Moveis Basicas ( Calculo otimizado )" #property indicator_chart_window //+------------------------------------------------------------------+ enum eTypeMedia { MME, //Media Movel Exponencial MMA //Media Movel Aritimetica }; //+------------------------------------------------------------------+ #property indicator_buffers 1 #property indicator_plots 1 #property indicator_type1 DRAW_LINE #property indicator_width1 2 #property indicator_applied_price PRICE_CLOSE //+------------------------------------------------------------------+ input color user00 = clrRoyalBlue; //Cor input int user01 = 9; //Periodos input eTypeMedia user02 = MME; //Tipo de Media input int user03 = 0; //Deslocamento //+------------------------------------------------------------------+ double Buff[], f_Expo; //+------------------------------------------------------------------+ int OnInit() { string sz0 = "MM" + (user02 == MME ? "E" : (user02 == MMA ? "A" : "_")) + (string)user01; f_Expo = (double) (2.0 / (1.0 + user01)); ArrayInitialize(Buff, EMPTY_VALUE); SetIndexBuffer(0, Buff, INDICATOR_DATA); PlotIndexSetInteger(0, PLOT_LINE_COLOR, user00); PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, user01); PlotIndexSetInteger(0, PLOT_SHIFT, user03); IndicatorSetString(INDICATOR_SHORTNAME, sz0); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { double Value; int c0; switch (user02) { case MME: if (user01 < rates_total) { for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : 0); c0 < rates_total - user03; c0++) Buff[c0] = (c0 > 0 ? ((price[c0] - Buff[c0 - 1]) * f_Expo) + Buff[c0 - 1] : price[c0] * f_Expo); for (; c0 < rates_total; c0++) Buff[c0] = EMPTY_VALUE; } break; case MMA: if (user01 < rates_total) { if (prev_calculated == 0) { Value = 0; for (int c1 = 0; c1 < user01; c1++) Value += price[user01 - c1]; Buff[user01] = Value / user01; } for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : user01 + 1); c0 < rates_total - user03; c0++) Buff[c0] = ((Buff[c0 - 1] * user01) - price[c0 - user01] + price[c0]) / user01; for (; c0 < rates_total; c0++) Buff[c0] = EMPTY_VALUE; } break; } return rates_total; } //+------------------------------------------------------------------+
Bien, ahora el nombre del indicador dependerá de algunas cosas, luego podemos hacer que el EA compruebe y se ajuste a cada una de las situaciones. Para ejemplificar, hagamos que nuestro EA utilice 2 medias móviles y que se muestren en el gráfico, el código del indicador se muestra arriba, pero hay que observar las partes resaltadas, son precisamente estas las que permiten al EA, y en este caso a la función iCustom, cambiar y ajustar los parámetros del indicador, es muy importante entender esto, para poder hacerlo cuando sea necesario. Así que una de las medias será una media exponencial de 17 periodos y la otra media exponencial de 52 periodos y aritmética, queremos que la media de 17 periodos sea verde y la media de 52 periodos sea roja. Así el EA verá el indicador como una función de la siguiente forma:
MEDIA (COLOR, PERIODO, TIPO, DESPLAZAMIENTO) es decir, ya no hay que pensar que el indicador es un archivo aparte, sino que es una función del EA, esto es muy común en programación, pues llamamos a un programa con los parámetros adecuados para que haga una tarea específica, y al final obtenemos los resultados más fácilmente. Pero la pregunta es: ¿Cómo hacemos que nuestro EA cree y gestione este escenario de la misma manera que lo hicimos con el VWAP?
Para hacer esto nuestro EA tendrá que cambiar, el código completo del nuevo EA se puede ver a continuación, recordando que debe ver el indicador como una función:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ long id; int handle1, handle2; double Buff1[], Buff2[]; string szCmd1, szCmd2; //+------------------------------------------------------------------+ int OnInit() { szCmd1 = "MME17"; szCmd2 = "MMA52"; id = ChartID(); handle1 = ChartIndicatorGet(id, 0, szCmd1); handle2 = ChartIndicatorGet(id, 0, szCmd2); SetIndexBuffer(0, Buff1, INDICATOR_DATA); SetIndexBuffer(0, Buff2, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ChartIndicatorDelete(id, 0, szCmd1); ChartIndicatorDelete(id, 0, szCmd2); IndicatorRelease(handle1); IndicatorRelease(handle2); EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i1, i2; if (handle1 == INVALID_HANDLE) { if ((handle1 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrGreen, 17, 0)) != INVALID_HANDLE) ChartIndicatorAdd(id, 0, handle1); }; if (handle2 == INVALID_HANDLE) { if ((handle2 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrRed, 52, 1)) != INVALID_HANDLE) ChartIndicatorAdd(id, 0, handle2); }; if ((handle1 != INVALID_HANDLE) && (handle2 != INVALID_HANDLE)) { i1 = CopyBuffer(handle1, 0, 0, 1, Buff1); i2 = CopyBuffer(handle2, 0, 0, 1, Buff2); Print(Buff1[0], "<< --- >>", Buff2[0]); } } //+------------------------------------------------------------------+
Y el resultado se ve en la animación de abajo:
Pero observen las partes resaltadas en el código de EA. Esto es exactamente lo que queríamos hacer, es decir, le pasamos los parámetros al indicador utilizando el mismo mecanismo que utilizamos en el VWAP, pero en el caso del VWAP no era necesario informarle ningún parámetro, pero en el caso de los promedios es necesario informarle lo que queremos, y el hecho de hacer esto nos permite tener un grado de libertad muy grande, ya que de esta manera no tiene forma de hacer algo enfocado o específico, ya que dependerá de lo que esté programando y tratando de hacer el EA para analizar.
Conclusión
Este artículo es uno de los pocos en los que no tengo como dejar un código genérico disponible para su uso, pero aun así detallé 2 EAs y 2 indicadores personalizados distintos para así mostrar cómo utilizar este tipo de sistema en un EA más complejo y elaborado. Creo que el hecho de haber hecho esto, será suficiente para que podamos utilizar indicadores personalizados desarrollados por nosotros. En análisis realizados por el EA, la cosa es muy interesante, y aunque algunos sigan afirmando que MetaTrader 5 es un sistema básico y con pocos recursos, demuéstrenle a esta gente que están equivocados, MetaTrader 5 es la plataforma más versátil y completa que un trader puede desear, pero la falta de conocimiento de esta gente que insiste en decir tonterías hace que las cosas no se exploren del todo.
Aprovechen los conocimientos que les he mostrado en este artículo, ya que MetaTrader 5 les permite llegar mucho más lejos de lo que muchos de ustedes han llegado hasta ahora.
Así que hasta nos veremos en un próximo artículo.
Referencias externas
Cómo llamar indicadores en MQL5
Indicadores personalizados para principiantes en MQL5
Guía para el Uso de Indicadores Técnicos en Asesores Expertos
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/10329
![Desarrollando un EA comercial desde cero (Parte 11): Sistema de órdenes cruzadas](https://c.mql5.com/2/46/development__2.png)
![Desarrollando un EA comercial desde cero (Parte 09): Un salto conceptual (II)](https://c.mql5.com/2/46/development__1.png)
![Desarrollando un EA comercial desde cero (Parte 12): Time and Trade (I)](https://c.mql5.com/2/46/development__3.png)
![Aprendiendo a diseñar un sistema de trading con Envelopes](https://c.mql5.com/2/45/why-and-how__3.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso