Desarrollando los Asesores Expertos multimódulo
Sergey Pavlov | 30 marzo, 2018
Introducción
Hoy en día existen varios enfoques en la programación, a saber: enfoque modular, orientado a objetos y estructurado. En este artículo se trata de la programación modular con arreglo a los robots.
La programación modular es un método del desarrollo de programas que supone la división de un programa en unos módulos independientes.
La regla principal de la programación modular es el principio «divide y vencerás». La conveniencia del uso de la arquitectura modular consiste en la posibilidad de actualizar (sustituir) el módulo, sin necesidad de modificar el resto del sistema.
La programación modular se basa en tres concepciones principales.
- Principio de ocultación de la información de Parnas. El módulo sirve para ocultar la información y el algoritmo de solución de una determinada tarea. Luego, se puede sustituir el módulo por otro.
- Axioma de la modularidad de Cowan. El módulo es una unidad de programación independiente que sirve para ejecutar unas determinadas funciones del programa.
- Programación de ensamblaje de Cetin. Los módulos son «ladrillos» de los que se construye un programa.
La única alternativa a la modularidad es un programa sólido. No es de todo conveniente: si surge la necesidad de modificar o completar algunas funciones del programa, es necesario editar el código del Asesor Experto (EA), y en mayoría de los casos, eso puede ser hecho por el autor del código, o por otro programador experimentado. Y si además, el programa sólido está compilado, la edición de este producto puede ser realizada exclusivamente por el propietario de los derechos del autor, y no es seguro que Usted podrá ponerse de acuerdo con él. Por tanto, parece más conveniente tener la posibilidad de modificar las funciones importantes del programa, sin hacer participar al autor, sino usando su propio potencial o servicios de otros desarrolladores.
Fig. 1. Esquema abstracto del robot comercial modular
Principio de modularidad múltiple
La programación modular es un arte de dividir las tareas en un determinado número de subtareas implementadas en forma de unos módulos separados (archivos). En caso general, el módulo programado es un programa separado, o una unidad funcionalmente terminada y compilada de modo autónomo que se identifica de alguna manera y se conecta con el módulo invocado. En otras palabras, el módulo es un fragmento del programa funcionalmente terminado e implementado en forma de un archivo compilado que sirve para el uso en otros programas.
Al determinar el conjunto de módulos que realizan las funciones de un determinado algoritmo, hay que tomar en cuenta lo siguiente:
- cada módulo se invoca por el módulo superior, y después de terminar su trabajo, devuelve la administración al módulo que le ha llamado;
- las decisiones principales en el algoritmo se toman en el nivel superior de la jerarquía;
- los módulos son independientes unos de otros en cuanto a los datos;
- los módulos no dependen de la prehistoria de las llamadas a ellos.
Pues, resumiendo todo lo dicho arriba, un programa modular es aquel programa en el que se puede modificar cualquier parte de la estructura lógica, sin alterar otras partes.
Características principales del módulo:
- una entrada y una salida — en la entrada, el módulo recibe un determinado conjunto de datos originales, los procesa y devuelve un conjunto de datos resultantes, es decir, se implementa el principio IPO (Input-Process-Output);
- acabamiento funcional — para ejecutar una función separada, el módulo ejecuta una serie completa de operaciones reglamentarias, la cual es suficiente para completar el procesamiento iniciado;
- independencia lógica — el resultado del trabajo del módulo de programa depende exclusivamente de datos originales, y no depende del trabajo de otros módulos;
- comunicaciones informativas débiles con otros módulos — el intercambio de la información entre los módulos de ser minimizado a ser posible.
En el lenguaje MQL5, se puede escribir tres tipos de programas: Asesor Experto, indicador o script. Para el módulo principal, lo más conveniente será el formado del EA en el que será implementada la administración de todos los módulos y se ubicarán las funciones comerciales.Mientras que el resto de los módulos podrá ser implementado en forma de los indicadores. Es verdad que los indicadores convienen mejor para la formación del módulo: los datos calculados según el algoritmo especificado pueden ser guardados en los búferes de indicador, y en caso de necesidad, se puede pasarlos al EA multimódulo. El EA, en su lugar, puede usar o ignorar estos datos, en función de la tarea planteada. Puede que en algunos proyectos sea conveniente usar los EAs como módulos externos, pero en este caso, habrá que idear detenidamente el mecanismo del intercambio de datos.
Seguramente Usted ha usado las tecnologías modulares en sus EAs más de una vez: por ejemplo, los indicadores personalizados como módulos de generación y filtración de las señales comerciales.
Según mi opinión, la solución más racional es la siguiente: todo la funcionalidad básica está concentrada en el módulo principal y no requiere la participación de módulos externos. En su lugar, los módulos externos son necesarios para la adaptación a diferentes condiciones de mercado y el mejoramiento de la estrategia comercial. El conjunto de las funciones del programa no se determina por el autor del código o estrategia, sino por el trader como usuario del robot comercial. Cabe mencionar que nadie viola los derechos legales de cada uno.
Módulo principal — Asesor Experto
El módulo principal —donde está concentrada la administración de todo el proyecto— es el más importante en la jerarquía del EA. Es obligatorio que incluya las funciones comerciales sin las cuales cualquier estrategia comercial carece del sentido.
Vamos a considerar el desarrollo del EA multimódulo a base del ejemplo concreto que ha sido cogido de CodeBase. El EA original negocia con un lote fijo en el canal del indicador iBands con la reversión de la posición en los bordes del canal. El EA es absolutamente autosuficiente y no requiere ningún programa externo.
No todo el EA puede ser del tipo multimódulo, sino aquél que tenga prediseñada esta posibilidad.
¿Qué es lo que necesitamos añadir al código para convertirlo en un proyecto de varios módulos?
- Declarar los módulos externos (indicadores), los cuales el usuario podrá aplicar en el futuro.
- Añadir la funcionalidad necesaria para su integración.
- Preparar la documentación para los desarrolladores de los módulos externos (incluir la función de generación de la documentación en un archivo separado). Así, para el diseño de los módulos externos, será necesaria la información sobre la estructura de datos que pueden ser usados por el módulo principal de forma correcta. Por ejemplo, en el ejemplo analizado, el módulo de la Gestión de capital tiene que transmitir el tamaño del lote en el EA, y el módulo de seguimiento de la posición: la distancia desde el precio actual en puntos.
Como resultado de las transformaciones, obtenemos un EA modular en el que podemos integrar de hasta siete módulos externos.
- Módulo №1 — módulo de la Gestión de capital. En la salida, nos da el tamaño del lote.
- Módulo №2 — módulo del seguimiento de la posición y establecimiento del SL. En la salida, nos da la distancia hasta el Stop Loss en puntos desde el precio de la apertura de la posición.
- Módulo №3 — módulo del seguimiento de la posición y establecimiento del TP. En la salida, nos da la distancia hasta el Take Profit en puntos desde el precio de la apertura de la posición.
- Módulo №4 — módulo del seguimiento de la posición y establecimiento del Trailing Stop. En la salida, nos da la distancia hasta el Stop Loss en puntos desde el precio actual.
- Módulo №5 — módulo de la generación de señales comerciales. En la salida, nos da el valor de la señal.
- Módulo №6 — módulo de la filtración de señales comerciales. En la salida, nos da el valor del filtro.
- Módulo №7 — módulo del seguimiento de la posición y establecimiento del nivel del punto muerto (break even). En la salida, nos da la distancia hasta el Stop Loss desde el precio de la apertura de la posición.
Fig. 2. Función OnInit() e inicialización de módulos externos
Fig. 3. Función OnTick() y lectura de datos desde los módulos externos
Fig. 4. Función OnTrade() y lectura de datos desde los módulos externos
Fig. 5. Función de la generación de señales comerciales y lectura de datos desde los módulos externos
//****** project (module expert): test_module_exp.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project TEST Main module." #property link "The project uses 7 external modules." //--- #include <Trade\Trade.mqh> //--- MqlTick last_tick; CTrade trade; //--- input int e_bands_period=80; // Moving average period int e_bands_shift=0; // shift input double e_deviation=3.0; // Number of standard deviations input ENUM_APPLIED_PRICE e_applied_price=PRICE_CLOSE; // Price type input bool on_module=false; // whether or not to use plug-ins //--- double lot=0.01; // Fixed lot double min_lot=0.01; // Minimum allowable lot bool on_trade=false; // Trade function flag //--- Variable for storing the indicator iBands handle int handle_Bands; //--- module 1 bool on_lot=false; int handle_m1; //--- module 2 bool on_SL=false; int handle_m2; //--- module 3 bool on_TP=false; int handle_m3; //--- module 4 bool on_Trail=false; int handle_m4; //--- module 5 bool on_signals=false; int handle_m5; //--- module 6 bool on_Filter=false; int handle_m6; //--- module 7 bool on_Breakeven=false; int handle_m7; //+------------------------------------------------------------------+ //| Structure of trading signals | //+------------------------------------------------------------------+ struct sSignal { bool Buy; // Buy signal bool Sell; // Sell signal }; //+------------------------------------------------------------------+ //| Trading signals generator | //+------------------------------------------------------------------+ sSignal Buy_or_Sell() { sSignal res={false,false}; //--- MODULE 5 if(on_signals) { // If there is an additional module double buffer_m5[]; ArraySetAsSeries(buffer_m5,true); if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res); if(buffer_m5[0]<-1) res.Sell=true; if(buffer_m5[0]>1) res.Buy=true; } //--- MODULE 6 if(on_Filter) { // If there is an additional module double buffer_m6[]; ArraySetAsSeries(buffer_m6,true); if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res); lot=buffer_m6[0]; if(buffer_m6[0]<1) res.Buy=false; if(buffer_m6[0]>-1) res.Sell=false; } //--- //--- Indicator buffers double UpperBuffer[]; double LowerBuffer[]; double MiddleBuffer[]; ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer); ArraySetAsSeries(UpperBuffer,true); CopyBuffer(handle_Bands,1,0,1,UpperBuffer); ArraySetAsSeries(LowerBuffer,true); CopyBuffer(handle_Bands,2,0,1,LowerBuffer); //--- Timeseries double L[]; double H[]; ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L); ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H); if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true; if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true; //--- return(res); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create the indicator handle handle_Bands=iBands(_Symbol,_Period,e_bands_period,e_bands_shift,e_deviation,e_applied_price); if(handle_Bands==INVALID_HANDLE) return(INIT_FAILED); else on_trade=true; if(on_module) { //--- MODULE 1 //--- check: whether there is an external module? handle_m1=iCustom(NULL,0,"Market\\test_module_MM"); if(handle_m1!=INVALID_HANDLE) on_lot=true; //--- MODULE 2 //--- check: whether there is an external module? handle_m2=iCustom(NULL,0,"Market\\test_module_SL"); if(handle_m2!=INVALID_HANDLE) on_SL=true; //--- MODULE 3 //--- check: whether there is an external moduleь? handle_m3=iCustom(NULL,0,"Market\\test_module_TP"); if(handle_m3!=INVALID_HANDLE) on_TP=true; //--- MODULE 4 //--- check: whether there is an external module? handle_m4=iCustom(NULL,0,"Market\\test_module_Trail"); if(handle_m4!=INVALID_HANDLE) on_Trail=true; //--- MODULE 5 //--- check: whether there is an external module? handle_m5=iCustom(NULL,0,"Market\\test_module_signals"); if(handle_m5!=INVALID_HANDLE) on_signals=true; //--- MODULE 6 //--- check: whether there is an external module? handle_m6=iCustom(NULL,0,"Market\\test_module_Filter"); if(handle_m6!=INVALID_HANDLE) on_Filter=true; //--- MODULE 7 //--- check: whether there is an external module? handle_m7=iCustom(NULL,0,"Market\\test_module_Breakeven"); if(handle_m7!=INVALID_HANDLE) on_Breakeven=true; } //--- Minimum allowable volume for trading operationsn min_lot=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double Equity=AccountInfoDouble(ACCOUNT_EQUITY); //--- MODULE 1 if(on_lot) { // If there is an additional module double buffer_m1[]; ArraySetAsSeries(buffer_m1,true); if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return; lot=buffer_m1[0]; } //--- MODULE 4 if(on_Trail) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m4[]; ArraySetAsSeries(buffer_m4,true); if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return; double TR=buffer_m4[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } } //--- MODULE 7 if(on_Breakeven) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m7[]; ArraySetAsSeries(buffer_m7,true); if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return; double TRB=buffer_m7[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } } //--- if(lot<min_lot) lot=min_lot; //--- if(on_trade) { sSignal signal=Buy_or_Sell(); //--- The value of the required and free margin double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- BUY if(signal.Buy) { if(!PositionSelect(_Symbol)) { SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: new position"); } else { if(PositionGetDouble(POSITION_PROFIT)<0) return; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { trade.PositionClose(_Symbol); SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,NormalizeDouble(lot,2),last_tick.ask,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,NormalizeDouble(lot,2),last_tick.ask,0,0,"BUY: reversal"); } } } //--- SELL if(signal.Sell) { if(!PositionSelect(_Symbol)) { SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: new position"); } else { if(PositionGetDouble(POSITION_PROFIT)<0) return; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { trade.PositionClose(_Symbol); SymbolInfoTick(_Symbol,last_tick); if(OrderCalcMargin(ORDER_TYPE_SELL,_Symbol,NormalizeDouble(lot,2),last_tick.bid,margin)) if(margin<Equity) trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,NormalizeDouble(lot,2),last_tick.bid,0,0,"SELL: reversal"); } } } } } //+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { if(on_SL && on_TP) // If there is an additional module { //--- MODULE 2 double buffer_m2[]; ArraySetAsSeries(buffer_m2,true); if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return; double SL=buffer_m2[0]; //--- MODULE 3 double buffer_m3[]; ArraySetAsSeries(buffer_m3,true); if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return; double TP=buffer_m3[0]; //--- Position modification if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_SL)==0) { //--- BUY if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } //--- SELL if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } } } }
Si resulta que la carpeta especificada por el programa no contiene módulos necesarios (archivos), entonces el robot comercial va a usar la funcionalidad predefinida. De esta manera, la falta de los módulos externos no afectará la capacidad funcional del EA de forma crítica.
Módulos más importantes
¿Cómo podemos convertir un programa sólido en un EA multimódulo? Un proyecto de varios módulos se empieza con el análisis de la tarea general y la selección de los fragmentos aislados funcionalmente, que luego pueden ser formados como módulos compilados. Además, es necesario seleccionar las funciones más típicas, que pueden alterar considerablemente el trabajo del EA y se basan en diferentes algoritmos. No es un secreto que en la mayoría de los EAs se aplican los mismos procedimientos:
- módulo de la Gestión de capital (riesgos);
- módulo del seguimiento de la posición (SL y TP);
- módulo del Trailing Stop;
- módulo de la generación de señales comerciales;
- módulo de filtración de señales comerciales.
Existen muchas opciones de implementar cada uno de los módulos mencionados. En este artículo, mostraremos las soluciones más primitivas, puesto que ahora la propia tecnología de la programación modular es más importante para nosotros que la funcionalidad multilínea.
Tecnología del diseño de módulos auxiliares
El módulo auxiliar (externo) es un indicador que ejecuta una determinada función y posiciona los datos de salida en los búferes de indicadores. El módulo principal utiliza estos datos cuando se anecesario. De esta manera, el EA se adapta a las exigencias del trader que utiliza esta estrategia comercial. El mismo EA original puede ser remodificado para cada instrumento financiero o bróker determinado. Prácticamente, el trader obtiene un constructor que permite diseñar una cantidad ilimitada de robots comerciales.
La programación es un proceso operoso. A pesar de la presencia de un componente creativo, incluye muchas operaciones rutinarias y homologables, las cuales es mejor automatizar. La automatización (aparte de lo demás) aumenta el rendimiento de la programación y reduce la cantidad de errores.
Al artículo se le adjunta un generador de los módulos que permite formar de hasta ocho archivos unidos en un proyecto multimódulo apenas en unos segundos. Eso simplifica y acelera considerablemente el proceso del desarrollo y composición (véase el video).
Vídeo 1. Panel del control del generador de proyectos multimódulo
En el panel del control se puede indicar qué módulos ha que generar para el proyecto en desarrollo. En nuestro ejemplo, creamos el proyecto "test". Dependiendo de la combinación de módulos seleccionada, el generador forma el código en el que no va a haber bloques y archivos innecesarios.
Los archivos generados se colocan en la carpeta "Files" (véase Fig. 2). El nombre del módulo principal "test_module_exp.mq5" se compone del nombre del proyecto "test" y el prefijo "_module_exp.mq5". Es necesario moverlo ala carpeta "Experts", y el resto de los archivos de los módulos externos, en la carpeta "Indicators\Market".
Fig. 6. Archivos generados del proyecto "test".
Después de eso, compilamos todos los archivos y empezamos el testeo del proyecto multimódulo.
Vídeo 2. Compilación de archivos generados del proyecto "test"
Para crear el mismo proyecto manualmente, necesitaremos más de una hora, y es evidente que habrá que empezar con el módulo principal. Después de definir los módulos externos que luego se podrá incluir en el proyecto, procedemos al diseño y programación. Lo importante es controlar los valores de los datos de salida de los módulos auxiliares para el módulo principal. Puesto que los módulos son los indicadores, y los búferes de indicadores contienen los valores exclusivamente del tipo real, entonces hay que prever la conversión de las variables del tipo real al tipo correspondiente al algoritmo en el módulo principal.
Es deseable diseñar los módulos externos de tal manera que haya la posibilidad de invocarlos en el módulo principal sin parámetros de entrada, es decir, por defecto. Este mecanismo de la llamada simplifica el diseño del sistema de la gestión de datos externos.
Vamos a considerar en detalle los módulos externos más requeridos en las estrategias comerciales.
Módulo 1: módulo de la Gestión de capital
Este módulo externo calcula el volumen del lote para la apertura de la orden del turno. A continuación, puede ver la versión más simple del cálculo del volumen comercial (en por cientos del dinero disponible en el depósito):
//****** project (module MM): test_module_MM_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module MM" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- input double lot_perc=0.1; // Percentage of Equity value double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double Equity=AccountInfoDouble(ACCOUNT_EQUITY); //--- calculation of the lot of equity Buffer1[0]=NormalizeDouble(Equity*lot_perc/1000.0,2); // Lot size determination function if(Buffer1[0]<0.01) Buffer1[0]=0.01; return(rates_total); };
Si llamamos a este módulo por defecto (sin especificar los valores de parámetros de entrada), el módulo principal recibirá el tamaño del lote permitido para realizar la transacción en el volumen 0,1% de fondos disponibles. Es el ejemplo de la llamada a este módulo desde el programa principal:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double Equity=AccountInfoDouble(ACCOUNT_EQUITY); //--- MODULE 1 if(on_lot) { // If there is an additional module double buffer_m1[]; ArraySetAsSeries(buffer_m1,true); if(CopyBuffer(handle_m1,0,0,1,buffer_m1)<0) return; lot=buffer_m1[0]; } ... }
Ejemplo 2: módulo del seguimiento de la posición (SL, TP y trailing)
Colocación del Stop Loss (SL) y Take Profit (TP) es uno de los modos del seguimiento de la posición abierta. Puesto que en diferentes estrategias comerciales se usan diferentes combinaciones del cálculo y colocación del Sl y TP, se impone la variante con la división en dos módulos: para SL y TP por separado. Pero si decidimos reunir SLy TP en el mismo módulo, habrá que colocar sus valores en búferes de indicadores diferentes.
Módulo de establecimiento de SL:
//****** project (module SL): test_module_SL_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module SL" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double SL=100; // SL in points //--- calculation of the SL Buffer1[0]=SL; return(rates_total); };
Módulo de establecimiento de TP:
//****** project (module TP): test_module_TP_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module TP" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double TP=100; // TP in points //--- calculation of the TP Buffer1[0]=TP; return(rates_total); };
En estos códigos, se muestra una variante más evidente del el cálculo de los valores de SL y TP (en puntos). Mejor dicho, no se calcula, sino se establece con una constante. Además, los valores se indican directamente en el programa, en vez de usar los parámetros de entrada. Eso ha sido hecho con el fin de demostrar la variante de la implementación de los módulos externos sin los datos de entrada. Cualquier programador principiante puede escribir este código sin problema alguno.
Yo recomiendo ubicar la llamada a los módulos considerados en la función OnTrade. Tendremos aproximadamente lo siguiente:
//+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { if(on_SL && on_TP) // If there is an additional module { //--- MODULE 2 double buffer_m2[]; ArraySetAsSeries(buffer_m2,true); if(CopyBuffer(handle_m2,0,0,1,buffer_m2)<0) return; double SL=buffer_m2[0]; //--- MODULE 3 double buffer_m3[]; ArraySetAsSeries(buffer_m3,true); if(CopyBuffer(handle_m3,0,0,1,buffer_m3)<0) return; double TP=buffer_m3[0]; //--- Position modification if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_SL)==0) { //--- BUY if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)+TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)-SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } //--- SELL if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { double priceTP=PositionGetDouble(POSITION_PRICE_OPEN)-TP*_Point; double priceSL=PositionGetDouble(POSITION_PRICE_OPEN)+SL*_Point; trade.PositionModify(_Symbol,NormalizeDouble(priceSL,Digits()),NormalizeDouble(priceTP,Digits())); } } } }
Aparte de los valores SL y TP estáticos establecidos inmediatamente de la apertura de la posición , a menudo se usa el Trailing Stop o SL flotante. En la mayoría de los casos, lo colocan después de que la posición se haga rentable. Vamos a examinar la variante de la implementación más evidente: establecemos la distancia de SL del precio actual en puntos.
//****** project (module Trail): test_module_Trail_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Trail" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double TR=50; // Trail in points //--- calculation of Trail Buffer1[0]=TR; return(rates_total); };
Aquí, igual como en los códigos anteriores para SL y TP, la distancia para el cálculo del Trailing Stop se establece usando una constante para simplificar el programa y su legibilidad.
La llamada al módulo del Trailing Stop debe realizarse en la función OnTick porque el precio actual se cambia en cada tick, y es necesario controlar el nivel del Stop constantemente. Y el módulo principal decide si hay que cambiarlo o no.Al obtener el valor de la distancia en puntos, el EA modifica la posición y mueve el nivel del SL en la dirección del crecimiento del beneficio.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { ... //--- MODULE 4 if(on_Trail) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m4[]; ArraySetAsSeries(buffer_m4,true); if(CopyBuffer(handle_m4,0,0,1,buffer_m4)<0) return; double TR=buffer_m4[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-TR*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+TR*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),0); } } ... }
Existe otro método del seguimiento de la posición, es la colocación del SL en el punto muerto (break even). Cuando SL dispara, la posición se cierra con el resultado nulo o con el beneficio establecido previamente. El módulo puede tener aproximadamente el aspecto siguiente:
//****** project (module Breakeven): test_module_Breakeven_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Breakeven" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double Breakeven=100; // Breakeven in points //--- calculation of the Breakeven Buffer1[0]=Breakeven; return(rates_total); };
En este módulo se establece la distancia entre el precio actual y el precio de la apertura en puntos para colocar SL en el punto muerto. La llamada al módulo del break even también debe ubicarse en la función OnTick.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { ... //--- MODULE 7 if(on_Breakeven) if(PositionSelect(_Symbol)) if(PositionGetDouble(POSITION_PROFIT)>0) { // If there is an additional module double buffer_m7[]; ArraySetAsSeries(buffer_m7,true); if(CopyBuffer(handle_m7,0,0,1,buffer_m7)<0) return; double TRB=buffer_m7[0]; if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) if(PositionGetDouble(POSITION_PRICE_CURRENT)-TRB*_Point>PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)-5*_Point; if(price_SL>PositionGetDouble(POSITION_SL)) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) if(PositionGetDouble(POSITION_PRICE_CURRENT)+TRB*_Point<PositionGetDouble(POSITION_PRICE_OPEN)) { double price_SL=PositionGetDouble(POSITION_PRICE_CURRENT)+5*_Point; if(price_SL<PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL)==NULL) trade.PositionModify(_Symbol,NormalizeDouble(price_SL,Digits()),PositionGetDouble(POSITION_TP)); } } ... }
Ejemplo 3: módulo de la generación de señales comerciales
Puede ser que sea el módulo más complicado en cuanto a la implementación. Debe enviar las señales para realizar las operaciones comerciales: colocación de órdenes, cierre de posiciones, etc. La complejidad de su diseño consiste en el hecho de que casi todos los indicadores deben ser adaptados a las condiciones comerciales. Prácticamente, no existen indicadores con los mismos parámetros de entrada que generan las señales de trabajo para diferentes instrumentos financieros.
Es obvio que el programa principal no debe configurar los módulos de señal por sí mismo, porque se puede crear tantos módulos que el trabajo del proyecto modular será paralizado. Por tanto, los indicadores que generan las señales comerciales tienen que estar preparados previamente antes de ser conectados al proyecto general. Hablaremos de eso más tarde en el apartado dedicado a la optimización de los EAs multimódulos. Ahora, echamos un vistazo al código del módulo de señales comerciales:
//****** project (module signals): test_module_signals_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Trading signals" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //--- Variable for storing the indicator iBands handle int handle_Bands; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { handle_Bands=iBands(_Symbol,_Period,20,0,3.5,PRICE_CLOSE); //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double signal=0.0; ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); //--- Indicator buffers double UpperBuffer[]; double LowerBuffer[]; ArraySetAsSeries(UpperBuffer,true); CopyBuffer(handle_Bands,1,0,1,UpperBuffer); ArraySetAsSeries(LowerBuffer,true); CopyBuffer(handle_Bands,2,0,1,LowerBuffer); //--- calculation of the Trading signals if(high[0]>UpperBuffer[0]) signal=-2.0; if(low[0]<LowerBuffer[0]) signal=2.0; Buffer1[0]=signal; return(rates_total); };
En el búfer de indicadores del módulo de señales, se escriben los siguientes valores:
- 2.0 — si se ha formado la señal BUY;
- 0.0 — si no hay señales comerciales;
- -2.0 — si se ha formado la señal SELL;
Es mejor usar los valores del módulo de señales obtenidos en una función especial del módulo principal, por ejemplo así:
//+------------------------------------------------------------------+ //| Trading signals generator | //+------------------------------------------------------------------+ sSignal Buy_or_Sell() { sSignal res={false,false}; //--- MODULE 5 if(on_signals) { // If there is an additional module double buffer_m5[]; ArraySetAsSeries(buffer_m5,true); if(CopyBuffer(handle_m5,0,0,1,buffer_m5)<0) return(res); if(buffer_m5[0]<-1) res.Sell=true; if(buffer_m5[0]>1) res.Buy=true; } //--- MODULE 6 if(on_Filter) { // If there is an additional module double buffer_m6[]; ArraySetAsSeries(buffer_m6,true); if(CopyBuffer(handle_m6,0,0,1,buffer_m6)<0) return(res); lot=buffer_m6[0]; if(buffer_m6[0]<1) res.Buy=false; if(buffer_m6[0]>-1) res.Sell=false; } //--- //--- Indicator buffers double UpperBuffer[]; double LowerBuffer[]; double MiddleBuffer[]; ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer); ArraySetAsSeries(UpperBuffer,true); CopyBuffer(handle_Bands,1,0,1,UpperBuffer); ArraySetAsSeries(LowerBuffer,true); CopyBuffer(handle_Bands,2,0,1,LowerBuffer); //--- Timeseries double L[]; double H[]; ArraySetAsSeries(L,true); CopyLow(_Symbol,_Period,0,1,L); ArraySetAsSeries(H,true); CopyHigh(_Symbol,_Period,0,1,H); if(H[0]>UpperBuffer[0]&& L[0]>MiddleBuffer[0]) res.Sell=true; if(L[0]<LowerBuffer[0] && H[0]<MiddleBuffer[0]) res.Buy=true; //--- return(res); }
Existe una enorme cantidad de estrategias comerciales, y cada una de ellas tiene sus propias señales. Por eso , es necesario organizar el trabajo del módulo de señales comerciales de tal manera que se ajusten a la estrategia establecida. La documentación del EA debe incluir la estrategia comercial descrita, para que los desarrolladores de los módulos de señal actúen de acuerdo con los requerimientos técnicos del proyecto modular.
Ejemplo 4: módulo de filtración de señales
Con el fin de aumentar la rentabilidad de robots comerciales, a menudo se usan los filtros de señales comerciales. Puede tratarse, por ejemplo, del control de la tendencia, tiempo del trading, noticias, indicadores de señal adicionales, etc.
//****** project (module Filter): test_module_Filter_ind.mq5 //+------------------------------------------------------------------+ //| The program code is generated Modular project generator | //| Copyright 2010-2017, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2010-2017, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Filter" //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //--- double Buffer1[]; //--- Variable for storing the indicator iBands handle int handle_Bands; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { handle_Bands=iBands(_Symbol,_Period,35,0,4.1,PRICE_CLOSE); //--- ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate (const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int &spread[]) { double filtr=0.0; ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); //--- Indicator buffers double MiddleBuffer[]; ArraySetAsSeries(MiddleBuffer,true); CopyBuffer(handle_Bands,0,0,1,MiddleBuffer); //--- calculation of the filter if(high[0]<MiddleBuffer[0]) filtr=2.0; if(low[0]>MiddleBuffer[0]) filtr=-2.0; Buffer1[0]=filtr; return(rates_total); };
Pues bien, hemos considerado las opciones de implementación de los módulos externos y el principio de su integración en el EA modular.
Optimización de EAs multimódulo
La optimización de los EAs multimódulo, probablemente, sea una de las cuestiones fundamentales. Es verdad, ¿cómo se puede optimizar los parámetros de entrada de los módulos externos en el Probador de Estrategias? Si no están escritos en el módulo principal, entonces no hay ninguna manera de hacerlo (o casi ninguna). Se puede establecer discretamente los datos de entrada de los módulos externos y luego testear el EA. Pero este trabajo tan minucioso, y probablemente irracional, no nos conviene. ¿Entonces, qué podemos hacer?
Ésta es una de las posibles opciones: usar los indicadores con optimización automática como módulos externos. Existen muchos artículos y ejemplos sobre la automatización automática. Haré mi aportación en el desarrollo de este tema. Vamos a prestar las ideas del artículo «Prueba visual de la rentabilidad de los indicadores y alertas». Como precio de la ejecución de una transacción virtual, su autor propone usar el valor máximo de la vela para abrir la posición BUY, y el valor mínimo, para SELL. Por tanto, se seleccionan las peores condiciones comerciales, y usando este enfoque en los precios, se realiza la optimización de los parámetros de entrada. Se supone que con estos valores óptimos obtenidos, el resultado no será peor en el trading real (en el mismo intervalo de datos históricos). Mientras que en el trading real, es imposible garantizar el beneficio después de cualquier optimización.
La estrategia de nuestro EA se basa en el trading dentro del canal del indicador Bollinger, con la reversión de las posiciones en sus bandas. Vamos a reemplazar este indicador y construir el canal a base del indicador Envelope: desde el indicador de la media móvil formamos las bandas equidistantes de la MA a una distancia fija. El nuevo indicador de señal va a optimizarse automáticamente antes de ser usado. Como parámetros de entrada, se usarán los valores óptimos con los cuales ha sido mostrado el beneficio máximo. Hay dos parámetros para la optimización: período de la MA y la distancia entre las bandas y la media móvil.
Algoritmo de la creación del indicador de señal con la función de optimización automática:
- Determinamos los parámetros y criterios de la optimización. En nuestro caso, los parámetros serán el período de la MA y la distancia del desplazamiento de las bandas, y el criterio, el beneficio máximo.
- Creamos el bloque de optimización en el indicador. En el ejemplo propuesto, se realiza el repaso completo de los datos de entrada en el intervalo establecido con un paso fijo. El período de la media móvil es de 10 a 100, con el paso de 10. Para el desplazamiento, los valores se repasan en el rango de 1 000 a 10 000, con el paso de 1 000.
//+------------------------------------------------------------------+ //| Copyright 2018, Sergey Pavlov (DC2008) | //| http://www.mql5.com/ru/users/dc2008 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, Sergey Pavlov (DC2008)" #property link "http://www.mql5.com/ru/users/dc2008" #property link "1.00" #property link "Example of a multimodule expert: project test module Trading signals" //--- #include <MovingAverages.mqh> //--- Display indicator in the chart window #property indicator_chart_window //--- Number of buffers to calculate the indicator #property indicator_buffers 1 //--- Number of graphic series in the indicator #property indicator_plots 1 //+------------------------------------------------------------------+ //| Estructura de los resultados de optimización | //+------------------------------------------------------------------+ struct Opt { int var1; // valor óptimo del parámetro 1 int var2; // valor óptimo del parámetro 2 double profit; // beneficio }; //--- double Buffer1[]; bool optimum=false; Opt test={NULL,NULL,NULL}; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { ArraySetAsSeries(Buffer1,true); SetIndexBuffer(0,Buffer1,INDICATOR_DATA); optimum=false; return(INIT_SUCCEEDED); }; //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { double signal=0.0; Buffer1[0]=signal; //--- optimization of input parameters if(!optimum) { ArraySetAsSeries(close,false); int count=rates_total; int total=0; int total_profit=0; for(int d=1000;d<10001;d+=1000) for(int j=10;j<101;j+=10) { double shift=d*_Point; bool open_buy=false; bool open_sell=false; double price_buy=0; double price_sell=0; double profit=0; int order=0; for(int i=j+1;i<count;i++) { double ma=SimpleMA(i,j,close); double sell=ma+shift; double buy=ma-shift; //--- BUY if(buy>close[i] && !open_buy) { price_buy=high[i]+spread[i]*_Point; if(order==0) profit=0; else profit+=price_sell-price_buy; order++; open_buy=true; open_sell=false; } //--- SELL if(sell<close[i] && !open_sell) { price_sell=low[i]-spread[i]*_Point; if(order==0) profit=0; else profit+=price_sell-price_buy; order++; open_sell=true; open_buy=false; } //--- } if(profit>0) if(profit>test.profit) { test.var1=j; test.var2=d; test.profit=profit; total_profit++; } //--- Comment("Optimización de los parámetros de entrada..."," repasos=",total," // rentables =",total_profit); total++; } //--- Print(" Optimización terminada: ",test.var1," ",test.var2); Comment("Optimización terminada"); optimum=true; } //--- if(optimum) if(test.profit>0) { ArraySetAsSeries(close,true); double ma=SimpleMA(0,test.var1,close); double sell=ma+test.var2*_Period; double buy=ma-test.var2*_Period; //--- calculation of the Trading signals if(buy>close[0]) signal=2.0; if(sell<close[0]) signal=-2.0; } //--- Indicator buffers Buffer1[0]=signal; return(rates_total); };
Claro que la optimización requerirá algo de tiempo. Durante este período, el EA no podrá realizar operaciones comerciales. Si el robot modular trabaja 24 horas al día, el retardo debido a la optimización automática no debe afectar considerablemente el tiempo total del trading.
Conclusión
- Escribir un Asesor Experto multimódulo usando MQL5 es posible, y en algunas ocasiones, es conveniente, e incluso, beneficioso.
- En el presente artículo, se muestra una concepción primitiva del trabajo del robót con módulos externos. No obstante, la tecnología de la programación modular permite crear unos proyectos bastante complejos, para el diseño de los cuales se puede acudir a los desarrolladores ajenos. Al crear los módulos, ellos son libres de no descubrir el código, protegiendo de esta manera sus derechos de autor de los algoritmos.
- La cuestión de la optimización de los proyectos modulares es un cabo suelto. La optimización automática de los indicadores de señal, que se usan como módulos de señal o filtros, es un tema que requiere su desarrollo.
Nota: El archivo adjunto al artículo permite generar los códigos fuente del proyecto modular en la configuración necesaria.