Desarrollando los Asesores Expertos multimódulo

30 marzo 2018, 14:43
Sergey Pavlov
0
671

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
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?

  1. Declarar los módulos externos (indicadores), los cuales el usuario podrá aplicar en el futuro. 
  2. Añadir la funcionalidad necesaria para su integración.
  3. 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. 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. 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. 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


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).

Panel del control del generador de proyectos multimódulo

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".

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:

  1. 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.
  2. 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

  1. Escribir un Asesor Experto multimódulo usando MQL5 es posible, y en algunas ocasiones, es conveniente, e incluso, beneficioso.
  2. 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.
  3. 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.

Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/3133

Archivos adjuntos |
Gráfico del balance de multisímbolos en MetaTrader 5 Gráfico del balance de multisímbolos en MetaTrader 5

En este artículo, se muestra el ejemplo de la aplicación MQL con la interfaz gráfica en la que se muestran los gráficos del balance de multisímbolos y reducción del depósito según los resultados de la última prueba.

Simulación de patrones que surgen al comerciar con cestas de parejas de divisas. Parte III Simulación de patrones que surgen al comerciar con cestas de parejas de divisas. Parte III

Terminamos el tema de la simulación de los patrones que surgen al comerciar con cestas de parejas de divisas. En este artículo, presentamos los resultados de la simulación de los patrones que monitorean el movimiento de las divisas de la pareja en relación una a otra.

Creando un feed de noticias personalizado en MetaTrader 5 Creando un feed de noticias personalizado en MetaTrader 5

En el artículo se analiza la posibilidad de crear un feed de noticias flexible, que ofrecezca multitud de opciones para elegir el tipo de noticias y su fuente. El artículo muestra cómo se pueden integrar web API con el terminal MetaTrader 5.

Sincronización de varios gráficos del mismo símbolo en timeframes diferentes Sincronización de varios gráficos del mismo símbolo en timeframes diferentes

Para tomar decisiones sobre la realización de las transacciones, a menudo es necesario analizar simultáneamente los gráficos en el proceso del trading. Además, los gráficos disponen de los objetos del análisis gráfico. Es bastante incómodo colocar los mismos objetos en todos los gráficos. En este artículo, yo propongo automatizar la clonación de los objetos en los gráficos.