![Desarrollo de un EA comercial desde cero (Parte 25): Dotando de robustez al sistema (II)](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_004_600x314.jpg)
Desarrollo de un EA comercial desde cero (Parte 25): Dotando de robustez al sistema (II)
1.0 - Introducción
En el artículo anterior Dotando de robustez al sistema (I), mostré cómo modificar algunas partes del EA para tener un sistema algo más robusto y fiable.
Bueno, eso fue sólo la introducción de lo que veremos aquí, olviden todo lo que saben, planean o desean, absolutamente todo. Lo más complicado de todo es saber separar las cosas, desde el inicio de esta secuencia, el EA ha crecido casi constantemente, teniendo algunas cosas añadidas, modificadas e incluso eliminadas, pero ahora vamos a ir al extremo de lo que se ha hecho hasta ahora.
La gran cuestión es que, a diferencia de lo que pueda parecer, un EA bien diseñado no contiene y repito no contendrá ningún tipo de indicador en él, no hará otra cosa que observar y asegurar que se respetan las posiciones indicadas de las órdenes. ¡El EA perfecto básicamente es sólo un asistente que da por real, y observen esto, lo que el precio está haciendo! No mira los indicadores, simplemente mira las posiciones u órdenes que están en el gráfico.
Puede que piensen que estoy diciendo tonterías y que no sé de qué estoy hablando. Bueno déjenme preguntarles algo, ¡¿se han detenido a pensar por qué MetaTrader 5 tiene diferentes clases para diferentes cosas?! ¡¿Por qué la plataforma tiene indicadores, servicios, scripts y EAs, por qué no se dejó todo en un solo bloque?! Sea como sea...
Esta es precisamente la cuestión. Si las cosas están separadas es porque hay que trabajarlas por separado.
Los indicadores se utilizan para un propósito general, sea cual sea, pero es bueno que el diseño de los indicadores esté bien trabajado para no comprometer todo el rendimiento, no de la plataforma MetaTrader 5, sino del resto de indicadores, ya que trabajan en un thread diferente al resto del código, por lo que pueden realizar tareas en paralelo de forma muy eficiente.
Los servicios nos sirven de ayuda en varias cuestiones, aquí mismo en esta serie hemos utilizado los servicios en los artículos Acceso a los datos en la Web (II) y Acceso a los datos en la Web (III) para proporcionarnos datos de forma muy interesante. Es cierto que podríamos hacerlo directamente dentro del EA, pero no es la forma más adecuada como expliqué en esos artículos.
Los scripts nos ayudan de una manera muy peculiar, ya que sólo existirán durante un periodo de tiempo determinado, puesto que pueden hacer algo muy concreto y luego salir del gráfico, o pueden permanecer allí hasta que cambiemos alguna configuración del gráfico, por ejemplo, el marco temporal del mismo.
Esto limita un poco las cosas, pero paciencia, es parte de esto y tenemos que aceptar este tipo de cosas. Los EAs, en cambio, son específicos para trabajar con el sistema comercial. Aunque podemos añadir funciones y códigos que no forman parte del sistema comercial dentro de los EAs, esto no es muy apropiado en sistemas de alto rendimiento o con un nivel de robustez muy alto, y la razón es que todo lo que no forma parte del sistema comercial no debe colocarse dentro de un EA, las cosas deben colocarse en los lugares adecuados, y se debe trabajar con ellas correctamente.
Por ello, lo primero que haremos para dar robustez a nuestro EA será retirar del código todo y absolutamente todo lo que no forme parte del sistema comercial, y convertir estas cosas en indicadores o algo así. Lo único que permanecerá en el código del EA serán las partes encargadas de gestionar, analizar y procesar las órdenes o posiciones, todo lo demás se eliminará.
Así que, empecemos.
2.0 - Implementación
2.0.1 - Eliminación del fondo de pantalla del EAAunque esto no trae ningún daño o problema al EA, puede ser que algunos quieran tener la pantalla limpia, con sólo algunas cosas en ella. Así que tomemos este sistema de dentro del EA y convirtámoslo en un indicador. La forma de hacerlo es súper sencilla, no tocaremos nada de las clases, solo crearemos el código de abajo:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh> //+------------------------------------------------------------------+ input string user10 = "Wallpaper_01"; //BitMap a ser usado input char user11 = 60; //Transparencia (0 a 100) input C_WallPaper::eTypeImage user12 = C_WallPaper::IMAGEM; //Tipo de imagem de fundo //+------------------------------------------------------------------+ C_Terminal Terminal; C_WallPaper WallPaper; //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "WallPaper"); Terminal.Init(); WallPaper.Init(user10, user12, user11); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); WallPaper.Resize(); break; } ChartRedraw(); } //+------------------------------------------------------------------+
Vean que fue algo súper natural y directo, sin ningún tipo de dolor o resentimiento, el código fue removido del EA y transformado en un indicador que puede o no ser agregado al gráfico. Y cualquier cambio, ya sea en el fondo de pantalla, o el nivel de transparencia o incluso la remoción del mismo del gráfico no tendrá ningún efecto en lo que el EA estará haciendo.
Pues bien, ahora vamos a empezar a quitar las cosas que realmente provocan alguna caída en el rendimiento del EA, ya que funcionan de vez en cuando o en todos y cada uno de los movimientos del precio, y por ello en algunos momentos pueden obligar al EA a disponer de un tiempo que sería vital para que se redujera, lo que le impide hacer su verdadera función, que es la de observar lo que está pasando con las órdenes o posiciones en el gráfico.
2.0.2 - Conversión del Volume At Price en un indicador
Aunque no lo parezca, el sistema Volume At Price emplea un tiempo que en varias ocasiones es primordial para el EA, y me refiero a momentos de gran volatilidad, donde los precios oscilan salvajemente sin mucha dirección. Es precisamente en esos momentos en los que el EA necesita cada ciclo de máquina disponible para realizar su tarea, y ustedes no querrán perder una buena oportunidad porque algún indicador decida adueñarse de la tajada, así que vamos a quitarlo del EA y convertirlo en un verdadero indicador, para ello tendremos que crear el código que aparece a continuación:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh> //+------------------------------------------------------------------+ input color user0 = clrBlack; //Cor das barras input char user1 = 20; //Transparencia (0 a 100 ) input color user2 = clrForestGreen; //Agressão Compra input color user3 = clrFireBrick; //Agressão Venda //+------------------------------------------------------------------+ C_Terminal Terminal; C_VolumeAtPrice VolumeAtPrice; //+------------------------------------------------------------------+ int OnInit() { Terminal.Init(); VolumeAtPrice.Init(user2, user3, user0, user1); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnTimer() { VolumeAtPrice.Update(); } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { VolumeAtPrice.DispatchMessage(id, sparam); ChartRedraw(); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+
Estas fueron la parte fácil, todo lo que tuvimos que hacer fue tomar el código del EA y ponerlo en un indicador, y si quieren devolver el código al EA sólo tienen que copiar el código del indicador y ponerlo de nuevo en el EA.
Vean que empezamos con algo sencillo, complicamos un poco la cosa, pero ahora la cosa se complica aún vamos a retirar el Times & Trade de dentro del EA.
2.0.3 - Conversión de Times & Trade en un indicador
Esto realmente va a ser bastante complicado de hacer, si el objetivo es tener un código que pueda ser utilizado tanto en el EA como dentro de un indicador. Esto, al ser un indicador que trabaja en una subventana, puede parecer que convertirlo en un indicador sería sencillo, pero no lo será exactamente porque trabaja en una subventana. El principal problema es que, si simplemente se hace como en los casos anteriores, se tendrá el siguiente resultado en la ventana del indicador:
No es conveniente tener este tipo de cosas en la ventana del indicador, ya que confundirá al operador si quiere quitar el indicador de la pantalla. Por eso tenemos que tomar otro camino. Y al final de este camino que para algunos parecerá tortuoso, pero que en realidad es un simple conjunto de directivas y un poco de edición, tendremos el siguiente resultado en la ventana de indicadores.
Esto es lo que un operador espera encontrar, y no ese desorden de la imagen anterior.
Pero veamos cómo es el código del indicador Times & Trade, puede verse a continuación en su totalidad:
#property copyright "Daniel Jose" #property version "1.00" #property indicator_separate_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh> //+------------------------------------------------------------------+ C_Terminal Terminal; C_TimesAndTrade TimesAndTrade; //+------------------------------------------------------------------+ input int user1 = 2; //Escala //+------------------------------------------------------------------+ bool isConnecting = false; int SubWin; //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "Times & Trade"); SubWin = ChartWindowFind(); Terminal.Init(); TimesAndTrade.Init(user1); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { if (isConnecting) TimesAndTrade.Update(); return rates_total; } //+------------------------------------------------------------------+ void OnTimer() { if (TimesAndTrade.Connect()) { isConnecting = true; EventKillTimer(); } } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_CHART_CHANGE: Terminal.Resize(); TimesAndTrade.Resize(); break; } } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+
Entonces uno piensa, pero el código es exactamente igual al código que se estaba utilizando en el EA, excepto por el hecho de que tenemos una línea que está resaltada, que no es parte del código del EA, entonces ¡¿dónde está la trampa?! ¡¿O no hay trampa?! En realidad, tiene una trampa, el código no es exactamente igual, tiene una diferencia, y la diferencia no está en el código ni del indicador ni del EA, sino en el código de la clase. Pero antes de ver la diferencia, pensemos en lo siguiente: ¡¿Cómo se le indica al compilador lo que se debe compilar o no?! ¡¿O cuando uno programa algo, no se preocupa por esto, simplemente crea el código y cuando algo no es de su agrado simplemente borra las líneas?!
Bueno, los programadores experimentados tienen una regla: Sólo se elimina algo cuando definitivamente no funciona, de lo contrario mantenemos los fragmentos aunque no estén realmente compilados. Pero, ¡¿cómo hago esto en un código lineal, donde las funciones se están escribiendo y quiero que siempre funcionen?! Entonces viene la primera pregunta: ¡¿Sabes cómo decirle al compilador lo que debe o no debe ser compilado?! Si la respuesta es no, no pasa nada, yo también cuando empecé no sabía hacer esto, pero es algo que ayuda mucho. Así que vamos a aprender a hacerlo.
Algunos lenguajes tienen una cosa llamada directivas de compilación, estas pueden recibir el nombre de preprocesador, dependiendo del autor, pero la idea es la misma, decirle al compilador lo que debe o no debe ser compilado y cómo debe hacerse la compilación. Hasta ahí creo que es fácil de entender, pero hay un tipo de directivas muy específicas que nos ayudan a aislar el código de forma intencional para que podamos probar cosas específicas, estas son las directivas de compilación condicional, las mismas si son bien utilizadas nos permiten hacer algo muy curioso, compilar el mismo código de diferentes formas, y es justamente esto lo que se hace aquí en el ejemplo del Times & Trade, yo elijo quien será el responsable de generar la compilación condicional, ¡¿será el EA o será el Indicador?! Una vez definido esto creo una directiva de definición #define y luego usando la directiva condicional #ifdef #else #endif informo al compilador como se compilará el código.
Puede que haya sido complicado de entender, pero veamos en la práctica cómo funciona realmente.
En el código del EA definiremos y añadiremos las líneas resaltadas a continuación:
#define def_INTEGRATION_WITH_EA //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh> #ifdef def_INTEGRATION_WITH_EA #include <NanoEA-SIMD\Auxiliar\C_Wallpaper.mqh> #include <NanoEA-SIMD\Tape Reading\C_VolumeAtPrice.mqh> #include <NanoEA-SIMD\Tape Reading\C_TimesAndTrade.mqh> #endif //+------------------------------------------------------------------+
Lo que ocurre es lo siguiente: Si quiero compilar el EA con las clases que están en los archivos MQH, dejo la directiva #define def_INTEGRATION_WIT_EA definida en el EA, esto hará que el EA tenga todas las clases que estamos tomando y poniendo en indicadores, pero que pasa si quiero quitar estos indicadores de una vez, no necesito salir borrando códigos y líneas, lo único que tengo que hacer es transformar la definición en una no definición, esto se puede hacer simplemente convirtiendo la línea en la que se declara la directiva en una línea de comentario, así de simple, ya que el compilador no encontrará la directiva, se dará como no existente, y como no existe, cada vez que se encuentre una directiva condicional #ifdef def_INTEGRATION_WITH_EA será totalmente ignorada y el código entre ella y la parte #endif que se puede ver en el ejemplo anterior no se compilará.
Esta es la idea, así que hacemos esto en la clase C_TimesAndTrade y vemos como queda la nueva clase, les mostraré solo un punto para despertar su curiosidad:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include <NanoEA-SIMD\Auxiliar\C_Canvas.mqh> #ifdef def_INTEGRATION_WITH_EA #include <NanoEA-SIMD\Auxiliar\C_FnSubWin.mqh> class C_TimesAndTrade : private C_FnSubWin #else class C_TimesAndTrade #endif { //+------------------------------------------------------------------+ #define def_SizeBuff 2048 #define macro_Limits(A) (A & 0xFF) #define def_MaxInfos 257 #define def_ObjectName "TimesAndTrade" //+------------------------------------------------------------------+ private : string m_szCustomSymbol; // ... Restante do código da classe .... }
Vean que aquí tenemos un código muy raro para los que no usan directivas de compilación, la directiva def_INTEGRATION_WITH_EA está siendo declarada en EA, no olviden esto, lo que pasa es lo siguiente, cuando el compilador va a generar el código objeto de este archivo, va a tomar la siguiente actitud: Si el archivo que se está compilando es el EA, y tiene la directiva declarada, el compilador generará el código objeto con las partes que se encuentran entre las directivas condicionales #ifdef def_INTEGRATION_WITH_EA y la #else, normalmente utilizamos la directiva #else en estos casos, y en caso de que lo que se esté compilando sea otro archivo, por ejemplo un puntero que no tenga definida la directiva def_INTEGRATION_WITH_EA se compilará lo que esté entre las directivas #else y #endif, así funciona.
Vean todo el código de la clase C_TimesAndTrade para entender cada una de estas pruebas y el funcionamiento general, cuando estén compilando el EA o cuando estén compilando el Indicador, de esta manera el propio compilador de MQL5 hará los ajustes por nosotros, ahorrándonos tiempo y esfuerzo de tener que mantener dos archivos distintos.
2.0.4 - Agilización del EA
Una cuestión interesante es el hecho de que he dicho que el EA sólo debe trabajar con el sistema de órdenes, pero hasta el momento, el EA estaba recibiendo cosas que ahora se convirtieron en indicadores. La razón de esto es algo muy personal, tiene que ver con cosas involucradas en los cálculos que el EA debe hacer, pero este sistema de cálculo fue modificado, siendo pasado a otro método, de esta manera pude notar que el sistema de órdenes estaba siendo dañado por algunas cosas que el EA estaba haciendo en lugar de cuidar las órdenes, y el peor de los puntos estaba en el evento OnTick:
void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, TradeView.SecureChannelPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); #ifdef def_INTEGRATION_WITH_EA TimesAndTrade.Update(); #endif }
Ahora este evento ha recibido esta directiva condicional, por si acaso alguien que no esté operando en momentos de alta volatilidad pueda tener el EA, si lo desea con todos los indicadores originales. Pero antes de que piense que esto sería una buena idea, permítame recordarle cómo funciona la rutina de actualización de Times & Trade.
inline void Update(void) { MqlTick Tick[]; MqlRates Rates[def_SizeBuff]; int i0, p1, p2 = 0; int iflag; long lg1; static int nSwap = 0; static long lTime = 0; if (m_ConnectionStatus < 3) return; if ((i0 = CopyTicks(Terminal.GetFullSymbol(), Tick, COPY_TICKS_ALL, m_MemTickTime, def_SizeBuff)) > 0) { // ... Restante do código ... } }
El código anterior es un fragmento de la rutina de actualización presente en la clase C_TimesAndTrade, la parte resaltada es el problema. Cada vez que se ejecuta, se envía una petición al servidor para que devuelva todos los tickets de operaciones ocurridos a partir de un determinado momento, esto no es tan problemático, por cierto, el problema es que de vez en cuando esta misma llamada coincidirá con otros 2 eventos.
El primer y más claro evento es una gran cantidad de operaciones que pueden estar ocurriendo y que hace que la rutina OnTick reciba una gran cantidad de llamadas, y esta rutina además de tener que ejecutar el código anterior presente en la clase C_TimesAndTrade tendrá que lidiar con otro problema, la llamada a la función SecureChannelPosition, presente en la clase C_IndicatorTradeView, es decir, tenemos un pequeño problema aquí, pero eso no es todo, dije que de vez en cuando aunque la volatilidad sea baja, tendremos la coincidencia de dos eventos, el primero fue este.
El segundo evento está en el evento OnTime, pero ahora se actualizó al formato de abajo:
#ifdef def_INTEGRATION_WITH_EA void OnTimer() { VolumeAtPrice.Update(); TimesAndTrade.Connect(); } #endif
Pero si vas a utilizar el EA tal y como fue diseñado, y además con el agravante de que ha recibido aún más código, puede tener problemas en algunos momentos, por esta coincidencia, y cuando ocurra, el EA estará, aunque sea por un solo segundo, haciendo cosas que no tienen que ver con el sistema de órdenes.
A diferencia de la rutina que se encuentra en C_TimesAndTrade, está presente en la clase C_VolumeAtPrice puede hacer un verdadero daño en el rendimiento del EA a la hora de atender las órdenes. Analicemos por qué ocurre esto:
inline virtual void Update(void) { MqlTick Tick[]; int i1, p1; if (macroCheckUsing == false) return; if ((i1 = CopyTicksRange(Terminal.GetSymbol(), Tick, COPY_TICKS_TRADE, m_Infos.memTimeTick)) > 0) { if (m_Infos.CountInfos == 0) { macroSetInteger(OBJPROP_TIME, m_Infos.StartTime = macroRemoveSec(Tick[0].time)); m_Infos.FirstPrice = Tick[0].last; } for (p1 = 0; (p1 < i1) && (Tick[p1].time_msc == m_Infos.memTimeTick); p1++); for (int c0 = p1; c0 < i1; c0++) SetMatrix(Tick[c0]); if (p1 == i1) return; m_Infos.memTimeTick = Tick[i1 - 1].time_msc; m_Infos.CurrentTime = macroRemoveSec(Tick[i1 - 1].time); Redraw(); }; };
La razón son los códigos resaltados, pero el peor de ellos es REDRAW, este realmente hace un gran daño en el rendimiento del EA, y la razón de esto es que, en cada tick de volumen recibido POR ENCIMA de un valor dado, todo el volumen en el precio es sacado de la pantalla, recalculado y reposicionado de nuevo, aunque esto sucede cada 1 segundo, aproximadamente. Puede coincidir con otras cosas, por eso el EA tenía todos los indicadores eliminados, aunque los he dejado para que puedan usarlos directamente en el EA, les desaconsejo que lo hagan, por las razones que he explicado hasta ahora.
Estos cambios eran necesarios, pero hay otro que por increíble que parezca es más emblemático y que de hecho hay que hacer, y el cambio esta vez es para el evento OnTradeTransaction, el hecho de que esté usando este evento en sí es un intento de dejar el sistema lo más ágil posible, muchos que programan los EA de ejecución de órdenes usan el evento OnTrade y ahí hacen pruebas de qué órdenes están o no en el servidor, o qué posiciones siguen abiertas. No digo que estén equivocados en hacer esto, simplemente no es muy eficiente, ya que el servidor nos informa de lo que está pasando, pero el gran problema de usar el evento OnTrade es el hecho de tener que seguir probando cosas innecesariamente, ya que si usamos el evento OnTradeTransaction tendremos un sistema al menos más eficiente en cuanto a análisis de movimientos, pero este no es el objetivo aquí, cada uno usa el método que mejor se ajusta a su criterio.
Pero el hecho de que, en el desarrollo de este EA, haya optado por no utilizar ninguna estructura para almacenar y así no limitar el número de órdenes o posiciones que se puedan trabajar complica las cosas de manera que se hace necesaria una alternativa al evento OnTrade, esto se consigue utilizando el evento OnTradeTransaction.
Este evento OnTradeTransaction es muy complicado de implementar, tal vez por eso muchos no lo usan, pero no tenía otra opción, o funciona o funciona, sino se complicaría todo, pero en la versión anterior el código de este evento era muy ineficiente, y se puede ver a continuación:
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { #define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY)) ulong ticket; if (trans.symbol == Terminal.GetSymbol()) switch (trans.type) { case TRADE_TRANSACTION_DEAL_ADD: case TRADE_TRANSACTION_ORDER_ADD: ticket = trans.order; ticket = (ticket == 0 ? trans.position : ticket); TradeView.IndicatorInfosAdd(ticket); TradeView.UpdateInfosIndicators(0, ticket, trans.price, trans.price_tp, trans.price_sl, trans.volume, (trans.position > 0 ? trans.deal_type == DEAL_TYPE_BUY : def_IsBuy(trans.order_type))); break; case TRADE_TRANSACTION_ORDER_DELETE: if (trans.order != trans.position) TradeView.RemoveIndicator(trans.order); else TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY); if (!PositionSelectByTicket(trans.position)) TradeView.RemoveIndicator(trans.position); break; case TRADE_TRANSACTION_ORDER_UPDATE: TradeView.UpdateInfosIndicators(0, trans.order, trans.price, trans.price_tp, trans.price_sl, trans.volume, def_IsBuy(trans.order_type)); break; case TRADE_TRANSACTION_POSITION: TradeView.UpdateInfosIndicators(0, trans.position, trans.price, trans.price_tp, trans.price_sl, trans.volume, trans.deal_type == DEAL_TYPE_BUY); break; } #undef def_IsBuy }
Aunque el código de arriba funciona, es PESSIMO, esto por decir algo, la cantidad de llamadas inútiles que genera este código de arriba llega a rozar la locura, nada haría mejorar el EA en cuanto a estabilidad y robustez, si esto de arriba no se puede arreglar.
Debido a esto realicé varias actividades en la cuenta DEMO para tratar de encontrar un patrón en los mensajes, y es bastante complicado encontrar un patrón, pero no encontré un patrón, sino algo para evitar la locura de llamadas inútiles que se generaba, dejando el código estable, robusto y al mismo tiempo lo suficientemente ágil para poder operar en cualquier momento del mercado. Es cierto que todavía hay algunos errores menores que hay que arreglar, pero el código es muy bueno, y pueden verlo a continuación:
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { #define def_IsBuy(A) ((A == ORDER_TYPE_BUY_LIMIT) || (A == ORDER_TYPE_BUY_STOP) || (A == ORDER_TYPE_BUY_STOP_LIMIT) || (A == ORDER_TYPE_BUY)) if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position); if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action) { case TRADE_ACTION_PENDING: TradeView.IndicatorAdd(request.order); break; case TRADE_ACTION_SLTP: TradeView.UpdateIndicators(request.position, request.tp, request.sl, request.volume, def_IsBuy(request.type)); break; case TRADE_ACTION_DEAL: TradeView.RemoveIndicator(request.position); break; case TRADE_ACTION_REMOVE: TradeView.RemoveIndicator(request.order); break; case TRADE_ACTION_MODIFY: TradeView.UpdateIndicators(request.order, request.tp, request.sl, request.volume, def_IsBuy(request.type)); break; } #undef def_IsBuy }
No intenten entender en un primer momento lo que está pasando, simplemente admiren la belleza de esta rutina de arriba, es casi la perfección en persona. Y voy a explicar por qué lo digo, no porque yo haya hecho esta codificación, sino por el alto grado de robustez y agilidad que tiene.
Aunque parezca complicado este código tiene dos pruebas, y están resaltadas a continuación para poder explicar mejor lo que ocurre.
if (trans.type == TRADE_TRANSACTION_HISTORY_ADD) if (trans.symbol == Terminal.GetSymbol()) TradeView.RemoveIndicator(trans.position); if (trans.type == TRADE_TRANSACTION_REQUEST) if ((request.symbol == Terminal.GetSymbol()) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action) { //... código interno ... }
La línea resaltada en VERDE verificará, cada vez que ocurra una transacción en el historial, si el activo informado es el mismo observado por el EA, si eso es cierto, se enviará una orden a la clase C_IndicatorTradeView para eliminar el indicador del gráfico, pero esta transacción puede ocurrir en 2 momentos, cuando una orden se convierte en una posición o cuando se cierra una posición, recordando que sólo uso el modo NETTING y no el HEDGING. Así que, pase lo que pase, el indicador será eliminado del gráfico.
Pero se preguntarán: en el caso de que se cierre la posición, todo bien, pero en el caso de que la orden se convierta en posición, ¿me quedaré indefenso? No, sin embargo este problema no se resuelve aquí, dentro del EA, este problema se resuelve dentro de la clase C_IndicatorTradeView y se explicará mejor en el siguiente tema de este artículo.
La línea roja reduce absurdamente la cantidad de mensajes inútiles que se pasaron a la clase C_IndicatorTradeView. Esto se hace comprobando cuál fue la respuesta que el servidor devolvió a nuestra petición, por lo que es necesario tener una confirmación planteando nuestra petición junto con el mismo nombre del activo que el EA está vigilando, sólo entonces se enviará una nueva ronda de llamadas a la clase C_IndicatorTradeView.
Pues esto es todo lo que se puede decir de este sistema. Pero la historia aún no ha terminado... tenemos mucho trabajo por delante, y a partir de ahora nos centraremos única y exclusivamente en la clase C_IndicatorTradeView, y empezaremos ahora con algunos cambios que hay que hacer.
2.0.5 - Reducción del número de objetos creados por la clase C_IndicatorTradeView
En el artículo Desarrollo de un EA comercial desde cero (Parte 23) introduje un concepto bastante abstracto, pero bastante interesante para los movimientos de órdenes o límites. El concepto era el uso de fantasmas o sombras de posiciones. Estos identificarían y mostrarían en el gráfico lo que el servidor de operaciones estaba viendo y se utilizarían hasta que el movimiento real se produjera. Ese modelo tiene un pequeño problema: añade objetos para que MetaTrader 5 se encargue de ellos, y los objetos añadidos no son realmente necesarios la gran mayoría de las veces, por lo que MetaTrader 5 se quedaba con la lista de objetos llena de cosas a menudo inútiles o raramente utilizadas.
De nuevo, no queremos que el EA siga creando objetos o tenga objetos innecesarios en la lista de objetos, ya que esto degrada el rendimiento del EA durante la gestión de las órdenes, y como utilizamos MetaTrader 5 para hacer esta gestión, no deberíamos tener objetos inútiles que perturben todo el sistema.
Pero hay una solución muy simple, en realidad no es tan simple, pero haremos NUEVAMENTE algunos cambios en la clase C_IndicatorTradeView para mejorar esto. Mantendremos los fantasmas o sombras en la pantalla, y utilizaremos un método muy curioso y poco utilizado por lo que he podido comprobar durante la fase de investigación.
Así que, prepárense, porque, la cosa va a ser muy divertida e interesante de aplicar.
Lo primero que haremos es cambiar la estructura de la selección, ahora tendrá el siguiente modelado:
struct st00 { eIndicatorTrade it; bool bIsBuy, bIsDayTrade; ulong ticket; double vol, pr, tp, sl; }m_Selection;
No les contaré lo que ha cambiado, dejaré que intenten entenderlo, pero el cambio ha aportado algunas ventajas en cuanto a la simplificación de algunos puntos de la lógica de codificación.
Por ello, ahora nuestro indicador fantasma tendrá su propio índice:
#define def_IndicatorGhost 2
Y esto ha hecho que el modelado de los nombres también cambie al siguiente código:
#define macroMountName(ticket, it, ev) StringFormat("%s%c%llu%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,\ ticket, def_SeparatorInfo, \ (char)it, def_SeparatorInfo, \ (char)(ticket <= def_IndicatorGhost ? ev + 32 : ev))
Parece poco, pero esto hará mucha diferencia pronto, enseguida... sigamos...
Ahora las macros de posición de precios son siempre directas, ya no hay esa cosa duplicada, por lo que su código es ahora como se muestra a continuación:
#define macroSetLinePrice(ticket, it, price) ObjectSetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE, price) #define macroGetLinePrice(ticket, it) ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, it, EV_LINE), OBJPROP_PRICE)
Estos cambios nos obligan a crear otras 2 rutinas, ahora mostraré una y luego mostraré la otra, la primera es el reemplazo de la rutina que crea los indicadores propiamente dichos, fue literalmente más clara en cuanto a lo que realmente diferencia a un indicador de otro, y se puede ver a continuación:
#define macroCreateIndicator(A, B, C, D) { \ m_TradeLine.Create(ticket, sz0 = macroMountName(ticket, A, EV_LINE), C); \ m_BackGround.Create(ticket, sz0 = macroMountName(ticket, A, EV_GROUND), B); \ m_BackGround.Size(sz0, (A == IT_RESULT ? 84 : 92), (A == IT_RESULT ? 34 : 22)); \ m_EditInfo1.Create(ticket, sz0 = macroMountName(ticket, A, EV_EDIT), D, 0.0); \ m_EditInfo1.Size(sz0, 60, 14); \ if (A != IT_RESULT) { \ m_BtnMove.Create(ticket, sz0 = macroMountName(ticket, A, EV_MOVE), "Wingdings", "u", 17, C); \ m_BtnMove.Size(sz0, 21, 23); \ }else { \ m_EditInfo2.Create(ticket, sz0 = macroMountName(ticket, A, EV_PROFIT), clrNONE, 0.0); \ m_EditInfo2.Size(sz0, 60, 14); } \ } void CreateIndicator(ulong ticket, eIndicatorTrade it) { string sz0; switch (it) { case IT_TAKE : macroCreateIndicator(it, clrForestGreen, clrDarkGreen, clrNONE); break; case IT_STOP : macroCreateIndicator(it, clrFireBrick, clrMaroon, clrNONE); break; case IT_PENDING : macroCreateIndicator(it, clrCornflowerBlue, clrDarkGoldenrod, def_ColorVolumeEdit); break; case IT_RESULT : macroCreateIndicator(it, clrDarkBlue, clrDarkBlue, def_ColorVolumeResult); break; } m_BtnClose.Create(ticket, macroMountName(ticket, it, EV_CLOSE), def_BtnClose); } #undef macroCreateIndicator
Ustedes habrán notado que me encanta usar directivas de preprocesamiento en mi código. Es casi una adicción por mi parte, no puedo prescindir de ello. Pero ya veis que ahora es bastante sencillo entender la diferencia entre un indicador y otro, no es algo que tengan que estar ahí matándose para entender cómo dejar un indicador con los colores que quieren, sólo hay que cambiarlos aquí, y como todos son casi un clon unos de otros, usando una macro puedo hacer la cosa para que todos funcionen igual, y tengan seguro los mismos elementos. Esto es la reutilización del código llevada al extremo.
Existe otra rutina con un nombre muy parecido a esta, pero eso hace otra cosa, aunque para el final entraré en detalles sobre ella.
La rutina IndicatorAdd fue modificada, eliminándose de ella los fragmentos cortados...
inline void IndicatorAdd(ulong ticket) { char ret; if (ticket == def_IndicatorTicket0) ret = -1; else { if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return; if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return; if ((ret = GetInfosTradeServer(ticket)) == 0) return; } switch (ret) { case 1: CreateIndicatorTrade(ticket, IT_RESULT); PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr); break; case -1: CreateIndicatorTrade(ticket, IT_PENDING); PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr); break; } ChartRedraw(); UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy); UpdateIndicators(ticket, m_Selection.tp, m_Selection.sl, m_Selection.vol, m_Selection.bIsBuy); }
Y uno de los fragmentos cortados ha sido sustituido por el fragmento resaltado. Pero un momento, ¿¡ya no se crean los indicadores de órdenes pendientes ni el indicador 0!? Tranquilos, se siguen creando, pero no en esta rutina, sino en otro lugar, y el motivo es otra rutina que tenía que surgir, tomémoslo con calma...
La siguiente rutina es exactamente la que creará los indicadores de órdenes pendientes y el indicador 0, estamos hablando de la rutina UpdateIndicators, que tiene su código visto justo debajo:
#define macroUpdate(A, B) { if (B > 0) { \ if (b0 = (macroGetLinePrice(ticket, A) == 0 ? true : b0)) CreateIndicator(ticket, A); \ PositionAxlePrice(ticket, A, B); \ SetTextValue(ticket, A, vol, (isBuy ? B - pr : pr - B)); \ } else RemoveIndicator(ticket, A); } void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy) { double pr; bool b0 = false; if (ticket == def_IndicatorGhost) pr = m_Selection.pr; else { pr = macroGetLinePrice(ticket, IT_RESULT); if ((pr == 0) && (macroGetLinePrice(ticket, IT_PENDING) == 0)) { CreateIndicator(ticket, IT_PENDING); PositionAxlePrice(ticket, IT_PENDING, m_Selection.pr); ChartRedraw(); } pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING)); SetTextValue(ticket, IT_PENDING, vol); } if (m_Selection.tp > 0) macroUpdate(IT_TAKE, tp); if (m_Selection.sl > 0) macroUpdate(IT_STOP, sl); if (b0) ChartRedraw(); } #undef macroUpdate
Fíjense que en esta rutina tenemos una prueba muy curiosa, que está resaltada en el código, esta prueba ayudará a crear los indicadores fantasmas, por eso la rutina IndicatorAdd ya no es capaz de crear los indicadores de órdenes pendientes y el indicador 0. Pero sólo con hacer esta prueba no es suficiente para crear un indicador fantasma, tenemos más cosas implicadas.
Ahora hay algunos detalles involucrados en la rutina DispatchMessage, son pequeños cambios, pero que hacen nuestra vida mucho más fácil, mostraré sólo las partes donde estos cambios realmente ocurrieron, así que observen el código a continuación:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { // ... Código .... switch (id) { case CHARTEVENT_MOUSE_MOVE: // ... Código .... }else if ((!bMounting) && (bKeyBuy == bKeySell) && (m_Selection.ticket > def_IndicatorGhost)) { if (bEClick) SetPriceSelection(price); else MoveSelection(price); } break; // ... Código ... case CHARTEVENT_OBJECT_CLICK: if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev) { case EV_CLOSE: // ... Código ... break; case EV_MOVE: CreateGhostIndicator(ticket, it); break; } break; } }
Fíjense que en CHARTEVENT_MOUSE_MOVE hay un fragmento que ha cambiado, este fragmento probará y comprobará si estamos trabajando con un fantasma, si es un fantasma se mantendrá bloqueado, pero si es algo diferente a esto, se moverá, siempre que por supuesto el indicador tenga esta capacidad de poder moverse.
Pero también hay que tener en cuenta que en cuanto hagamos clic indicando al sistema una nueva posición del indicador, el fantasma con todos sus componentes será eliminado de la lista de objetos. Hasta ahí creo que es bastante sencillo de entender, pero fíjense ahora en otro punto, y este está resaltado, una llamada a la función CreateGhostndicator, pero este código no lo he explicado todavía, es un tema para el siguiente tema de este artículo.
2.0.6 - Entendamos cómo funciona la función CreateGhostIndicator
La función CreateGhostIndicator es una función muy extraña a primera vista, veamos su código a continuación:
CreateGhostIndicator
#define macroSwapName(A, B) ObjectSetString(Terminal.Get_ID(), macroMountName(ticket, A, B), OBJPROP_NAME, macroMountName(def_IndicatorGhost, A, B)); void CreateGhostIndicator(ulong ticket, eIndicatorTrade it) { if (GetInfosTradeServer(m_Selection.ticket = ticket) != 0) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); macroSwapName(it, EV_LINE); macroSwapName(it, EV_GROUND); macroSwapName(it, EV_MOVE); macroSwapName(it, EV_EDIT); macroSwapName(it, EV_CLOSE); m_TradeLine.SetColor(macroMountName(def_IndicatorGhost, it, EV_LINE), def_IndicatorGhostColor); m_BackGround.SetColor(macroMountName(def_IndicatorGhost, it, EV_GROUND), def_IndicatorGhostColor); m_BtnMove.SetColor(macroMountName(def_IndicatorGhost, it, EV_MOVE), def_IndicatorGhostColor); ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE)); m_TradeLine.SpotLight(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); m_Selection.it = it; }else m_Selection.ticket = 0; } #undef macroSwapName
Tengamos en cuenta que no se crea nada en esta función, sin embargo, si se compila y ejecuta el EA, se producirá la creación de fantasmas en los indicadores, que muestran el estado de la orden en el servidor. Para entenderlo, veamos el siguiente vídeo, que es una demostración del sistema en funcionamiento real.
Como he dicho son fantasmas, de hecho, no verán que se crean, no tiene sentido intentar leer el código tratando de encontrar una línea que diga: " AQUÍ... encontré... en este punto es donde se crean los indicadores fantasmas... ", la verdad es que simplemente ya están en el gráfico, pero no aparecen por ningún lado, hasta que se llega a manipular una orden o posición, sólo en ese momento se hacen visibles. Pero, ¡¿cómo he conseguido esta hazaña?!
Para entender es necesario comprender el flujo de ejecución de EA, así que vamos a entender desde el principio.
Cuando se inicializa el EA tenemos el siguiente flujo de ejecución que se ve en la figura siguiente:
Flujo 1
<<<< Flujo de inicialización del sistema
La región naranja es parte del EA, mientras que la región verde es parte de la clase C_IndicatorTradeView. Noten cómo suceden las cosas hasta que el indicador es realmente creado para luego ser presentado en la pantalla, las flechas en negro son un camino común tanto de las órdenes pendientes como de las posiciones abiertas, la flecha en azul es el camino que hacen las posiciones, y las flechas en morado es el camino que toman las órdenes pendientes para que sus indicadores sean creados. Por supuesto que dentro de las funciones tenemos cosas que dirigen el flujo en una u otra dirección, pero el diagrama de flujo aquí tiene la intención de mostrar cómo funcionan las cosas a grandes rasgos, para que, si quieren, puedan entender el sistema hasta el punto de adaptarlo a otras cuestiones o a su estilo de operar.
Pues bien, el diagrama de flujo anterior se utiliza sólo una vez, y esto es sólo durante el inicio del sistema. Ahora cada vez que vayamos a colocar una orden pendiente en el gráfico tendremos dos flujos de ejecución distintos, el primero se encarga de crear el indicador 0 y el intento de colocar una orden en el gráfico, este primer paso del flujo de ejecución se ve en la imagen inferior:
Flujo 2
![](https://c.mql5.com/2/45/004__5.png)
Es muy importante que ustedes se percaten de que no es la clase la que realmente creará el orden que aparece en el gráfico, ella sólo lo intentará. Si todo va bien, la función SetPriceSelection tendrá éxito en su ejecución y se producirá un nuevo flujo, este es el que presentará la orden en el gráfico. De esta forma tendremos el flujo que se ve a continuación, que de hecho posicionará la orden en la ubicación que el servidor comercial estará informando, por lo que no tiene sentido esperar que la orden esté de hecho en la ubicación que ustedes indicaron inicialmente, ya que si la volatilidad llega a hacer que el servidor ejecute su orden en otro punto, que no sea el que ustedes indicaron, el EA lo corregirá y presentará la orden en la ubicación correcta, y ustedes sólo deberán analizar si las condiciones son adecuadas a su modelo de operación.
Flujo 3
<<< Flujo de la colocación de orden pendiente
Esto de aquí es sólo la parte responsable de colocar la orden en el gráfico y estoy hablando de una orden completa, es decir, tendrá un punto de entrada, un take y un stop, pero cuál será el flujo en caso de que uno de los límites, ya sea el take o el stop, se eliminen de la orden, porque estos flujos de arriba no responden a esto. De hecho, el flujo será bastante diferente de estos aquí, pero los elementos casi se repetirán. Veamos a continuación cómo será el flujo en caso de que se pulse el botón de cierre en uno de los límites.
Bueno, este flujo es bastante extraño para alguien que quería ver algo lineal.
Flujo 4
<<< Flujo de la eliminación de una orden o de los límites de la misma
Vemos que tenemos 2 flujos, uno al lado del otro, el primero que se ejecuta es el de la flecha morada. Una vez que se ejecuta, una respuesta del servidor es capturada por el evento OnTradeTransaction, y este sí que desencadenará el sistema que retira el indicador de la pantalla. Sólo hay una diferencia entre el flujo de eliminar los límites al de cerrar una posición o cerrar una orden, en estos casos, la función SetPriceSelection no se ejecutará, pero el flujo del evento OnTradeTransaction permanecerá.
Todo esto es muy bonito y estupendo, pero sigue sin responder a cómo ocurre la aparición de los fantasmas, lo que es precisamente el tema aquí.
Resulta que para entender cómo se crean de hecho los fantasmas, hay que saber cómo sucede el flujo de ejecución, para ser más precisos hay que saber cómo el EA coloca una orden pendiente o cómo sucede en la práctica el flujo de creación del indicador 0, y este flujo se muestra en esta imagen, si entienden los flujos de ejecución anteriores, entender los fantasmas les será mucho más sencillo.
Basta ya de dar rodeos y veamos cómo se crean los fantasmas. Mira de nuevo la rutina CreateGhostIndicator, puedes ver que no crea nada, sólo manipula algunos datos, pero ¿por qué hace esto? La razón es que si se intenta crear algún objeto, este se superpondrá a los objetos existentes, mostrándose delante de ellos. De este modo, los objetos que no queremos ocultar quedarán ocultos. Hay dos soluciones a este problema, la primera es crear un conjunto que sea inferior a todos los demás, este conjunto se crearía primero que cualquier otro objeto que representara las órdenes, pero esto tiene un problema, tendríamos un montón de objetos inútiles en la ventana que contiene las listas de objetos, y no queremos esto, fíjense que todo el código se está modificando sólo para evitar esto. Así que veamos una segunda solución, crear el fantasma y luego eliminar el puntero que será manipulado y luego crearlo de nuevo. Pues ninguna de las dos soluciones es muy práctica, de hecho ambas son bastante caras.
Pero observando la documentación, me encontré con un dato que me llamó la atención, la función ObjectSetString permite manipular una propiedad de los objetos que a simple vista no tiene el más mínimo sentido, la propiedad en cuestión es OBJPROP_NAME. Me intrigó esto, ¡¿por qué se permite esto?! No tiene sentido, una vez creado el objeto, ¡¿qué sentido tendría cambiar su nombre?!
Pues bien, esta es la cuestión, cuando renombramos un objeto, el objeto antiguo dejará de existir, y tendrá un nuevo nombre, una vez renombrado el objeto tomará la posición del objeto original, de esta forma el EA podrá crear el objeto original sin problemas, y el fantasma podrá aparecer y desaparecer sin efectos secundarios para el gráfico, y sin dejar rastros de su paso. El único objeto que tendré que eliminar será el botón para cerrar el indicador, y esto se hace en esta línea:
ObjectDelete(Terminal.Get_ID(), macroMountName(def_IndicatorGhost, it, EV_CLOSE));
Pero aquí tenemos un pequeño detalle extra, mirando la documentación de la función ObjectSetString vemos una advertencia sobre su funcionamiento:
Cuando se renombra un objeto, se forman dos eventos simultáneamente. Estos eventos pueden ser manipulados en el Expert Advisor o en el indicador mediante la función OnChartEvent():
- un evento de eliminación de un objeto con el nombre antiguo;
- un evento de creación de un objeto con un nuevo nombre.
Es importante tener en cuenta esto, porque no queremos que el objeto que vamos a renombrar aparezca sin más, sin estar preparados para ello. De este modo, añadimos una cosa más antes y después del cambio de nombre:
ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); // ... Código seguro ... ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
Cualquier cosa que esté dentro del código seguramente no activará los eventos de creación y eliminación de objetos. Ahora tenemos un código completo donde aparecerán los fantasmas y podemos tener un comportamiento adecuado.
Pero quizás todavía no hayan entendido cómo en realidad este código está creando los fantasmas, cuando en realidad sólo estamos renombrando el indicador que queremos manipular. Les dejo esa inquietud, pero para hacerles la vida un poco más fácil, les mostraré cómo es el flujo de ejecución del fantasma, y esto se muestra en la imagen de abajo:
Flujo 5
<<<< Flujo de ejecución del fantasma
Fíjense en que es un clon casi perfecto del flujo 2, así que diviértanse entendiendo cómo se crean y eliminan los fantasmas sin que se cree realmente ningún código de creación.
3.0 - Conclusión
Este artículo fue bastante interesante e incluso emocionante de escribir. Es cierto que hubo que cambiar muchas cosas en el código del EA, pero créame, es para mejor, y a partir de ahora se tratará así. Hay algunas cosas más que hacer y medidas que tomar para hacerlo aún más robusto, pero por ahora los cambios realizados aquí ya supondrán un gran beneficio para todo el sistema. Me gustaría destacar que un programa bien desarrollado suele pasar por los pasos que se hicieron aquí: estudio de la documentación, análisis del flujo de ejecución, evaluación comparativa del sistema para ver si se está sobrecargando en los momentos críticos, y sobre todo, serenidad para no convertir el código en un auténtico monstruo a punto de volvernos locos, evitemos convertir nuestro código en un hijo de Frankenstein, porque esto no hace que el código sea mejor, sólo complica futuras mejoras y sobre todo correcciones.
Un fuerte abrazo a todos los que están siguiendo esta serie y hasta el próximo artículo, porque aún no hemos terminado, todavía quedan cosas por crear...
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/10606
![Desarrollo de un EA comercial desde cero (Parte 26): Rumbo al futuro (I)](https://c.mql5.com/2/48/development__2.png)
![Desarrollo de un EA comercial desde cero (Parte 24): Dotando de robustez al sistema (I)](https://c.mql5.com/2/48/development.png)
![Desarrollo de un EA comercial desde cero (Parte 27): Rumbo al futuro (II)](https://c.mql5.com/2/48/development__3.png)
![Trabajamos con matrices y vectores en MQL5](https://c.mql5.com/2/48/matrix_and_vectors_2.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