Asistente MQL5: Ampliación de la biblioteca estándar para el establecimiento de órdenes, stops y objetivos según precios calculados
Introducción
La Biblioteca estándar MQL5 constituye una ayuda excelente en el desarrollo de grandes proyectos, que normalmente exigen una arquitectura determinada. El Asistente MQL5 permite, usando el modo de diálogo, montar un proyecto de grandes dimensiones en sólo unos minutos, algo que, sin duda, no tiene precio. El Asistente automatiza el montaje en una sola unidad de las partes del asesor y anuncia de manera automática los parámetros de los módulos en el asesor, de acuerdo con sus descriptores. Cuando se trata de un gran número de módulos heterogéneos, semejante automatización ahorrará una enorme cantidad de tiempo y nos evitará operaciones muy rutinarias.
Y todo esto estaría bien, pero las posibilidades de los sistemas comerciales creados con la ayuda del asistente sobre la base de clases estándar está muy limitada. En este artículo se propondrá un método universal que permitirá ampliar significativamente las posibilidades de los asesores creados. Además, se conservará la compatibilidad del asistente con los módulos estándar.
La esencia del método es uso de los mecanismos de herencia y polimorfismo en la POO para la creación de clases, que sustiruirán a las clases estándar en el código de los asesores creados. De esta forma, conseguiremos utilizar todas las ventajas del asistente y de la biblioteca estándar, obteniendo un asesor con las posibilidades imprescindibles, sim embargo, tendremos que editar un poco el código (4 líneas, literalmente).
El objetivo práctico de esta publicación es añadir a los asesores creados las posibilidades de abrir órdenes, stop loss y take profit en los niveles de precio requeridos y no sólo a una distancia determinada del precio actual.
Podemos encontrar una tarea similar en el artículo "Wizard MQL5: Cómo enseñar a un Asesor Experto a abrir las órdenes pendientes de cualquier precio", pero la variante que proponen en él para resolver el problema tiene una desventaja significativa: el cambio "forzoso" de un parámetro del módulo de señales comerciales del filtro subordinado. Esta aproximación no posibilita adecuadamente el trabajo con multitud de módulos. Al usarlo, la utilización del asistente para la optimización del proceso de desarrollo pierde su sentido.
A continuación, estudiaremos con detalle la implementación de la apertura de órdenes, stops y takes de cualquier precio y clase, heredados de los estándar. Además, cualquier conflicto entre módulos queda excluido. Espero que este artículo sirva de ejemplo e inspire a los lectores a desarrollar sus propias mejoras del marco de trabajo estándar, y que además permita aplicar la ampliación de la biblioteca para sus propios fines a los usuarios que entiendan instrucciones no muy complejas.
1. Algoritmo estándar de toma de decisiones
Los expertos generados en el Wizard MQL5 se basan en un ejemplar de la clase CExpert. En esta clase se anuncia un indicador de objeto de la clase CExpertSignal. En lo sucesivo y para ser más breves, llamaremos a este objeto señal principal. La señal principal contiene índices de referencia a los filtros subordinados (módulos de señales son herederos de la clase CExpertSignal).
En el nievo tic, si no hay posiciones u órdenes abiertas, el experto se dirige a la señal principal para comprobar si es posible abrir una posición. La señal principal pregunta por turnos a todos los filtros subordinados, y en base a los pronósticos recibidos, calcula el pronóstico medido medio (dirección). Si su valor supera el umbral (el valor del parámetro m_threshold_open en la señal principal), al experto se le transmiten los parámetros de la orden y el resultado de la comprobación de las condiciones del tipo bool. Si se cumplen las condiciones, se abre la posición según el precio de mercado, o bien se abre la orden pendiente a una distancia determinada del mismo (fig. 1). Los stops igualmente se pueden encontrar sólo a una distancia fijada. La diferencia entre el precio de apertura, el stop loss y el take profit con respecto al precio de mercado se establece en los ajustes del asesor y se guardan dentro de la señal principal en las variables m_price_level, m_stop_level, m_take_level respectivamente.
Y bien, en estos momentos, para enviar órdenes se deben cumplir dos condiciones:
- Que no haya posiciones abiertas del instrumento actual;
- El valor absoluto del pronóstico medido medio debe superar el valor umbral (la tendencia es lo bastante fuerte).
Fig. 1. Esquema de toma de decisiones de entrada en el mercado
El esquema actual de toma de decisiones en la fig. 1 limita significativamente la zona de aplicación del Asistente MQL5. Las estrategias con un número fijado de swaps rara vez muestran ser efectivas en periodos largos, debido a la permanente volatilidad, y los sistemas basados en las órdenes pendientes, normalmente, exigen su apertura en niveles calculados dinámicamente.
2. Algoritmo modificado de toma de decisiones
Desde el punto de vista del lugar, los niveles y la ubicación en ellos de las órdenes, la situación resultante es un callejón sin salida, dado que los módulos de señal no saben generar nada, excepto valores de pronóstico, y la señal principal no está diseñada para trabajar con los niveles. Debido a esto, proponemos:
- Introducir un nuevo tipo de módulos de señal (los llamaremos módulos de precio) que sepan generar los parámetros de las órdenes;
- Enseñar a la señal principal a procesar estos parámetros: elegir los mejores y transmitirlos al experto.
El algoritmo modificado (fig. 2) permite trabajar no sólo con órdenes pendientes. Su esencia reside en que separa el punto de entrada (precio) de la determinación de la tendencia (pronóstico medido medio). Es decir, para la toma de decisiones, en primer lugar, se determina la dirección preferida de comercio con los filtros; en segundo lugar, se elige un conjunto de parámetros de orden de los módulos de precio que venga bien según la dirección. Si hay disponibles varios de estos conjuntos, se escogerá el que se obtenga del módulo con mayor peso (valor de parámetro m_weight). Si tenemos dirección, pero no se han destacado puntos de entrada accesibles en este momento, el asesor permanecerá inactivo.
Fig. 2. Esquema modificado de toma de decisiones de entrada en el mercado
Bien, para enviar una nueva orden se necesitará respetar las siguientes condiciones:
- Que no haya posiciones u órdenes abiertas del instrumento actual
- El valor absoluto del pronóstico medido medio debe superar el valor umbral
- Que se haya encontrado como mínimo un precio de apertura de orden.
El algoritmo en la fig. 2 permite, en un asesor, procesar multitud de posibles puntos de entrada, filtrarlos conforme a su dirección y elegir el mejor de ellos.
3. Desarrollo de las clases modificadas del experto y del módulo de señales
La ampliación de la biblioteca se basa en dos clases: CExpertSignalAdvanced y CExpertAdvanced, heredadas de CExpertSignal y CExpert, respectivamente.
Todas las medidas para añadir nuevas posibilidades están dirigidas al cambio de interfaces que sirven para el intercambio de datos entre diferentes bloques del asesor. Por ejemplo, para implementar el algoritmo según el esquema en la fig. 2 es necesario organizar la interacción entre la señal principal (de clase CExpertSignalAdvanced) y los módulos de precio (herederos de esta clase). A su vez, la actualización de las órdenes ya abiertas al cambiar de datos presupone la interacción del experto (de clase CExpertAdvanced) y la señal principal.
Bien, nuestra tarea es: para abrir las posiciones tenemos que realizar el esquema en la fig. 2 y organizar la actualización de las órdenes ya abiertas con cambio de parámetros (por ejemplo, con la aparición de un punto de entrada más preferible). Pasemos al estudio de la clase CExpertSignalAdvanced.
3.1. CExpertSignalAdvanced
Esta clase sustiruirá a su antepasada en el papel de señal principal y será la básica para los módulos de precio, exactamente igual que su antepasada es la básica para los módulos de señales.
class CExpertSignalAdvanced : public CExpertSignal { protected: //---miembros de los datos para guardar los parámetros de las órdenes abiertas double m_order_open_long; //precio de apertura de orden de compra double m_order_stop_long; //orden stop loss de compra double m_order_take_long; //orden take profit de compra datetime m_order_expiration_long; //tiempo de finalización de la orden de compra double m_order_open_short; //percio de apertura de orden de venta double m_order_stop_short; //orden stop loss de venta double m_order_take_short; //orden take profit de venta datetime m_order_expiration_short; //tiempo de finalización de la orden de venta //--- int m_price_module; //índice del primer módulo de precio en la matriz m_filters public: CExpertSignalAdvanced(); ~CExpertSignalAdvanced(); virtual void CalcPriceModuleIndex() {m_price_module=m_filters.Total();} virtual bool CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration); virtual bool CheckOpenShort(double &price,double &sl,double &tp,datetime &expiration); virtual double Direction(void); //cálculo del pronóstico medido medio según los datos de los módulos de señales virtual double Prices(void); //actualización de los parámetros de las órdenes abiertas según los datos de los módulos deprecio virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex); virtual bool CheckUpdateOrderShort(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex); double getOpenLong() { return m_order_open_long; } double getOpenShort() { return m_order_open_short; } double getStopLong() { return m_order_stop_long; } double getStopShort() { return m_order_stop_short; } double getTakeLong() { return m_order_take_long; } double getTakeShort() { return m_order_take_short; } datetime getExpLong() { return m_order_expiration_long; } datetime getExpShort() { return m_order_expiration_short; } double getWeight() { return m_weight; } };
En la clase CExpertSignalAdvanced se anuncian los miembros de los datos para el guardado de los parámetros de las órdenes. Los valores de estas variables se actualizan en el método Prices(). Estas variables juegan el papel de búffers.
Después se anuncia el parámetro m_price_module, que guarda el índice del primer módulo de precio en la matriz m_filters, anunciado en CExpertSignal. En esta matriz se encuentra el indicador de referencia de los módulos de señales conectados Al principio se guardan los indicadores de referencia de los módulos estándar (filtros), después, comenzando por el índice m_price_module, van los indicadores de referencia a los módulos de precio. Se decidió guardar todo en una matriz para no cambiar los métodos de inicialización de los indicadores y las series temporales. Tanto más que, en total, a través de una matriz se pueden conectar 64 módulos, y esto normalmente es más que de sobra.
Asímismo, en la clase CExpertSignalAdvanced se anuncian los métodos auxiliares para obtener los valores de los miembros protegidos de los datos. Sus nombres comienzan a partir de get (ver declaración de clase).
3.1.1. Constructor
El Constructor CExpertSignalAdvanced inicializa las variables anunciadas dentro de la clase:
CExpertSignalAdvanced::CExpertSignalAdvanced() { m_order_open_long=EMPTY_VALUE; m_order_stop_long=EMPTY_VALUE; m_order_take_long=EMPTY_VALUE; m_order_expiration_long=0; m_order_open_short=EMPTY_VALUE; m_order_stop_short=EMPTY_VALUE; m_order_take_short=EMPTY_VALUE; m_order_expiration_short=0; m_price_module=-1; }
3.1.2. CalcPriceModuleIndex()
El método CalcPriceModuleIndex() asigna a m_price_module el valor actual del número de elementos de la matriz, que es igual al índice del siguiente módulo que se añade. El método se llama antes de añadir el primer módulo de precio. El cuerpo de la función se encuentra en la declaración de clase
virtual void CalcPriceModuleIndex() {m_price_module=m_filters.Total();}
3.1.3. CheckOpenLong(...) y CheckOpenShort(...)
El método CheckOpenLong(...) se llama desde el ejemplar de clase CExpert y funciona de la manera siguiente:
- Comprobamos si hay módulos de precio conectados. Si no los hay, llamamos el método homónimo de clase parental;
- Obtenemos el pronóstico medido medio (dirección) del método Direction();
- Comprobamos el cumplimiento de las condiciones de entrada: comparamos la dirección del EMPTY_VALUE y el valor de umbral m_threshold_open;
- Actualizamos los valores de los parámetros de la orden con el método Prices(), los transmitimos al experto con la función OpenLongParams(...), y guardamos el resultado de esta función;
- Retornamos el resultado guardado.
bool CExpertSignalAdvanced::CheckOpenLong(double &price,double &sl,double &tp,datetime &expiration) { //--- si no se han encontrado módulos de precio, llamamos el método de clase básica CExpertSignal if(m_price_module<0) return(CExpertSignal::CheckOpenLong(price,sl,tp,expiration)); bool result =false; double direction=Direction(); //--- señal de prohibición if(direction==EMPTY_VALUE) return(false); //--- comprobando si se ha superado el umbral if(direction>=m_threshold_open) { Prices(); result=OpenLongParams(price,sl,tp,expiration);//there's a signal if m_order_open_long!=EMPTY_VALUE } //--- retornando resultado return(result); }
CheckOpenShort(...) está confeccionado y se utiliza de manera análoga, por eso no lo vamos a ver.
3.1.4 Direction()
El método Direction() solicita información de los filtros y calcula el pronóstico medido medio. Este método es una copia casi exacta del método homónimo de clase parentalCExpertSignal con una pequeña excepción: en el ciclo no nos dirigimos a todos los elementos de la matriz m_filters, sino sólo a aquellos cuyo índice es a partir de 0 y menor que m_price_module. El resto coincide con CExpertSignal::Direction().
double CExpertSignalAdvanced::Direction(void) { long mask; double direction; double result=m_weight*(LongCondition()-ShortCondition()); int number=(result==0.0)? 0 : 1; // cantidad de módulos interpelados //--- ciclo por los filtros for(int i=0;i<m_price_module;i++) { //--- máscara para los mapas de bits (variables que contengan etiqueta) mask=((long)1)<<i; //--- comprobación de las etiquetas para ignorar la señal del filtro if((m_ignore&mask)!=0) continue; CExpertSignal *filter=m_filters.At(i); //--- comprobación del índice if(filter==NULL) continue; direction=filter.Direction(); //--- señal de prohibición if(direction==EMPTY_VALUE) return(EMPTY_VALUE); if((m_invert&mask)!=0) result-=direction; else result+=direction; number++; } //--- promediación de la suma de los pronósticos medidos if(number!=0) result/=number; //--- retornando resultado return(result); }
3.1.5. Prices()
El método Prices() revisa la segunda parte de la matriz m_filters, comenzando por el índice m_price_module hasta el final, inquiere a los módulos de precio y actualiza los valores de las variables correspondientes de clase con ayuda de las funciones OpenLongParams(...) y OpenShortParams(...). Antes del ciclo los valores de los parámetros se desechan.
Durante el funcionamiento de un ciclo, los valores de los parámetros se regraban si el peso del módulo de precio actual (m_weight) es mayor que el de los anteriores requerimientos, de los cuales ya se han tomado los valores. Como resultado quedan o bien vacíos (si no se encuentra nada), o bien se quedan los mejores parámetros en cuanto al peso, que estén disponibles en el momento de la llamada del método.
double CExpertSignalAdvanced::Prices(void) { m_order_open_long=EMPTY_VALUE; m_order_stop_long=EMPTY_VALUE; m_order_take_long=EMPTY_VALUE; m_order_expiration_long=0; m_order_open_short=EMPTY_VALUE; m_order_stop_short=EMPTY_VALUE; m_order_take_short=EMPTY_VALUE; m_order_expiration_short=0; int total=m_filters.Total(); double last_weight_long=0; double last_weight_short=0; //--- ciclo de módulos de precio for(int i=m_price_module;i<total;i++) { CExpertSignalAdvanced *prm=m_filters.At(i); if(prm==NULL) continue; //--- ignoramos el módulo actual, si retorna EMPTY_VALUE if(prm.Prices()==EMPTY_VALUE)continue; double weight=prm.getWeight(); if(weight==0.0)continue; //--- se eligen valores no vacíos de los módulos con un peso pequeño if(weight>last_weight_long && prm.getExpLong()>TimeCurrent()) if(prm.OpenLongParams(m_order_open_long,m_order_stop_long,m_order_take_long,m_order_expiration_long)) last_weight_long=weight; if(weight>last_weight_short && prm.getExpShort()>TimeCurrent()) if(prm.OpenShortParams(m_order_open_short,m_order_stop_short,m_order_take_short,m_order_expiration_short)) last_weight_short=weight; } return(0); }
3.1.6. OpenLongParams(...) y OpenShortParams(...)
En el marco de la clase CExpertSignalAdvanced, el Método OpenLongParams(...) transmite por los enlaces los valores de los parámetros de la orden de compra, desde las variables de clase a los parámetros que toma en la entrada.
El cometido de este método en la clase parental era un poco diferente: en la señal principal calculaba los parámetros necesarios sobre la base del precio de mercado y las diferencias establecidas. Ahora sólo transmite los parámetros ya listos. Si el precio de apertura es correcto (no es igual a EMPTY_VALUE), entonces el método retorna true, si no, false.
bool CExpertSignalAdvanced::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { if(m_order_open_long!=EMPTY_VALUE) { price=m_order_open_long; sl=m_order_stop_long; tp=m_order_take_long; expiration=m_order_expiration_long; return(true); } return(false); }
OpenShortParams(...) está construido y funciona de manera análoga, por eso no vamos a detenernos a estudiarlo.
3.1.7. CheckUpdateOrderLong(...) y CheckUpdateOrderShort(...)
Los métodos CheckUpdateOrderLong(...) y CheckUpdateOrderShort(...) se llaman con la clase CExpertAdvanced. Se usan para actualizar una orden pendiente ya abierta, de acuerdo con los últimos valores de los niveles de precio actuales.
Miremos con más atención el método CheckUpdateOrderLong(...): primero se actualizan los niveles de precio con la ayuda del método Prices(...), después se comprueba si hay actualizaciones de datos, para excluir posibles errores de modificación. Al fin, se llama el método OpenLongParams(...) para transmitir los datos actualizados y el retorno del resultado.
bool CExpertSignalAdvanced::CheckUpdateOrderLong(COrderInfo *order_ptr,double &open,double &sl,double &tp,datetime &ex) { Prices(); //actualizamos los precios //--- comprobamos si ha habido cambios double point=m_symbol.Point(); if( MathAbs(order_ptr.PriceOpen() - m_order_open_long)>point || MathAbs(order_ptr.StopLoss() - m_order_stop_long)>point || MathAbs(order_ptr.TakeProfit()- m_order_take_long)>point || order_ptr.TimeExpiration()!=m_order_expiration_long) return(OpenLongParams(open,sl,tp,ex)); //--- la actualización no es necesaria return (false); }
CheckUpdateOrderShort(...) está construido y funciona de manera análoga, por eso no vamos a detenernos a estudiarlo.
3.2. CExpertAdvanced
Los cambios en la clase del experto conciernen sólo a la modificación de las órdenes ya abiertas, de acuerdo con los datos actualizados sobre los precios en la señal principal. La declaración de la clase CExpertAdvanced la tenemos más abajo.
class CExpertAdvanced : public CExpert { protected: virtual bool CheckTrailingOrderLong(); virtual bool CheckTrailingOrderShort(); virtual bool UpdateOrder(double price,double sl,double tp,datetime ex); public: CExpertAdvanced(); ~CExpertAdvanced(); };
Como se puede ver, hay pocos métodos, y el constructor y el destructor están vacíos.
3.2.1. CheckTrailingOrderLong() y CheckTrailingOrderShort()
El método CheckTrailingOrderLong() determina de nuevo el método homónimo de clase básica y llama el método CheckUpdateOrderLong(...) de la señal principal, para aclarar la necesidad o no de modificar la orden. Si es necesaria una modificación, se llama el método UpdateOrder(...) y se retorna el resultado.
bool CExpertAdvanced::CheckTrailingOrderLong(void) { CExpertSignalAdvanced *signal_ptr=m_signal; //--- se comprueba la posibilidad de cambiar la orden de compra double price,sl,tp; datetime ex; if(signal_ptr.CheckUpdateOrderLong(GetPointer(m_order),price,sl,tp,ex)) return(UpdateOrder(price,sl,tp,ex)); //--- retorno sin acción return(false); }
El método CheckTrailingOrderShort() está construido y funciona de manera análoga.
3.2.2. UpdateOrder()
La función UpdateOrder() primero comprueba si existe un precio actual (no hay EMPTY_VALUE). Si no, se elimina la orden, de otra forma modificamos la orden de acuerdo con los parámetros obtenidos.
bool CExpertAdvanced::UpdateOrder(double price,double sl,double tp,datetime ex) { ulong ticket=m_order.Ticket(); if(price==EMPTY_VALUE) return(m_trade.OrderDelete(ticket)); //--- cambiamos la orden, retornamos el resultado return(m_trade.OrderModify(ticket,price,sl,tp,m_order.TypeTime(),ex)); }
Con esto ya se ha finalizado el desarrollo de los herederos de las clases estándar.
4. Desarrollo de los módulos de precio
Ya tenemos base para crear asesores que abran órdenes y stops en los niveles calculados. O para ser más precisos, ya tenemos preparadas clases para trabajar con los niveles de precio. Queda escribir los módulos que generarán estos niveles.
El proceso de desarrollo de los módulos de precio es análogo a la escritura de módulos de señales comerciales: lo única diferencia es que hay que determinar de nuevo, no LongCondition(), ShortCondition() o Direction() como módulos de señales, sino Prices(), el método responsable de la actualización de los precios dentro del módulo. Por eso es deseable que el lector tenga idea del desarrollo de módulos de señales. En ello le ayudarán los artículos "¡Cree su propio robot de trading en 6 pasos!" y "Generador de señales comerciales del indicador de usuario".
A continuación, a modo de ejemplo, echaremos un vistazo al código de varios módulos de precio.
4.1. Módulo de precio sobre la base del indicador "Delta ZigZag"
El indicador Delta ZigZag construye los niveles según la cantidad de últimos picos indicada. El cruce de estos niveles de precio significa un probable viraje en la tendencia.
La tarea del módulo de precio es tomar el nivel de entrada del búffer de indicador, encontrar el extremo local más cercano para establecer un stop loss sobre él, calcular el take profit, multiplicando el stop loss por el coeficiente introducido en los ajustes.
Fig. 3. Ilustración del funcionamiento del módulo de precio en el indicador "Delta ZigZag" tomando como ejemplo una orden de compra
En la figura 3 se muestran los niveles generados por los módulos de precio. Antes de que se active la orden, stop loss y take profit cambian de situación después de la actualización del mínimo.
4.1.1. Descriptor del módulo
// wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=DeltaZZ Price Module | //| Type=SignalAdvanced | //| Name=DeltaZZ Price Module | //| ShortName=DeltaZZ_PM | //| Class=CPriceDeltaZZ | //| Page=not used | //| Parameter=setAppPrice,int,1, Applied price: 0 - Close, 1 - H/L | //| Parameter=setRevMode,int,0, Reversal mode: 0 - Pips, 1 - Percent | //| Parameter=setPips,int,300,Reverse in pips | //| Parameter=setPercent,double,0.5,Reverse in percent | //| Parameter=setLevels,int,2,Peaks number | //| Parameter=setTpRatio,double,1.6,TP:SL ratio | //| Parameter=setExpBars,int,10,Expiration after bars number | //+------------------------------------------------------------------+ // wizard description end
En el descriptor, los cinco primeros parámetros son necesarios para los ajustes del indicador utilizado. Siguiendo por orden, tenemos el coeficiente para el cálculo de take profit en base al stop loss, y luego el tiempo de expiración en barras para las órdenes de dicho módulo de precio.
4.1.2. Declaración de clase
class CPriceDeltaZZ : public CExpertSignalAdvanced { protected: CiCustom m_deltazz; //objeto del indicador DeltaZZ //--- ajustes del módulo int m_app_price; int m_rev_mode; int m_pips; double m_percent; int m_levels; double m_tp_ratio; //correlación tp:sl int m_exp_bars; //tiempo de expiración de la orden en barras //--- método de inicialización del indicador bool InitDeltaZZ(CIndicators *indicators); //--- métodos auxiliares datetime calcExpiration() { return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); } double getBuySL(); //function for searching latest minimum of ZZ for buy SL double getSellSL(); //function for searching latest maximum of ZZ for sell SL public: CPriceDeltaZZ(); ~CPriceDeltaZZ(); //--- métodos de cambio de ajustes del módulo void setAppPrice(int ap) { m_app_price=ap; } void setRevMode(int rm) { m_rev_mode=rm; } void setPips(int pips) { m_pips=pips; } void setPercent(double perc) { m_percent=perc; } void setLevels(int rnum) { m_levels=rnum; } void setTpRatio(double tpr) { m_tp_ratio=tpr; } void setExpBars(int bars) { m_exp_bars=bars;} //--- método para comprobar la corrección de los ajustes virtual bool ValidationSettings(void); //--- método de creación de indicadores virtual bool InitIndicators(CIndicators *indicators); //--- método principal del módulo de precio, que actualiza los datos de salida virtual double Prices(); };
En el módulo se anuncian los datos protegidos: el objeto del indicador del tipo CiCustom y los parámetros que corresponden a los tipos indicados en el descriptor.
4.1.3. Constructor
El constructor inicializa los parámetros de clase con los valores por defecto. Estos valores, como consecuencia, se inicializan de nuevo al conectar el módulo a la señal principal, correspondiéndose con los parámatros de entrada del asesor.
CPriceDeltaZZ::CPriceDeltaZZ() : m_app_price(1), m_rev_mode(0), m_pips(300), m_percent(0.5), m_levels(2), m_tp_ratio(1), m_exp_bars(10) { }
4.1.4. ValidationSettings()
Un método importante para comprobar los parámetros de entrada es ValidationSettings(). Si los valores de los parámetros del módulo no son adecuados, se retorna el resultado false, y en el registro se muestra un mensaje sobre el error.
bool CPriceDeltaZZ::ValidationSettings(void) { //--- comprobando los ajustes de los filtros adicionales if(!CExpertSignal::ValidationSettings()) return(false); //--- comprobación inicial de datos if(m_app_price<0 || m_app_price>1) { printf(__FUNCTION__+": Applied price must be 0 or 1"); return(false); } if(m_rev_mode<0 || m_rev_mode>1) { printf(__FUNCTION__+": Reversal mode must be 0 or 1"); return(false); } if(m_pips<10) { printf(__FUNCTION__+": Number of pips in a ray must be at least 10"); return(false); } if(m_percent<=0) { printf(__FUNCTION__+": Percent must be greater than 0"); return(false); } if(m_levels<1) { printf(__FUNCTION__+": Ray Number must be at least 1"); return(false); } if(m_tp_ratio<=0) { printf(__FUNCTION__+": TP Ratio must be greater than zero"); return(false); } if(m_exp_bars<0) { printf(__FUNCTION__+": Expiration must be zero or positive value"); return(false); } //--- la comprobación de datos ha finalizado return(true); }
4.1.5. InitIndicators(...)
El método InitIndicators(...) llama el método homónimo de la clase básica e inicializa el indicador del módulo actual con el método InitDeltaZZ(...).
bool CPriceDeltaZZ::InitIndicators(CIndicators *indicators) { //--- inicialización de los indicadores de los filtros if(!CExpertSignal::InitIndicators(indicators)) return(false); //--- creación e inicialización del indicador personalizado if(!InitDeltaZZ(indicators)) return(false); //--- éxito return(true); }
4.1.6. InitDeltaZZ(...)
El método InitDeltaZZ(...) añade un objeto del indicador personalizado a la colección y crea el nuevo indicador "Delta ZigZag".
bool CPriceDeltaZZ::InitDeltaZZ(CIndicators *indicators) { //--- añadir a la colección if(!indicators.Add(GetPointer(m_deltazz))) { printf(__FUNCTION__+": error adding object"); return(false); } //--- establecer los parámetros del indicador MqlParam parameters[6]; //--- parameters[0].type=TYPE_STRING; parameters[0].string_value="deltazigzag.ex5"; parameters[1].type=TYPE_INT; parameters[1].integer_value=m_app_price; parameters[2].type=TYPE_INT; parameters[2].integer_value=m_rev_mode; parameters[3].type=TYPE_INT; parameters[3].integer_value=m_pips; parameters[4].type=TYPE_DOUBLE; parameters[4].double_value=m_percent; parameters[5].type=TYPE_INT; parameters[5].integer_value=m_levels; //--- inicialización del objeto if(!m_deltazz.Create(m_symbol.Name(),m_period,IND_CUSTOM,6,parameters)) { printf(__FUNCTION__+": error initializing object"); return(false); } //--- cantidad de búffers del indicador if(!m_deltazz.NumBuffers(5)) return(false); //--- ok return(true); }
Cuando hayan finalizado correctamente los métodos ValidationSettings(), InitDeltaZZ(...) y InitIndicators(...) el módulo es inicializado y está listo para funcionar.
4.1.7. Prices()
Este método es elemental para el módulo de precio. Es precisamente aquí donde se actualizan los parámetros de las órdenes que se transmiten a la señal principal. El método retorna un resultado de la operación tipo double. Esto se ha hecho, principalmente, para futuros desarrollos. En lo sucesivo, el resultado del método Prices() puede codificar ciertas situaciones y eventos especiales, para que la señal principal los procese de la manera correspondiente. En estos momentos, sólo se ha previsto el desarrollo del valor retornado EMPTY_VALUE. Para obtener este resultado, la señal principal ignora los parámetros propuestos por el módulo.
El algoritmo de funcionamiento del método Prices() se encuentra en este módulo:
- Tomamos los precios de apertura de los búffers de indicador 3 y 4 para las compras y las ventas respectivamente;
- Ponemos a cero los parámetros finales de las órdenes;
- Comprobamos que haya precios de compra. Si encontramos, buscamos dónde poner stop loss con el método getBuySL(), calculamos el nivel de take profit según el tamaño del stop loss y el precio de apertrura, calculamos el tiempo de expiración de la orden;
- Comprobamos que haya precio de venta. Si encontramos, buscamos dónde poner stop loss con el método getSellSL(), calculamos el nivel de take profit según el tamaño del stop loss y el precio de apertrura, calculamos el tiempo de expiración de la orden;
- Salimos.
double CPriceDeltaZZ::Prices(void) { double openbuy =m_deltazz.GetData(3,0);//obtenemos el último valor del búffer 3 double opensell=m_deltazz.GetData(4,0);//obtenemos el último valor del búffer 4 //--- se desechan los valores de los parámetros m_order_open_long=EMPTY_VALUE; m_order_stop_long=EMPTY_VALUE; m_order_take_long=EMPTY_VALUE; m_order_expiration_long=0; m_order_open_short=EMPTY_VALUE; m_order_stop_short=EMPTY_VALUE; m_order_take_short=EMPTY_VALUE; m_order_expiration_short=0; int digits=m_symbol.Digits(); //--- comprobamos que haya precios de compra if(openbuy>0)//si el búffer 3 no está vacío { m_order_open_long=NormalizeDouble(openbuy,digits); m_order_stop_long=NormalizeDouble(getBuySL(),digits); m_order_take_long=NormalizeDouble(m_order_open_long + m_tp_ratio*(m_order_open_long - m_order_stop_long),digits); m_order_expiration_long=calcExpiration(); } //--- comprobamos que haya precios de venta if(opensell>0)//si el búffer 4 no está vacío { m_order_open_short=NormalizeDouble(opensell,digits); m_order_stop_short=NormalizeDouble(getSellSL(),digits); m_order_take_short=NormalizeDouble(m_order_open_short - m_tp_ratio*(m_order_stop_short - m_order_open_short),digits); m_order_expiration_short=calcExpiration(); } return(0); }
4.1.8. getBuySL() y getSellSL()
Los métodos getBuySL() y getSellSL() buscan mínimos y máximos locales, respectivamente, para establecer stop loss. En cada método se busca el último valor cero en el búffer correspondiente – el nivel de precio del último extremo local.
double CPriceDeltaZZ::getBuySL(void) { int i=0; double sl=0.0; while(sl==0.0) { sl=m_deltazz.GetData(0,i); i++; } return(sl); }
double CPriceDeltaZZ::getSellSL(void) { int i=0; double sl=0.0; while(sl==0.0) { sl=m_deltazz.GetData(1,i); i++; } return(sl); }
4.2. Módulo de precio en base a la barra interior
La Barra interior (Inside bar) es uno de los modelos más ampliamente utilizados (patrones) en los procedimientos de comercio sin indicadores, llamado Price action. Se considera una barra interior a aquella cuyos High y Low no salen de los extremos de la barra anterior. La barra interior es una señal de que está comenzando la consolidación o de que es posible un viraje en el movimiento del precio .
En el dib. 4 las barras interiores están designadas con elipses rojas. Al detectar una barra interna, el módulo de precio genera precios de apertura de órdenes buystop y sellstop en los extremos de la barra anterior a la barra interior.
Los precios de apertura son niveles de stop loss para las órdenes opuestas. Los take profit se calculan igual que en módulo visto anteriormente, mediante la multiplicación del tamaño de stop loss por un coeficiente establecido en los ajustes. Los precios de apertura y los stop loss están marcados con líneas rojas horizontales, y las take profit, con líneas verdes.
Fig. 4. Ilustración de las barras interiores y de los niveles del módulo de precio
En el dib. 4 las órdenes de venta no se abren, dado que la dirección de la tendencia se considera ascendente. Tanto más, cuanto que el módulo de precio genera niveles para la entrada en ambos sentidos. Algunos niveles de take profit se ubican más allá de los límites de la zona del dibujo.
A diferencia del anterior, el algoritmo de este módulo es sencillo y no necesita inicializar los indicadores. En el módulo hay poco código, por eso lo hemos mostrado al completo, sin estudiar con detalle cada función por separado.
#include <Expert\ExpertSignalAdvanced.mqh> // wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Inside Bar Price Module | //| Type=SignalAdvanced | //| Name=Inside Bar Price Module | //| ShortName=IB_PM | //| Class=CPriceInsideBar | //| Page=not used | //| Parameter=setTpRatio,double,2,TP:SL ratio | //| Parameter=setExpBars,int,10,Expiration after bars number | //| Parameter=setOrderOffset,int,5,Offset for open and stop loss | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| Class CPriceInsideBar | //| Purpose: Clase del generador de niveles de precio para las órdenes en base | //| al patrón "barra interior". | //| Is derived from the CExpertSignalAdvanced class. | //+------------------------------------------------------------------+ class CPriceInsideBar : public CExpertSignalAdvanced { protected: double m_tp_ratio; //correlación tp:sl int m_exp_bars; //tiempo de expiración de la orden en barras double m_order_offset; //desplazamiento de niveles de apertura y stop loss datetime calcExpiration() { return(TimeCurrent()+m_exp_bars*PeriodSeconds(m_period)); } public: CPriceInsideBar(); ~CPriceInsideBar(); void setTpRatio(double ratio){ m_tp_ratio=ratio; } void setExpBars(int bars) { m_exp_bars=bars;} void setOrderOffset(int pips){ m_order_offset=m_symbol.Point()*pips;} bool ValidationSettings(); double Prices(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CPriceInsideBar::CPriceInsideBar() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CPriceInsideBar::~CPriceInsideBar() { } //+------------------------------------------------------------------+ //| Validation of protected settings | //+------------------------------------------------------------------+ bool CPriceInsideBar::ValidationSettings(void) { //--- comprobar parámetros de los filtros if(!CExpertSignal::ValidationSettings()) return(false); //--- comenzando a comprobar los datos if(m_tp_ratio<=0) { printf(__FUNCTION__+": TP Ratio must be greater than zero"); return(false); } if(m_exp_bars<0) { printf(__FUNCTION__+": Expiration must be zero or positive value"); return(false); } //--- la comprobación ha finalizado return(true); } //+------------------------------------------------------------------+ //| Price levels refreshing | //+------------------------------------------------------------------+ double CPriceInsideBar::Prices(void) { double h[2],l[2]; if(CopyHigh(m_symbol.Name(),m_period,1,2,h)!=2 || CopyLow(m_symbol.Name(),m_period,1,2,l)!=2) return(EMPTY_VALUE); //--- comprobación de la barra interior if(h[0] >= h[1] && l[0] <= l[1]) { m_order_open_long=h[0]+m_order_offset; m_order_stop_long=l[0]-m_order_offset; m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio; m_order_expiration_long=calcExpiration(); m_order_open_short=m_order_stop_long; m_order_stop_short=m_order_open_long; m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio; m_order_expiration_short=m_order_expiration_long; return(0); } return(EMPTY_VALUE); } //+------------------------------------------------------------------+
En este módulo, en el método Prices() se utiliza el retorno del resultado EMPTY_VALUE, para mostrar a la señal principal la ausencia de niveles de precio disponibles.
4.3. Módulo de precio en base a la barra exterior
La Barra exterior (Outside bar) es otro patrón Price Action ampliamente utilizado, el también llamado patrón de "absorción". Se considera que es externa la barra cuyo diapasón por arriba y por abajo cubre High y Low de la barra anterior. Una barra externa es señal del crecimiento de la volatilidad y del posterior movimiento dirigido del precio.
En el dib. 5 las barras externas están designadas con elipses rojas. Al detectar una barra externa, el módulo de precio genera precios de apertura de órdenes buystop y sellstop en sus extremos.
Los precios de apertura son niveles de stop loss para las órdenes opuestas. Los take profit se calculan igual que en módulo visto anteriormente, mediante la multiplicación del tamaño de stop loss por un coeficiente establecido en los ajustes. Los precios de apertura y los stop loss están marcados con líneas rojas horizontales, y las take profit, con líneas verdes.
Fig. 5. Ilustración de las barras exteriores y de los niveles del módulo de precio
En el dib. 5 las barras están marcadas con óvalos rojos. Las líneas horizontales muestran los precios de apertura de las órdenes pendientes, generados por el módulo de precio.
En este ejemplo se abren sólo órdenes de compra, dado que la tendencia se considera descendente. En general, los fundamentos del funcionamiento del módulo son análogos a los del anterior. El código sólo se diferencia en un método, el método Prices(), todo lo demás es lo mismo, con sus nombres correspondientes. Por eso vamos a mostrar sólo el código del método Prices().
double CPriceOutsideBar::Prices(void) { double h[2],l[2]; if(CopyHigh(m_symbol.Name(),m_period,1,2,h)!=2 || CopyLow(m_symbol.Name(),m_period,1,2,l)!=2) return(EMPTY_VALUE); //--- comprobación de la barra exterior if(h[0] <= h[1] && l[0] >= l[1]) { m_order_open_long=h[1]+m_order_offset; m_order_stop_long=l[1]-m_order_offset; m_order_take_long=m_order_open_long+(m_order_open_long-m_order_stop_long)*m_tp_ratio; m_order_expiration_long=calcExpiration(); m_order_open_short=m_order_stop_long; m_order_stop_short=m_order_open_long; m_order_take_short=m_order_open_short-(m_order_stop_short-m_order_open_short)*m_tp_ratio; m_order_expiration_short=m_order_expiration_long; return(0); } return(EMPTY_VALUE); }
4.4. Comprobación del correcto funcionamiento de los módulos
En este apartado vamos a mostrar ejemplos de los tres módulos, para comprobar primero su capacidad individual, y después su capacidad conjunta de trabajo en un solo asesor y así convencernos de que todo funciona como se había pensado. En conclusión, generaremos cuatro asesores. En calidad de filtro usamos en cada uno de ellos un módulo de señales comerciales en base al indicador Awesome Oscillator con un intervalo temporal H12. Los módulos de precio funcionarán con un intervalo de H6.
Gestión de capital: lote fijo, no se usará trailing stop. Todos los tests se realizan con la pareja EURUSD con las cotizaciones de la demo-cuenta en el servidor MetaQuotes-Demo en el periodo comprendido del 2013.01.01 al 2014.06.01. Versión del terminal: 5.00, build 965 (27 de junio de 2014).
Para mayor brevedad, en calidad de resultados de la simulación se mostrarán sólo los gráficos del saldo. Son más que suficientes para ver el comportamiento del asesor en cada caso.
4.4.1. Prueba del módulo en base al indicador Delta ZigZag
Fig. 6. Gráfico del saldo en la simulación del módulo de precio en base al indicador "Delta ZigZag"
4.4.2. Puesta a prueba del módulo en base al patrón "barra interior"
Fig. 7. Gráfico del saldo en la simulación del módulo de precio en base a la barra interior
Al ver el gráfico en la figura 7 sólo nos queda recordar que el objetivo de la simulación en esta etapa no es conseguir una estrategia que dé beneficios, sino comprobar la capacidad de trabajo del código escrito.
4.4.3. Puesta a prueba del módulo en base al patrón "barra exterior"
Fig. 8. Gráfico del saldo en la simulación del módulo de precio en base a la barra exterior
4.4.4. Puesta a prueba del trabajo conjunto de los módulos de precio desarrollados
Tomando en cuenta los resultados en las simulaciones anteriores, los módulos de precio en base a DeltaZigZag, a la barra interior y a la barra exterior, hemos obtenido coeficientes de peso de 1, 0.5 y 0.7, respectivamente. Los coeficientes de peso, recuerdo, son responsables de la prioridad de los módulos de precio.
Fig. 9. Gráfico del saldo en la simulación del asesor con los tres módulos de precio
En cada etapa de la simulación se llevó a cabo un minucioso examen de las operaciones realizadas según los gráficos de precio. No hubo errores o alteraciones con respecto a la estrategia. Puede poner a prueba de manera independiente los programas propuestos, se adjuntan al final del artículo.
5. Instrucciones de aplicación
Veamos las etapas de creación del asesor con el uso de la ampliación desarrollada y tomando como ejemplo el asesor con los tres módulos de precio y el filtro AO.
Antes de comenzar la generación, asegúrese de que los archivos de encabezado "CExpertAdvanced.mqh" y "CExpertSignalAdvanced.mqh" se encuentren en el catálogo de datos del terminal MetaTrader 5 en la carpeta MQL5/Include/Expert, y que los archivos de los módulos de precio se hallen en la carpeta MQL5/Include/Expert/MySignal. Antes del inicio del asesor, es necesario abastecer la carpeta MQL5/Indicators con los archivos compilados necesarios. En este caso se trata del archivo "DeltaZigZag.ex5". En el archivo adjunto se encuentran todos los archivos en su sitio correspondiente. Basta sencillamente con desempacar este archivo en la carpeta con el terminal MetaTrader 5 y confirmar la unión de los catálogos.
5.1. Generación del asesor
Para proceder a la generación del asesor, en el MetaEditor elija el punto del menú "Archivo"->"Crear".
Fig. 10. Crear un nuevo asesor con ayuda del Asistente MQL5
En la ventana que aparecerá, marque el punto "Asesor (generar)" y pulse "Siguiente".
Fig. 11. Generar un asesor con ayuda del Asistente MQL5
Aparecerá una ventana para introducir el nombre. En este ejemplo, el nombre del asesor es "TEST_EA_AO_DZZ_IB_OB", los parámetros Symbol y TimeFrame tienen los valores por defecto. Después de introducir el nombre, pulse "Siguiente".
Fig. 12. Ajustes generales del asesor
En la ventana que aparecerá, añadimos por orden los módulos conectados, pulsando el botón "Añadir". El proceso al completo se muestra en la figura de más abajo.
¡ATENCIÓN! Al añadir los módulos, primero se conectan todos los módulos de señales (herederos de CExpertSignal), y sólo después los módulos de precio (herederos de CExpertSignalAdvanced). Si no se respeta este orden de conexión, el resultado del funcionamiento del asesor, lo más seguro, es que sea impredecible.
Conectamos primero el módulo de señales del indicador Awesome Oscillator. En nuestro ejemplo se trata del único filtro.
Fig. 13. Conexión del módulo de señales Awesome Oscillator
Después de conectar todos los módulos de señales, conectamos los módulos de precio necesarios en orden arbitrario. En este ejemplo son tres.
Fig. 14. Conexión del módulo de señales DeltaZZ Price Module
Fig. 15. Conexión del módulo de señales Bar Price Module
Fig. 16. Conexión del módulo de señales Outside Bar Price Module
Después de haber añadido todos los módulos, la ventana tendrá el aspecto siguiente:
Fig. 17. Lista de módulos de señales comerciales añadidos
El valor del parámetro "Peso" para el módulo de precio determina su prioridad.
Después de pulsar "Siguiente, el Asistente le propondrá elegir los módulos de gestión de capital y los módulos de trailing stop. Elija según su criterio o déjelo todo como está, después pulse el botón "Siguiente" o "Listo".
Tras pulsar el botón "Listo", obtendremos el código generado de nuestro asesor.
5.2. Corrección del código generado
Bien, ya hemos obtenido el código.
1. Encontramos justo al principio la línea
#include <Expert\Expert.mqh>
y la editamos para conseguir lo siguiente
#include <Expert\ExpertAdvanced.mqh>
Esto es necesario para conectar los archivos de las clases desarrolladas.
2. A continuación encontramos la línea
CExpert ExtExpert;
y la cambiamos a
CExpertAdvanced ExtExpert;
Con ello cambiamos la clase estándar del asesor por su descendiente, pero con las funciones que necesitamos.
3. Encontramos la línea
CExpertSignal *signal=new CExpertSignal;
y la cambiamos a
CExpertSignalAdvanced *signal=new CExpertSignalAdvanced;
Con ello cambiamos la clase estándar de la señal principal por su descendiente, pero con las funciones que necesitamos.
4. Encontramos en el código la línea que ejecuta el añadido del primer módulo de precio en la matriz m_filters de la señal principal. En este ejemplo tiene el siguiente aspecto:
signal.AddFilter(filter1);
Antes de ella pegamos la línea
signal.CalcPriceModuleIndex();
Esto es necesario para que la señal principal sepa hasta qué índice en la matriz m_filters se encuentran los indicadores de referencia de los módulos de señal, y desde cuál empiezan los indicadores de referencia de los módulos de precio.
La búsqueda de la posición necesaria para pegar la línea indicada puede ser difícil para los usuarios. Para facilitar la búsqueda y no fallar, es mejor orientarse por el número que sigue a la palabra "filter". El Asistente MQL5 da automáticamente en orden los nombres de los módulos conectados. El primer módulo se llama filter0, el segundo, filter1, el tercero, filter2 y así sucesivamente. En nuestro ejemplo, sólo hay un módulo de señal, por eso el primer módulo de precio conectado es el segundo dentro del orden, lo cual significa que necesitamos buscar la línea de añadido de filtro "signal.AddFilter(filter1);", dado que la numeración de los filtros en el código comienza por el cero. Para verlo mejor, vamos a mostrar esto en la fig. 18:
Fig. 18. Nombres de los módulos en el código, de acuerdo con el orden de conexión
5. (No es obligatorio) Debido a las nuevas introducciones, los parámetros del asesor que son responsables de la diferencia en los precios de apertura, stop loss, take profit y del tiempo de expiración de las órdenes, han perdido su aplicación. Por eso, a su discreción y para liberar espacio, puede eliminar del código las líneas siguientes:
input double Signal_PriceLevel =0.0; // Price level to execute a deal input double Signal_StopLevel =50.0; // Stop Loss level (in points) input double Signal_TakeLevel =50.0; // Take Profit level (in points) input int Signal_Expiration =4; // Expiration of pending orders (in bars)
Después de borrarlas, al compilar aparecerá un error que nos conducirá al siguiente grupo de líneas, que también será necesario borrar:
signal.PriceLevel(Signal_PriceLevel); signal.StopLevel(Signal_StopLevel); signal.TakeLevel(Signal_TakeLevel); signal.Expiration(Signal_Expiration);
En cuanto borremos estas líneas que hemos mostrado más arriba, todo finalizará bien.
En el archivo adjunto del asesor "TEST_EA_AO_DZZ_IB_OB", en los comentarios, se dan aclaraciones sobre la edición. Las líneas de código que se pueden borrar están comentadas.
Conclusión
Como resultado del trabajo hemos conseguido ampliar sustancialmente la esfera de uso del Asistente MQL5. Ahora, con su ayuda, podemos optimizar el proceso de desarrollo de los sistemas comerciales automáticos que necesitan de la apertura órdenes, stop loss y take profit en diferentes niveles de precio, independientemente del precio actual.
Los expertos generados pueden incluir multitud de módulos de precio, que calculen los parámetros de las órdenes enviadas. De los conjuntos de parámetros disponibles, se elige el que más convenga, la conveniencia se establece en los ajustes. Esto permitirá usar en el asesor multitud de puntos de entrada diferentes con una efectividad máxima. Esta aproximación hace los expertos más escrupulosos: si se sabe la dirección, pero no se ha determinado el punto de entrada, el asesor esperará su aparción.
La introducción de los módulos conjuntos, responsables de la búsqueda de niveles de precio, es una ventaja significativa que está llamada a simplificar el desarrollo de robots. Y aunque en el momento actual sólo hay tres de estos módulos, su número crecerá indudablemente. Si considera que este artículo le ha sido útil, puede proponer algoritmos de funcionamiento de los módulos de precio en los comentarios al mismo. Las ideas interesantes no quedarán inadvertidas y se verán plasmadas en código.
Además, en el artículo se ha descrito un método con ayuda del cual se puede ampliar más adelante las posibilidades de los expertos creados en el asistente. La utilización de la herencia es el mejor método para introducir cambios.
Y para acabar, diremos que los usuarios que carezcan de habilidades relacionadas con la programación, y que sean capaces de redactar código con instrucciones, tendrán la posibilidad de crear asesores avanzados en base a los modelos disponibles.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/987
- 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