English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Crear Multi-Expert Advisors basados en los modelos de trading

Crear Multi-Expert Advisors basados en los modelos de trading

MetaTrader 5Probador | 3 abril 2014, 15:25
2 725 1
Vasiliy Sokolov
Vasiliy Sokolov


Introducción

Las capacidades técnicas del terminal de MetaTrader 5 y su probador de estrategias determinan el funcionamiento y las pruebas de los sistemas de trading multidivisa. La complejidad del desarrollo de dichos sistemas en MetaTrader 4 es muy complicada, en primer lugar, debido a la imposibilidad de probar varias herramientas de trading de modo simultáneo tick por tick. Por otro lado, los recursos del lenguaje MQL4 son limitados y no permiten la organización de estructuras complejas de datos ni la gestión eficiente de estos últimos.

Con el lanzamiento de MQL5, la situación ha cambiado. Dese entonces, MQL5 soporta la metodología orientada a objetos, se basa en un mecanismo de desarrollo de funciones adicionales, e incluso dispone de de un conjunto de clases básicas de librerías estándar para facilitar la tareas diarias de los usuarios; que van desde la organización de los datos hasta las interfaces de trabajo de las funciones estándar del sistema.

Y a pesar de las especificaciones técnicas del probador de estrategias y la posibilidad de usar Expert Advisors multidivisa en el terminal, no hay métodos integrados para poner en paralelo el funcionamiento de un EA de modo simultáneo con varios instrumentos y períodos de tiempo. Al igual que antes, para un funcionamiento más sencillo del EA, hay que ejecutarlo en la ventana del símbolo que determina el nombre del instrumento de trading y su período de tiempo. Como resultado, la metodología de trabajo, aprobada en los tiempos de MetaTrader 4, no permite sacar el máximo provecho del probador de estrategias y del terminal de MetaTrader 5.

La situación se complica por el hecho de que solo se permite una posición acumulativa para cada instrumento, equivalente a la cantidad total de transacciones con el instrumento, sin duda, la transición a una posición neta es correcta y oportuna. La posición neta es la que más se aproxima a la representación perfecta de los intereses del trader en un mercado concreto.

Sin embargo, dicha organización de las transacciones no simplifica ni el proceso del trading ni su visualización. Antes, le bastaba a un EA con elegir su orden abierta (por ejemplo, la orden podría ser identificada mediante el número mágico), y poner en práctica la operación necesaria. Ahora, incluso la falta de una posición neta en un instrumento no quiere decir que no exista en la misma una instancia determinada de un EA en este momento en el mercado.

Los desarrolladores externos ofrecen varios métodos para resolver el problema de la posición neta; a partir de la escritura de programas especiales de gestión de órdenes virtuales (véase el artículo Un gestor de órdenes virtuales para rastrear órdenes dentro del entorno centrado en posiciones de MetaTrader 5)  para incorporar las entradas en una posición añadida, mediante el número mágico(véase El método ideal para calcular el volumen total de una posición mediante el número mágico indicado o El uso de ORDER_MAGIC para el trading con distintos Expert Advisors con un solo instrumento).

No obstante, además de los problemas con la posición añadida, está el problema de la multidivisa, cuando se requiere el mismo EA para operar con múltiples instrumentos. Se puede encontrar la solución a este problema en el artículo Creación de un Expert Advisor que opera con varios instrumentos.

Todos los métodos propuestos funcionan y tienen sus propias ventajas. Sin embargo, su error fatal es que cada uno de estos métodos trata de enfocar el problema desde su propia perspectiva, proporcionando soluciones que son, por ejemplo, adecuadas para el trading simultáneo mediante varios Expert Advisors con un instrumento único, pero no son adecuadas para soluciones multidivisa.

En este artículo se pretende resolver todos los problemas con una sola solución. El uso de esta solución puede resolver el problema de las divisas múltiples e incluso las pruebas multisistema de interacción entre distintos Expert Advisors con un solo instrumento. Esto puede parecer difícil, o incluso imposible de alcanzar, pero en realidad es mucho más fácil.

Simplemente imagine su único EA operando con docenas de estrategias de trading, con todos los instrumentos disponibles, y todos los períodos de tiempo posibles.  Además, se prueba el EA fácilmente en el probador, y para todas las estrategias que lo componen, dispone de uno o varios sistemas funcionales de gestión de dinero.

Por tanto, estas son las principales tareas que tenemos que abordar:

  1. El EA tiene que operar en base a varios sistemas de trading al mismo tiempo. Además, tiene que poder operar con la misma facilidad tanto con un solo sistema de trading como con varios.
  2. Ninguno de los sistemas de trading, implementados en el EA, debe entrar en conflicto con otro. Cada sistema de trading debe manejar únicamente su propia contribución a la posición neta total, y solo a sus propias órdenes;
  3. Cada sistema tiene que poder operar con la misma facilidad tanto con un solo período como con todos los períodos a la vez.
  4. Cada sistema tiene que poder operar con la misma facilidad tanto con un solo instrumento de trading como con todos los instrumentos disponibles a la vez;

Si examinamos con detenimiento la lista de las tareas que tenemos que procesar, llegaremos a una matriz tridimensional. La primera dimensión de la matriz; el número de sistemas de trading, la segunda dimensión; el número de períodos de tiempo, en los cuales tiene que operar el sistema de trading indicado, y la tercera; el número de instrumentos para el sistema de trading. Un simple cálculo muestra que incluso un EA sencillo como el ejemplo de MACD, al trabajar simultáneamente con 8 pares de divisas principales, se obtienen 152 soluciones independientes: 1 EA * 8 pares * 19 períodos de tiempo (no están incluidos los períodos de tiempo semanales y mensuales).

Para un sistema de trading más grande, y con una la cartera de trading de Expert Advisors mucho más amplia, ¡el número de soluciones puede superar fácilmente 500, e incluso 1000! Está claro que es imposible configurar las combinaciones manualmente y luego cargar cada una por separado. Por ello, es necesario implementar un sistema que pueda ajustar automáticamente cada combinación, cargarla en la memoria del EA y entonces, el EA puede operar en base a las reglas de una instancia determinada de esta combinación.


Términos y conceptos

Aquí y en adelante, se va a sustituir la noción "estrategia de trading" por un término más especifico, modelo de trading o simplemente modelo. Un modelo de trading es una clase especial, que se implementa de acuerdo a unas reglas específicas, que describen íntegramente la estrategia del trading. Los indicadores usados en la operación de trading, las condiciones de entrada y salida de la operación, los métodos de gestión del dinero, etc. Cada modelo de trading es abstracto y no define unos parámetros específicos para su funcionamiento.

Un ejemplo sencillo es la estrategia de trading basada en el cruce de dos promedios móviles. Si en los cruces de promedios rápidos, aumenta el lento, abre una transacción para comprar, si en cambio disminuye, abre una transacción para vender. Esta formulación es suficiente para la escritura de un modelo de trading que opera en base a ello.

Sin embargo, cuando hay que describir dicho modelo, es necesario determinar los métodos de los promedios móviles, con su período de promediado, el período de la ventana de datos, y el instrumento, con el cuál va a operar este modelo. En general, este modelo abstracto incluirá unos parámetros que habrá que rellenar una vez creamos una instancia de modelo específica. Es obvio, que con este enfoque, un modelo abstracto puede ser padre de múltiples instancias de modelos, con parámetros distintos.


Rechazo completo de la contabilización de la posición neta 

Muchos desarrolladores tratan de hacer un seguimiento de la posición añadida. No obstante, de lo anterior, podemos apreciar que ni el tamaño de la posición añadida, ni su dinámica, son relevantes para una instancia particular del modelo. El modelo puede ser corto, mientras la posición añadida puede no existir en absoluto (posición añadida neutral). En cambio, la posición añadida puede ser corta, mientras el modelo tenga una posición larga.

De hecho, vamos a ver estos casos con más detalle. Suponemos que cada instrumento opera con tres estrategias de trading distintas, cada una con su propio sistema independiente de gestión de dinero. También suponemos que el primero de los tres sistemas decide comprar tres contratos pendientes, o simplemente coloca una posición corta, con el volumen de los tres contratos. Después de finalizar las transacciones, la posición neta incluirá solamente las transacciones del primer sistema de trading, se le restará el volumen de los tres contratos, o simplemente tres contratos cortos pendientes. Después de algún tiempo, el segundo sistema de trading toma la decisión de comprar 4 contratos del mismo activo.

Como resultado, la posición neta cambiará, y va a contener 1 contrato largo. Esta vez, incluirá las contribuciones de dos sistemas de trading. A continuación, entra en escena el tercer sistema de trading y lleva a cabo una posición corta con el mismo activo, con el volumen de un contrato estándar. La posición neta se convierte en neutral ya que -3 short+ 4 long - 1 short = 0.

¿Significaría la falta de una posición neta que ninguno de los tres sistemas de trading está en el mercado? De ninguna manera. Dos de ellos tienen cuatro contratos pendientes, lo que significa que la liquidación se hará después de un tiempo. Por otra parte, el tercer sistema tiene 4 contratos largos, que aún no se han vendido. La posición neutra significa una verdadera falta de posición en los tres sistemas, solo cuando se completa el pago de los cuatro contratos cortos, y se hace la liquidación de la venta de los cuatro contratos largos.

Por supuesto, podemos reconstruir cada vez la secuencia de todas las operaciones de cada modelo, y así determinar su contribución concreta en el tamaño de la posición actual, pero existe un método mucho más sencillo. En este método hay que abandonar completamente la contabilización de la posición añadida, que puede ser de cualquier tamaño y que puede depender a la vez de factores externos (por ejemplo, trading manual) como internos (el funcionamiento de otros modelos de EA en un instrumento). Puesto que no se puede depender de la posición añadida actual, entonces, ¿cómo contabilizamos las operaciones de una instancia específica del modelo?

El método más sencillo y eficiente sería proporcionar a cada instancia de un modelo su propia tabla de órdenes, que cubriría todas las órdenes; incluyendo las pendientes, y aquellas iniciadas por una transacción o eliminadas. Se almacena una gran cantidad de informaciones acerca de la orden en el servidor. Conociendo el ticket de la orden, podemos obtener casi cualquier información relacionada con la misma, desde el momento de su apertura y hasta su volumen.

Lo único que tenemos que hacer es vincular la orden del ticket con una instancia específica del modelo. Cada instancia de modelo debe contener su propia instancia de una clase especial; es decir, la tabla de órdenes, que contiene una lista de las órdenes actuales, establecidas mediante la instancia del modelo.


Diseño de un modelo de trading abstracto

Vamos a tratar de describir ahora la clase común abstracta del modelo, en la cual se basarán las estrategias del trading. Ya que el EA debe utilizar múltiples modelos (o ilimitados), es obvio que esta clase debe tener una interfaz uniforme a través de la cual recibe señales desde un Expert externo potente.

Por ejemplo, esta interfaz puede ser la función Processing(). En otras palabras, cada clase CModel tendrá su función Processing(). Se va a llamar a esta función con cada tick o en cada minuto, o cuando se produzca un nuevo evento de tipo Trade.

He aquí un ejemplo sencillo para completar esta tarea:

class CModel
{
protected:
   string            m_name;
public:
   void             CModel(){m_name="Model base";}
   bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

cmodel_macd     *macd;
cmodel_moving   *moving;

Vamos a averiguar cómo funciona este código. La clase base CModel contiene una variable protegida de tipo string, su nombre es m_name. La palabra clave "protected" permite el uso de esta variable por los herederos de esta clase, por lo que sus descendientes siempre contendrán esta variable. Además, la clase base define la función virtual Processing(). En este caso, la palabra "virtual" indica que es un contenedor o una interfaz entre el Expert Advisor y la instancia específica del modelo.

Cualquier clase, heredada de CModel, tendrá la garantía de tener la interfaz Processing() para la interacción. Se asigna la implementación del código de esta función a sus descendientes. Esta asignación es obvio, ya que el funcionamiento interno de cada modelo puede ser muy diferente, por lo que no se pueden encontrar generalizaciones comunes a nivel general de CModel.

A continuación, está la descripción de dos clases, cmodel_macd y cmodel_moving. Se generan ambas a partir de la clase CModel, así que cada una cuenta con su propia instancia de la función Processing() y la variable m_name. Tenga en cuenta que la implementación interna de la función Processing() de ambos modelos es distinta. En el primero, consiste en Print ("It is cmodel_macd. Model name is ", m_name), en el segundo es Print("It is cmodel_moving. Model name is ", m_name). A continuación, se crean dos punteros, cada uno de ellos debe apuntar a la instancia específica del modelo, uno a la clase de tipo cmodel_macd, y el otro a la clase de tipo cmodel_moving.

En la función OnInit estos punteros heredan los modelos de clases creados de manera dinámica, después, la función Processing() incluida en cada clase, llama a la función Onevent(). Se anuncian ambos punteros a nivel global, de modo que hasta después de salir de la función OnInit(), no se eliminarán las clases creadas en ella, pero continúan existiendo a nivel global. Ahora, cada cinco segundos, la función OnTimer() tomará una muestra de ambos modelos a la vez, llamando en los mismos a la función Processing() correspondiente.

Este sistema inicial de muestreo de modelos, que acabamos de crear, carece de flexibilidad y escalabilidad. ¿Qué hacemos si queremos trabajar con varias docenas de estos modelos? Trabajar con cada modelo por separada no es conveniente. Sería mucho más fácil reunir todos los modelos en una sola comunidad, por ejemplo, una matriz, y luego recorrer en iteración todos los elementos de esta matriz llamando a la función Processing() de cada uno de estos elementos.

Pero el problema con la organización de las matrices es la necesidad de que los datos que almacenan sean del mismo tipo. En nuestro caso, aunque los modelos cmodel_macd y cmodel_moving se parezcan mucho, no son idénticos, lo que hace que su uso en la matriz sea imposible.

Afortunadamente, las matrices no representan la única manera de resumir datos, hay otras formas de generalizaciones más flexibles y escalables. Una de ellas es la técnica de las listas vinculadas. Su diagrama de trabajo es sencillo. Cada elemento incluido en la lista global debe contener dos punteros. Un puntero apunta al elemento anterior de la lista, y el otro al siguiente. 

Además, conociendo el número del índice del elemento, siempre se puede hacer referencia al mismo. Cuando quiere añadir o eliminar un elemento, basta con reconstruir sus punteros, y los punteros de los elementos adyacentes, por lo que siempre apunta el uno al otro. No es necesario conocer la organización interna de estos conjuntos, basta con entender su dispositivo común.

La instalación estándar de MetaTrader 5 incluye la clase adicional especial CList, que proporciona la posibilidad de trabajar con las listas vinculadas. Sin embargo, el elemento de esta lista solo puede ser un objeto de tipo CObject, ya que son los únicos que tienen punteros especiales para trabajar con listas vinculadas. A su vez, la clase CObject es bastante elemental, siendo solamente una interfaz para la interacción con la clase CList.

Puede ver esto echando un vistazo a su implementación:

//+------------------------------------------------------------------+
//|                                                       Object.mqh |
//|                      Copyright © 2010, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//|                                              Revision 2010.02.22 |
//+------------------------------------------------------------------+
#include "StdLibErr.mqh"
//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Elemento de almacenamiento de la clase base.                             |
//+------------------------------------------------------------------+
class CObject
  {
protected:
   CObject          *m_prev;               // previous list item
   CObject          *m_next;               // next list item

public:
                     CObject();
   //--- methods of access to protected data
   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }
   //--- method of identifying the object
   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
  };
//+------------------------------------------------------------------+
//| Constructor CObject.                                             |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CObject::CObject()
  {
//--- initialize protected data
   m_prev=NULL;
   m_next=NULL;
  }
//+------------------------------------------------------------------+

Como se puede observar, la base de esta clase consiste en dos punteros, para los cuales se han implementado las típicas características.

Veamos ahora la parte más importante. Debido al mecanismo de herencia, es posible incluir esta clase en el modelo de trading, lo que significa que se puede incluir la clase del modelo de trading en una lista de tipo CList. Vamos a intentarlo.

Por tanto, vamos a crear nuestra clase abstracta CModel como descendiente de la clase CObject :

class CModel : public CObject

Puesto que nuestras clases cmodel_moving y cmodel_average están heredadas de la clase CModel class, incluyen los datos y los métodos de la clase CObject, por tanto, se pueden incluir en la lista de tipo CList. A continuación, está el código fuente que crea los dos modelos condicionales de trading, los coloca en la lista, y toma muestras de modo secuencial a cada tick:

//+------------------------------------------------------------------+
//|                                            ch01_simple_model.mq5 |
//|                            Copyright 2010, Vasily Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Vasily Sokolov (C-4)."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Arrays\List.mqh>

// Base model
class CModel:CObject
{
protected:
   string            m_name;
public:
        void              CModel(){m_name="Model base";}
        bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

//Create list of models
CList *list_models;

void OnInit()
{
   int rezult;
   // Great two pointer
   cmodel_macd          *m_macd;
   cmodel_moving        *m_moving;
   list_models =        new CList();
   m_macd   =           new cmodel_macd();
   m_moving =           new cmodel_moving();
   //Check valid pointer
   if(CheckPointer(m_macd)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_macd);
      if(rezult!=-1)Print("Model MACD successfully created");
      else          Print("Creation of Model MACD has failed");
   }
   //Check valid pointer
   if(CheckPointer(m_moving)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_moving);
      if(rezult!=-1)Print("Model MOVING AVERAGE successfully created");
      else          Print("Creation of Model MOVING AVERAGE has failed");
   }
}

void OnTick()
{
   CModel               *current_model;
   for(int i=0;i<list_models.Total();i++){
      current_model=list_models.GetNodeAtIndex(i);
      current_model.Processing();
   }
}

void OnDeinit(const int reason)
{
   delete list_models;
}

Una vez se compila y ejecuta este programa, deben aparecer unas líneas parecidas en el diario "Expert", indicando el funcionamiento normal del EA.

2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Prosessing Moving Average...
2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Processing MACD Model...
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MOVING AVERAGE was created successfully
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MACD was created successfully  

Analicemos en detalle cómo funciona este código. Como se ha mencionado antes, nuestro modelo básico de trading CModel se deriva de la clase CObject, que nos proporciona el derecho de incluir los descendientes del modelo básico en la lista de tipo CList:

rezult=list_models.Add(m_macd);
rezult=list_models.Add(m_moving);

Para organizar los datos hay que trabajar con punteros. Una vez creados los punteros de los modelos concretos a nivel local de la función OnInit() e introducidos en la lista global list_models, ya no hacen falta, y se pueden destruir tranquilamente, además de las otras variables de esta función.

En general, una característica destacada del modelo propuesto es que solo la variable global (además de las propias clases del modelo) es una lista vinculada de forma dinámica a estos modelos. De modo que, desde el principio, hay un alto grado de compatibilidad con el encapsulado del proyecto.

Si falla la creación del modelo por algún motivo (por ejemplo, se enumeran los valores de los parámetros necesarios de forma errónea), no se añadirá este modelo a la lista. Esto no afectará al funcionamiento general del EA, ya que manejará solamente los modelos que se agregaron correctamente a la lista.

Se muestrean los modelos creados mediante la función OnTick(). Consiste en un bucle "for". Se determina el número de elementos en este bucle, luego se recorre continuamente en serie desde el primer elemento del bucle (i = 0) hasta el último (i <list_models.Total();i++):

CModel               *current_model;
for(int i=0;i<list_models.Total();i++){
   current_model=list_models.GetNodeAtIndex(i);
   current_model.Processing();
}

Se usa el puntero de la clase base CModel como un adaptador universal. Esto garantiza que cualquier función soportada por este indicador estará disponible para los modelos derivados. En este caso, solo necesitamos la función Processing(). Cada modelo posee su propia versión de Processing(), y su implementación puede ser distinta a la de otros modelos con funciones similares. No hace falta anular esta función, ya que solo puede existir en un formato: no dispone de parámetros de salida y devuelve un valor de tipo booleano.

Las tareas que recaen sobre los "hombros" de esta función son muy amplias:

  1. La función debe determinar de manera independiente la situación actual del mercado en base a sus propios modelos de trading.
  2. Después de tomar la decisión de entrar al mercado, la función debe calcular de forma independiente la cantidad necesaria de la garantía implicada en la transacción (el margen), el volumen de la transacción y el valor de la máxima pérdida posible o el nivel de beneficio.
  3. El comportamiento del modelo debe estar asociado con su funcionamiento anterior. Por ejemplo, si hay una posición corta, iniciada por el modelo, su construcción en el futuro sería imposible. Se deben llevar a cabo todas estas comprobaciones en la función Processing() .
  4. Todas estas funciones deben poder acceder a los parámetros comunes, como por ejemplo, el estado de la cuenta. En base a estos datos, esta función debería llevar a cabo su propia gestión de dinero, mediante los parámetros incluidos en su modelo. Por ejemplo, si se hace la gestión de dinero en uno de los modelos a través de los procedimientos de la formula óptima f, entonces su valor debe ser diferente para cada uno de sus modelos.

Obviamente, la función Processing() , debido a la magnitud de las tareas que tiene que llevar a cabo, dependerá del mecanismo desarrollado de las clases adicionales, aquellas que están incluidas en MetaTrader 5, así como de aquellas designadas concretamente para esta solución.

Como se puede apreciar, se delega la mayor parte del trabajo en unas instancias específicas del modelo. El nivel externo del EA proporciona el control de cada modelo a la vez, y se concluye su trabajo con esto. Lo que se hará mediante el modelo específico dependerá de su lógica interna.

En general, se puede ilustrar el sistema de interacción que hemos construido con el siguiente diagrama:

Tenga en cuenta que a aunque la clasificación de los modelos, como se muestra en el código anterior, se lleve a cabo en la función OnTick(), no es necesario que se haga así. Se puede colocar fácilmente el bucle de la clasificación en cualquier otra función, tales como OnTrade() u OnTimer()

 

La tabla de las órdenes virtuales; la base del modelo

Una vez hayamos combinado todos los modelos de trading en una sola lista, podemos describir el proceso de la operación de trading. Volvamos a la clase CModel y tratemos de completarla con datos y funciones adicionales, y que podrían basarse en el proceso de trading.

Como se mencionó anteriormente, el nuevo paradigma de la posición neta define las distintas reglas para trabajar con órdenes y transacciones. En MetaTrader 4, se acompaña cada transacción por su orden, que está en la pestaña "Trade" desde el momento de su emisión y hasta la finalización de la orden o el cierre de la transacción, iniciada por dicha orden.

En MetaTrader 5 solo existen las órdenes pendientes hasta el momento de la finalización efectiva de la transacción. Después de la transacción, o de la implementación de su entrada en el mercado, se envían estas órdenes al historial de órdenes, que se almacena en el servidor de trading. Esta situación crea incertidumbre. Supongamos que el EA ha colocado una orden y que se ha ejecutado. La posición abierta ha cambiado. Después de algún tiempo, el EA tiene que cerrar su posición.

No se puede cerrar una orden especifica, tal y como se podía hacer en MetaTrader 4 , y debido a la falta del concepto mismo del cierre de órdenes, podemos cerrar la posición o parte de la misma. La pregunta es, ¿qué parte de la posición hay que cerrar? Como alternativa, podemos recorrer todas las órdenes del historial, seleccionar las que ha emitido el EA, y luego correlacionar estas órdenes con la situación actual del mercado y, si es necesario, sus órdenes contrarias. Este método contiene muchas dificultades.

Por ejemplo, ¿cómo podemos determinar que las órdenes no estaban ya bloqueadas en el pasado? Podemos escoger un método diferente, asumiendo que la posición actual pertenece exclusivamente al EA actual. Solo se puede usar esta opción si pretendemos operar con un EA, haciendo trading con una estrategia. Todos estos métodos no son capaces de resolver a la perfección los retos que afrontamos.

La solución más obvia y sencilla consiste en almacenar toda la información necesaria relativa a las órdenes del modelo actual (es decir, las órdenes que no están bloqueadas por las transacciones contrarias) en el modelo mismo.

Por ejemplo, si el modelo emite una orden, se registra su ticket en una parte especial de la memoria de este modelo, por ejemplo, se puede organizar con la ayuda de un sistema de listas vinculadas que ya conocemos.

Conociendo la orden del ticket, podemos encontrar casi cualquier información acerca de la misma, así que todo lo que necesitamos es vincular la orden del ticket con el modelo que la ha emitido. Vamos a guardar la orden del ticket en la clase especial CTableOrder. Además del ticket, puede contener las informaciones más importantes, por ejemplo, el volumen de las órdenes, el tiempo de su instalación, el número mágico, etc.

Vamos a ver la estructura de esta clase:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"

#include <Trade\_OrderInfo.mqh>
#include <Trade\_HistoryOrderInfo.mqh>
#include <Arrays\List.mqh>
class CTableOrders : CObject
{
private:
   ulong             m_magic;       // Magic number of the EA that put out the order
   ulong             m_ticket;      // Ticket of the basic order
   ulong             m_ticket_sl;    // Ticket of the simulated-Stop-Loss order, assigned with the basic order
   ulong             m_ticket_tp;    // Ticket of the simulated-Take-Profit, assigned with the basic order
   ENUM_ORDER_TYPE   m_type;         // Order type
   datetime          m_time_setup;  // Order setup time
   double            m_price;       // Order price
   double            m_sl;          // Stop Loss price
   double            m_tp;          // Take Profit price
   double            m_volume_initial;  // Order Volume
public:
                     CTableOrders();
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);
   double            StopLoss(void){return(m_sl);}
   double            TakeProfit(void){return(m_tp);}
   ulong             Magic(){return(m_magic);}
   ulong             Ticket(){return(m_ticket);}
   int               Type() const;
   datetime          TimeSetup(){return(m_time_setup);}
   double            Price(){return(m_price);}
   double            VolumeInitial(){return(m_volume_initial);}
};

CTableOrders::CTableOrders(void)
{
   m_magic=0;
   m_ticket=0;
   m_type=0;
   m_time_setup=0;
   m_price=0.0;
   m_volume_initial=0.0;
}

bool CTableOrders::Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit)
{
   if(HistoryOrderSelect(history_order_info.Ticket())){
      m_magic=history_order_info.Magic();
      m_ticket=history_order_info.Ticket();
      m_type=history_order_info.Type();
      m_time_setup=history_order_info.TimeSetup();
      m_volume_initial=history_order_info.VolumeInitial();
      m_price=history_order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

bool CTableOrders::Add(COrderInfo &order_info, double stop_loss, double take_profit)
{
   if(OrderSelect(order_info.Ticket())){
      m_magic=order_info.Magic();
      m_ticket=order_info.Ticket();
      m_type=order_info.Type();
      m_time_setup=order_info.TimeSetup();
      m_volume_initial=order_info.VolumeInitial();
      m_price=order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

int   CTableOrders::Type() const
{
   return((ENUM_ORDER_TYPE)m_type);
}

Al igual que la clase CModel, la clase CTableOrders se hereda a partir de CObject. Justo como las clases de los modelos, colocaremos instancias de CTableOrders en la lista ListTableOrders de tipo CList.

Además de su propia orden de ticket (m_tiket), la clase incluye también informaciones acerca del número mágico (ORDER_MAGIC) del EA que la ha emitido, su tipo, precio de apertura, volumen, y el importe estimado de las órdenes superpuestas: stoploss (m_sl) y takeprofit (m_tp). Comentaremos los dos últimos valores por separado. Es obvio que cualquier transacción se cerrará tarde o temprano mediante una transacción opuesta. Se puede iniciar la transacción opuesta en base a la situación actual del mercado o el precio de cierre de la posición a un precio predefinido, cuando haya concluido.

En MetaTrader4, estas "salidas incondicionales de la posición" son tipos especiales de salidas: StopLoss y TakeProfit. La característica que distingue MetaTrader 4 es el hecho de que estos niveles se aplican a unas órdenes determinadas. Por ejemplo, si ocurre una parada en una de las órdenes activas, no afectará a las otras órdenes abiertas en este instrumento.

En MetaTrader 5, esto es algo distinto. Aunque para cada una de las órdenes establecidas, entre otras cosas, podemos especificar el precio de StopLoss y TakeProfit, estos niveles no van a actuar en contra de la orden especificada, en la cual se establecieron estos precios, sino en relación a la posición global en este instrumento.

Supongamos que hay una posición abierta de COMPRA para EURUSD de 1 lote estándar sin los niveles de StopLoss y TakeProfit. Un poco más tarde, se coloca otra orden para EURUSD para comprar 0.1 lote, con los niveles establecidos de StopLoss y TakeProfit - cada uno a una distancia de 100 puntos del precio actual. Después de un tiempo, el precio alcanza el nivel de StopLoss o el nivel de TakeProfit. Cuando esto ocurra, se cerrará toda la posición con un tamaño de 1.1 lote para EURUSD.

En otras palabras, se puede establecer el StopLoss y TakeProfit solamente en función de la posición abierta, y no en contra de una orden concreta. Partiendo de esta base, resulta imposible usar estas órdenes en Expert Advisors multisistema. Esto es obvio, ya que si un sistema coloca sus propios StopLoss y TakeProfit, se aplicarán estos a todos los demás sistemas, y sus intereses ya están incluidos en la posición abierta del instrumento.

Por consiguiente, cada subsistema del EA de trading debe usar solamente sus propios StopLoss y TakeProfit para cada orden individualmente. Además, puede deducirse este concepto del hecho de que incluso en el mismo sistema de trading, las distintas órdenes pueden tener distintos niveles de StopLoss y TakeProfit, y como ya se ha señalado anteriormente, en MetaTrader 5 no se pueden asignar estas salidas a órdenes individuales.

Si colocamos, en las órdenes virtuales, los niveles virtuales de StopLoss y TakeProfit, el EA será capaz de bloquear de forma independiente las órdenes existentes, una vez que el precio alcance o supere estos niveles. Después de bloquear estas órdenes, se pueden eliminar con toda seguridad de la lista de órdenes activas. A continuación, se describe la manera de hacerlo.

Además de sus propios datos, la clase CTableOrders incluye una función muy importante, se trata de la función Add() . Esta función recibe el ticket de la orden, el cual hay que registrar en la tabla. Además del ticket de la orden, esta función recibe los niveles de StopLoss y TakeProfit virtuales. En primer lugar, la función Add() trata de asignar la orden entre las órdenes anteriores que se almacenan en el servidor. Si puede hacerlo, introducirá la información del ticket en la instancia de la clase history_order_info, y luego comenzará a introducir la información en el nuevo elemento TableOrders. A continuación, se añade este elemento a la lista de órdenes. Si no se puede completar la selección de la orden, entonces, puede que estemos tratando con una orden pendiente, con lo cual, tenemos que tratar de asignar esta orden a partir de las órdenes actuales mediante la función OrderSelect(). En el caso de una selección satisfactoria de la orden, se hará lo mismo con las órdenes del historial.

Por el momento, y antes de introducir la estructura que describe el evento Trade, es difícil trabajar con órdenes pendientes de un EA multisistema. Por supuesto, después de la introducción de esta estructura, será posible diseñar Expert Advisors basados en órdenes pendientes. Por otra parte, si hay una tabla de órdenes, se puede llevar prácticamente cualquier estrategia de trading con órdenes pendientes al mercado. Por estos motivos, todos los modelos de trading descritos en este artículo tendrán la ejecución en el mercado (ORDER_TYPE_BUY o ORDER_TYPE_SELL).


CModel; la clase base del modelo de trading

Por consiguiente, cuando se completa el diseño de la tabla de órdenes, llega el momento de describir la versión completa del modelo básico CModel:

class CModel : public CObject
{
protected:
   long              m_magic;
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;
   string            m_model_name;
   double            m_delta;
   CTableOrders      *table;
   CList             *ListTableOrders;
   CAccountInfo      m_account_info;
   CTrade            m_trade;
   CSymbolInfo       m_symbol_info;
   COrderInfo        m_order_info;
   CHistoryOrderInfo m_history_order_info;
   CPositionInfo     m_position_info;
   CDealInfo         m_deal_info;
   t_period          m_timing;
public:
                     CModel()  { Init();   }
                     ~CModel() { Deinit(); }
   string            Name(){return(m_model_name);}
   void              Name(string name){m_model_name=name;}
   ENUM_TIMEFRAMES    Timeframe(void){return(m_timeframe);}
   string            Symbol(void){return(m_symbol);}
   void              Symbol(string set_symbol){m_symbol=set_symbol;}
   bool virtual      Init();
   void virtual      Deinit(){delete ListTableOrders;}
   bool virtual      Processing(){return (true);}
   double            GetMyPosition();
   bool              Delete(ENUM_TYPE_DELETED_ORDER);
   bool              Delete(ulong Ticket);
   void              CloseAllPosition();
   //bool virtual      Trade();
protected:
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);

   void              GetNumberOrders(n_orders &orders);
   bool              SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, double lot,
                              double price, double stop_loss, double take_profit, string comment);
};

Los datos de esta clase incluyen las constantes esenciales de cualquier modelo de trading.

Se trata del número mágico (m_magic), el símbolo en el cual se inicia el modelo, (m_symbol) el período de tiempo (m_timeframe), y el nombre del modelo de trading más utilizado (m_name).

Además, el modelo incluye la clase de órdenes (CTableOrders * table), que ya conocemos, y la lista dónde se guardan las instancias de esta tabla, una copia para cada orden (CList*ListTableOrders). Puesto que todos los datos se van a crear de manera dinámica, cuando sea necesario, se llevará a cabo el funcionamiento de estos datos mediante punteros.

Lo siguiente es la variable m_delta. Esta variable debe guardar un coeficiente especial para el cálculo del lote actual en las fórmulas de la gestión de dinero. Por ejemplo, para un porcentaje fijo de la fórmula de capitalización, esta variable puede almacenar una parte de la cuenta, que se puede arriesgar, para la instancia con un riesgo del 2% de la cuenta, esta variable debe ser igual a 0,02. Para los métodos más agresivos, por ejemplo, para el método óptimo f, esta variable debe ser mayor.

Lo importante de esta variable es que permite una selección individual del riesgo para cada modelo, que es parte de un solo EA. Si no se usa la fórmula de capitalización, no hará falta rellenarla. Por defecto, es igual a 0,0.

El siguiente paso consiste en incluir todas las clases de trading adicionales, que están diseñadas para facilitar la recepción y el procesamiento de toda la información necesaria, que va desde la información de la cuenta hasta la información de la posición. Se entiende que las derivadas de los modelos concretos de trading requieren el uso de las clases adicionales de forma activa, pero no es el caso con las características ordinarias de tipo OrderSelect u OrderSend.

Hay que describir la variable m_timing por separado. Durante el proceso de funcionamiento del EA, es necesario llamar a ciertos eventos durante ciertos intervalos de tiempo. La función OnTimer() no es adecuada para ello, ya que pueden existir distintos modelos en distintos intervalos de tiempo.

Por ejemplo, hay que llamar a algunos eventos con cada barra nueva. Para el modelo de trading con un gráfico horario, hay que llamar a estos eventos a cada hora y con un modelo de trading diario, a cada nueva barra diaria. Está claro que estos modelos tienen una configuración de tiempo distinta, y hay que almacenar cada una, respectivamente, en su propio modelo. La estructura t_period, incluida en la clase CModel, nos permite almacenar estas configuraciones por separado, cada una en su modelo.

La estructura es la siguiente:

struct t_period
{
   datetime m1;
   datetime m2;
   datetime m3;
   datetime m4;
   datetime m5;
   datetime m6;
   datetime m10;
   datetime m12;
   datetime m15;
   datetime m20;
   datetime m30;
   datetime h1;
   datetime h2;
   datetime h3;
   datetime h4;
   datetime h6;
   datetime h8;
   datetime h12;
   datetime d1;
   datetime w1;
   datetime mn1;  
   datetime current; 
};

Como se puede observar, incluye el listado de los períodos de tiempo comunes. Para ver si hay una nueva barra, hay que comparar el tiempo de la última barra con el tiempo almacenado en la estructura t_period. Si no coinciden los tiempos, hay una nueva barra, y hay que actualizar el tiempo en la estructura al tiempo de la barra actual y devolver un resultado positivo (true). Si el tiempo del última barra es igual al tiempo de la estructura, se trata de una barra nueva que todavía no había ocurrido, y hay que devolver un resultado negativo (false).

A continuación se muestra la función que trabaja en base al algoritmo descrito:

bool timing(string symbol, ENUM_TIMEFRAMES tf, t_period &timeframes)
{
   int rez;
   MqlRates raters[1];
   rez=CopyRates(symbol, tf, 0, 1, raters);
   if(rez==0)
   {
      Print("Error timing");
      return(false);
   }
   switch(tf){
      case PERIOD_M1:
         if(raters[0].time==timeframes.m1)return(false);
         else{timeframes.m1=raters[0].time; return(true);}
      case PERIOD_M2:
         if(raters[0].time==timeframes.m2)return(false);
         else{timeframes.m2=raters[0].time; return(true);}
      case PERIOD_M3:
         if(raters[0].time==timeframes.m3)return(false);
         else{timeframes.m3=raters[0].time; return(true);}
      case PERIOD_M4:
         if(raters[0].time==timeframes.m4)return(false);
         else{timeframes.m4=raters[0].time; return(true);}
     case PERIOD_M5:
         if(raters[0].time==timeframes.m5)return(false);
         else{timeframes.m5=raters[0].time; return(true);}
     case PERIOD_M6:
         if(raters[0].time==timeframes.m6)return(false);
         else{timeframes.m6=raters[0].time; return(true);}
     case PERIOD_M10:
         if(raters[0].time==timeframes.m10)return(false);
         else{timeframes.m10=raters[0].time; return(true);}
     case PERIOD_M12:
         if(raters[0].time==timeframes.m12)return(false);
         else{timeframes.m12=raters[0].time; return(true);}
     case PERIOD_M15:
         if(raters[0].time==timeframes.m15)return(false);
         else{timeframes.m15=raters[0].time; return(true);}
     case PERIOD_M20:
         if(raters[0].time==timeframes.m20)return(false);
         else{timeframes.m20=raters[0].time; return(true);}
     case PERIOD_M30:
         if(raters[0].time==timeframes.m30)return(false);
         else{timeframes.m30=raters[0].time; return(true);}
     case PERIOD_H1:
         if(raters[0].time==timeframes.h1)return(false);
         else{timeframes.h1=raters[0].time; return(true);}
     case PERIOD_H2:
         if(raters[0].time==timeframes.h2)return(false);
         else{timeframes.h2=raters[0].time; return(true);}
     case PERIOD_H3:
         if(raters[0].time==timeframes.h3)return(false);
         else{timeframes.h3=raters[0].time; return(true);}
     case PERIOD_H4:
         if(raters[0].time==timeframes.h4)return(false);
         else{timeframes.h4=raters[0].time; return(true);}
     case PERIOD_H6:
         if(raters[0].time==timeframes.h6)return(false);
         else{timeframes.h6=raters[0].time; return(true);}
     case PERIOD_H8:
         if(raters[0].time==timeframes.h8)return(false);
         else{timeframes.h8=raters[0].time; return(true);}
     case PERIOD_H12:
         if(raters[0].time==timeframes.h12)return(false);
         else{timeframes.h12=raters[0].time; return(true);}
     case PERIOD_D1:
         if(raters[0].time==timeframes.d1)return(false);
         else{timeframes.d1=raters[0].time; return(true);}
     case PERIOD_W1:
         if(raters[0].time==timeframes.w1)return(false);
         else{timeframes.w1=raters[0].time; return(true);}
     case PERIOD_MN1:
         if(raters[0].time==timeframes.mn1)return(false);
         else{timeframes.mn1=raters[0].time; return(true);}
     case PERIOD_CURRENT:
         if(raters[0].time==timeframes.current)return(false);
         else{timeframes.current=raters[0].time; return(true);}
     default:
         return(false);
   }
}

De momento, no existe ninguna posibilidad de clasificar las estructuras de modo secuencial. Esta clasificación es necesaria cuando tenemos que crear múltiples instancias en un bucle del mismo modelo de trading, operando con diferentes períodos de tiempo. Así que tuve que escribir una función de clasificación especial con respecto a la estructura t_period.

Este es el código fuente de la función:

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

Todas estas funciones están correctamente combinadas en un solo archivo en la carpeta \\Include. La llamaremos Time.mqh.

Esto es lo que incluirá nuestra clase base CModel:

#incude <Time.mqh>

Además de las funciones get/set de tipo Name(), Timeframe() y Symbol(), la clase CModel incluye funciones complejas de tipo  Init(), GetMyPosition(), Delete(), CloseAllPosition() y Processing(). El nombre de la última función ya le debe resultar familiar. Describiremos su estructura interna más adelante, pero por ahora, vamos a comenzar con la descripción de las funciones principales de la clase base CModel.

La función CModel::Add() crea de forma dinámica una instancia de la clase CTableOrders, y luego la rellena mediante la función apropiada CTabeOrders::Add(). Su principio de funcionamiento ha sido descrito anteriormente. Después de rellenarlo, se incluye este elemento en la lista general de todas las órdenes del modelo actual (ListTableOrders.Add (t)).

En cambio, la función CModel::Delete() elimina el elemento de tipo CTableOrders de la lista de órdenes activas. Para hacerlo, tenemos que indicar el ticket de las órdenes que hay que eliminar. Su principio de funcionamiento es sencillo. La función ordena de manera secuencial toda la tabla de órdenes, en busca de la orden con el ticket correcto. Si encuentra dicho ticket, lo borra.

La función CModel::GetNumberOrders() cuenta el número de órdenes activas y rellena la estructura especial n_orders:

struct n_orders
{
   int all_orders;
   int long_orders;
   int short_orders;
   int buy_sell_orders;
   int delayed_orders;
   int buy_orders;
   int sell_orders;
   int buy_stop_orders;
   int sell_stop_orders;
   int buy_limit_orders;
   int sell_limit_orders;
   int buy_stop_limit_orders;
   int sell_stop_limit_orders;
};

Como se puede observar, después de llamar a la función, podemos averiguar cuántos tipos específicos de órdenes se han establecido. Por ejemplo, para obtener el número de todas las órdenes cortas, hay que leer todos los valores de short_orders de la instancia n_orders.

La función CModel::SendOrder() es la única función básica para el envío real de órdenes al servidor de trading. En lugar de que haya un algoritmo propio a cada modelo para enviar órdenes al servidor, la función SendOrder() define el procedimiento general de estos envíos. Independientemente del modelo, se asocia el proceso de colocación de órdenes con los mismos procesos de comprobación, que se llevan a cabo de un modo eficiente en una ubicación centralizada.

Vamos a familiarizarnos con el código fuente de esta función:

bool CModel::SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, 
                          double lot, double price, double stop_loss, double take_profit, string comment)
{
   ulong code_return=0;
   CSymbolInfo symbol_info;
   CTrade      trade;
   symbol_info.Name(symbol);
   symbol_info.RefreshRates();
   mm send_order_mm;
   
   double lot_current;
   double lot_send=lot;
   double lot_max=m_symbol_info.LotsMax();
   //double lot_max=5.0;
   bool rez=false;
   int floor_lot=(int)MathFloor(lot/lot_max);
   if(MathMod(lot,lot_max)==0)floor_lot=floor_lot-1;
   int itteration=(int)MathCeil(lot/lot_max);
   if(itteration>1)
      Print("The order volume exceeds the maximum allowed volume. It will be divided into ", itteration, " deals");
   for(int i=1;i<=itteration;i++)
   {
      if(i==itteration)lot_send=lot-(floor_lot*lot_max);
      else lot_send=lot_max;
      for(int i=0;i<3;i++)
      {
         //Print("Send Order: TRADE_RETCODE_DONE");
         symbol_info.RefreshRates();
         if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask();
         if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid();
         m_trade.SetDeviationInPoints(ulong(0.0003/(double)symbol_info.Point()));
         m_trade.SetExpertMagicNumber(m_magic);
         rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, 0.0, 0.0, comment); 
         // Sleeping is not to be deleted or moved! Otherwise the order will not have time to get recorded in m_history_order_info!!!
         Sleep(3000);
         if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
         {
               //Print(m_trade.ResultComment());
               //rez=m_history_order_info.Ticket(m_trade.ResultOrder());
               if(op_mode==ORDER_ADD){
                  rez=Add(m_trade.ResultOrder(), stop_loss, take_profit);
               }
               if(op_mode==ORDER_DELETE){
                  rez=Delete(ticket);
               }
               code_return=m_trade.ResultRetcode();
               break;
         }
         else
         {
            Print(m_trade.ResultComment());
         }
         if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED||
            m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED||
            m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY||
            m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS||
            m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME)
         {
            break;
         }
      }
   }
   return(rez);
}

Lo primero que hace esta función es comprobar si es posible procesar el volumen indicado en el servidor de trading. Se lleva a cabo mediante la función CheckLot(). Pueden existir algunas restricciones de trading relativas al tamaño de la posición. Hay que tenerlas en cuenta.

Considere el siguiente caso: hay un límite relativo al tamaño de las posiciones de trading de 15 lotes estándar en ambas direcciones. La posición actual es larga y es igual a 3 lotes. El modelo de trading, basado en el sistema de gestión de dinero, quiere abrir una posición larga con un volumen de 18,6 lotes. La función CheckLot() devolverá el volumen corregido de la transacción. En este caso, será igual a 12 lotes (dado que 3 de los 15 lotes están ya ocupados con otras transacciones). Si la posición abierta actual era corta, en lugar de larga, entonces la función devolverá 15 lotes en lugar de 18,6. Este es el máximo volumen posible de posiciones.

Después de sacar 15 lotes de compra, en este caso, la posición neta será de 12 lotes (3 de venta, 15 de compra). Cuando otro modelo sustituye su posición corta inicial de 3 lotes de compra, la posición abierta toma su máximo valor posible, es decir 15 lotes. No se procesarán las otras señales hasta que el modelo sustituya algunos de los 15 lotes de compra o todos. Se ha superado el volumen que puede requerir la transacción, la función devuelve una constante EMPTY_VALUE, se debe enviar esta señal.

Si la comprobación de la posibilidad de establecer el volumen es satisfactoria, se realizarán los cálculos sobre el valor del margen requerido. Puede que no haya suficientes fondos en la cuenta para el volumen indicado. Para estos casos, existe la función CheckMargin(). Si el margen no es suficiente, intentará corregir el volumen de la orden, de modo que el margen libre actual permita su apertura. Si el margen no es suficiente ni siquiera para abrir el importe mínimo, estamos en el estado de llamada de margen (Margin-Call).

Si en este momento no hay posiciones, y no se usa el margen, solo se trata de un cosa; llamada técnica de margen; - un estado en el cuál es imposible abrir una transacción. No podemos continuar sin añadir dinero a la cuenta. Si todavía hay algún margen en uso, solo nos queda esperar hasta el cierre de la transacción que usa este margen. En cualquier caso, la falta de margen devolverá una constante EMPTY_VALUE.

Una característica destacada de esta función es su capacidad de dividir la orden actual en varias transacciones independientes. Si los modelos de trading usan un sistema de capitalización de la cuenta, la cantidad requerida puede superar fácilmente los límites posibles (por ejemplo, el sistema de capitalización puede requerir la apertura de una transacción con cientos, y a veces miles, de lotes estándar). Obviamente, no se puede garantizar dicho volumen para una sola transacción. En general, las condiciones de trading determinan un tamaño máximo de cien lotes, pero algunos servidores de trading tienen otras restricciones, por ejemplo, en el servidor de los Campeonatos 2010 de MetaQuotes el volumen máximo era de 5 lotes. Está claro que hay que tener en cuenta estas restricciones, y en base a las mismas, calcular correctamente el volumen actual de la transacción.

En primer lugar, se calcula el número de órdenes necesarias para implementar el volumen establecido. Si el volumen establecido no supera la cantidad máxima de la transacción, solo hace falta una pasada para sacar esta orden. Si el volumen deseado de las transacciones supera el máximo volumen posible, divide este volumen en varias partes. Por ejemplo, queremos comprar 11,3 lotes de EURUSD. El tamaño máximo de la transacción en este instrumento es 5,0 lotes. Entonces la función OrderSend divide este volumen en tres órdenes: la primera orden de 5,0 lotes, la segunda orden de 5,0 lotes y la tercera orden de 1,3 lotes.

Por tanto, en lugar de una orden tendremos tres. Se mostrará cada una en la tabla de órdenes, y tendrá cada una su propia configuración por separado, tales como los valores virtuales de Stop Loss y Take Profit, el número mágico, y otros parámetros. El procesamiento de estas órdenes será sencillo, ya que los modelos de trading están diseñados de tal modo que pueden manejar cualquier número de órdenes en sus listas.

De hecho, todas las órdenes tendrán los mismos valores de TakeProfit y StopLoss. Se ordenará cada una de modo secuencial mediante las funciones LongClose y ShortClose. Una vez se producen las condiciones propicias para su cierre, o alcanzan su umbral de SL y TP, se procederá al cierre de todas.

Se envía cada orden al servidor mediante la función OrderSend de la clase CTrade. El detalle más interesante del funcionamiento está oculto a continuación.

Por el hecho de que la asignación de una orden puede ser de dos tipos, se puede enviar la orden para comprar o vender según aparezca una señal, o puede ser una orden para bloquear una anterior. La función OrderSend tiene que saber cuál es el tipo de orden enviada, ya que en realidad es la función que coloca todas las órdenes en la tabla de órdenes, o las retira de la tabla en función de la aparición de ciertos eventos.

Si el tipo de orden que queremos enviar es ADD_ORDER, es decir, una orden independiente, que hay que colocar en la tabla de órdenes, entonces, la función añade informaciones acerca de esta orden en la tabla de órdenes. Si se coloca la orden para sustituir una orden existente (por ejemplo, si se produce un Stop Loss virtual), entonces, tendrá que ser del tipo DELETE_ORDER. Después de su colocación, la función OrderSend retira manualmente la información acerca de la orden con la cual está vinculada mediante la lista de órdenes. Para conseguirlo, la función hereda, además del tipo de orden, un ticket de orden con la cual está vinculada. Si esta es ADD_ORDER, se puede rellenar el ticket con un simple cero.


El primer modelo de trading basado en el cruce de promedios móviles

Hemos discutido todos los elementos esenciales de la clase base CModel. Es el momento de considerar una clase de trading específica.

Para ello, crearemos primero un modelo de trading sencillo, basado en un simple indicador MACD.

Este modelo tendrá siempre una posición larga o una corta. En cuanto la línea rápida cruce la línea lenta hacia abajo, abriremos una posición corta, mientras se cierra la posición larga si la hay. En el caso del cruce hacia arriba, abriremos una posición larga, mientras se cierra la posición corta si la hay. En este modelo no usamos los niveles de Stops de protección y de beneficio.

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model uses MACD indicator.                                      |
//| Buy when it crosses the zero line downward                           |
//| Sell when it crosses the zero line upward                            |
//+----------------------------------------------------------------------+  
struct cmodel_macd_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               fast_ema;
   int               slow_ema;
   int               signal_ema;
};
   
class cmodel_macd : public CModel
{
private:
   int               m_slow_ema;
   int               m_fast_ema;
   int               m_signal_ema;
   int               m_handle_macd;
   double            m_macd_buff_main[];
   double            m_macd_current;
   double            m_macd_previous;
public:
                     cmodel_macd();
   bool              Init();
   bool              Init(cmodel_macd_param &m_param);
   bool              Init(string symbol, ENUM_TIMEFRAMES timeframes, int slow_ma, int fast_ma, int smothed_ma);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_macd_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
};

cmodel_macd::cmodel_macd()
{
   m_handle_macd=INVALID_HANDLE;
   ArraySetAsSeries(m_macd_buff_main,true);
   m_macd_current=0.0;
   m_macd_previous=0.0;
}
//this default loader
bool cmodel_macd::Init()
{
   m_magic      = 148394;
   m_model_name =  "MACD MODEL";
   m_symbol     = _Symbol;
   m_timeframe  = _Period;
   m_slow_ema   = 26;
   m_fast_ema   = 12;
   m_signal_ema = 9;
   m_delta      = 50;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_macd::Init(cmodel_macd_param &m_param)
{
   m_magic      = 148394;
   m_model_name = "MACD MODEL";
   m_symbol     = m_param.symbol;
   m_timeframe  = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_fast_ema   = m_param.fast_ema;
   m_slow_ema   = m_param.slow_ema;
   m_signal_ema = m_param.signal_ema;
   if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}


bool cmodel_macd::CheckParam(cmodel_macd_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
   {
      Print("Symbol ", m_symbol, " selection has failed. Check symbol name");
      return(false);
   }
   if(m_fast_ema == 0)
   {
      Print("Fast EMA must be greater than 0");
      return(false);
   }
   if(m_slow_ema == 0)
   {
      Print("Slow EMA must be greater than 0");
      return(false);
   }
   if(m_signal_ema == 0)
   {
      Print("Signal EMA must be greater than 0");
      return(false);
   }
   return(true);
}

bool cmodel_macd::InitIndicators()
{
   if(m_handle_macd==INVALID_HANDLE)
   {
      Print("Load indicators...");
      if((m_handle_macd=iMACD(m_symbol,m_timeframe,m_fast_ema,m_slow_ema,m_signal_ema,PRICE_CLOSE))==INVALID_HANDLE)
      {
         printf("Error creating MACD indicator");
         return(false);
      }
   }
   return(true);
}

bool cmodel_macd::Processing()
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   return(true);
}

bool cmodel_macd::LongOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   //Print("LongOpened");
   if(m_macd_current>0&&m_macd_previous<=0&&m_orders.buy_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, 0, lot, m_symbol_info.Ask(), 0, 0, "MACD Buy");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::ShortOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   if(m_macd_current<=0&&m_macd_previous>=0&&m_orders.sell_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, 0, lot, m_symbol_info.Bid(), 0, 0, "MACD Sell");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close buy stop-loss");
      }
      if(m_macd_current<0&&m_macd_previous>=0)
      {
         //Print("Long position closed by Order Send");
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close by signal");
      }
   }
   return(rez);
}

bool cmodel_macd::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close buy stop-loss");
      }
      if(m_macd_current>0&&m_macd_previous<=0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close by signal");
      }
   }
   return(rez);
}

La clase base CModel no impone ninguna restricción en cuanto a los contenidos internos de sus descendientes. Su único requisito es el uso de la función de interfaz Processing(). Todos los problemas de la organización interna de esta función se asignan a una clase específica de modelos. No existe ningún algoritmo genérico que se pueda colocar en una función Processing(), así que no hay ninguna razón para imponer a los descendientes de su manera de organizar un modelo concreto. No obstante, se puede estandarizar la estructura interna de casi cualquier modelo. Esta estandarización facilitará enormemente la comprensión de un código externo e incluso el suyo, y hará que el modelo sea más "predecible".

Cada modelo debe tener su propio procedimiento de inicialización. El procedimiento de inicialización del modelo se encarga de cargar los parámetros correctos y necesarios para su funcionamiento, por ejemplo, para que nuestro modelo funcione, tenemos que seleccionar los valores del indicador MACD, obtener el identificador del buffer correspondiente, y por supuesto, determinar el instrumento y el período de tiempo del trading para el modelo. Hay que hacer todo esto mediante el procedimiento de inicialización.

Los procedimientos de inicialización de los modelos son simplemente unos métodos sobrecargados de sus clases. El nombre común de estos métodos es Init. Lo cierto es que MQL5 no soporta la sobrecarga de los constructores, con lo cual no hay ninguna forma de crear el procedimiento de inicialización del modelo en su constructor, ya que los parámetros de entrada van a necesitar la sobrecarga. Aunque nadie nos impide indicar los parámetros básicos en el constructor del modelo.

Cada modelo debe tener tres procedimientos de inicialización. El primero; es el procedimiento de inicialización por defecto. Tiene que configurar y cargar el modelo por defecto, sin pedir los parámetros. Esto puede ser muy conveniente para las pruebas en el modo "tal cual". Por ejemplo, el procedimiento de inicialización para nuestro modelo como la herramienta del instrumento y el período de tiempo del modelo, seleccionarán el gráfico actual y el período de tiempo actual.

La configuración del indicador MACD también será estándar: fast EMA = 12, slow EMA = 26, signal MA = 9; Si el modelo requiere una configuración específica, tal procedimiento de inicialización ya no será adecuado. Nos harán falta los parámetros de los procedimientos de inicialización. Es recomendable (pero no necesario) que haya dos tipos. El primero recibirá sus parámetros al igual que una función ordinaria: Init (type param1, type param2, ..., type paramN). El segundo usará una estructura especial para averiguar y guardar los parámetros del modelo. A veces, esta opción es preferible ya que algunas veces el número de parámetros puede ser elevado, en este caso, sería conveniente pasarlos por las estructuras.

Cada modelo tiene una estructura de parámetros. Se puede asignar cualquier nombre a esta estructura, pero es preferible usar el formato modelname_param. La configuración del modelo es un paso muy importante en el uso de las posibilidades del trading multiperíodo, multisistema y multidivisa. En esta etapa se determina el instrumento sobre el cual opera este modelo y cómo.

Nuestro modelo de trading dispone solo de cuatro funciones de trading. La función para abrir una posición larga: LongOpen, la función para abrir una posición corta: ShortOpen, la función para cerrar una posición larga: LongClosed, la función para cerrar una posición corta: ShortClosed. El funcionamiento de las funciones LongOpen y ShortOpen es trivial. Ambos reciben el valor del indicador MACD de la barra anterior, que se compara con el valor de las dos barras anteriores. Para evitar la "duplicidad del dibujo", no se usará la barra (cero) actual.

Si hay un cruce hacia abajo, la función ShortOpen hace el cálculo mediante las funciones incluidas en el archivo de cabecera mm.mqh, tras el cual se envían los comandos de los lotes necesarios a la función OrderSend. En cambio, en este instante LongClose cierra todas las posiciones largas en el modelo. Esto se debe a que la función clasifica de modo secuencial todas las órdenes abiertas actuales en la tabla de órdenes del modelo. Si se encuentra una orden larga, la cierra la función con una contraorden. Ocurre exactamente lo mismo, pero en sentido contrario con la función ShortClose(). Se puede comprobar el funcionamiento de estas funciones en la lista proporcionada anteriormente.

Analicemos con más detalle cómo se calcula el lote actual en el modelo de trading.

Como se mencionó anteriormente, para esta finalidad usamos unas funciones especiales para la capitalización de la cuenta. Además de las fórmulas de capitalización, estas funciones incluyen la comprobación del cálculo del lote, basada en el nivel del margen utilizado y en las restricciones del tamaño de las posiciones de trading. Pueden existir algunas restricciones para el tamaño de la posición. Hay que tenerlas en cuenta.

Considere el siguiente caso: hay un límite relativo al tamaño de las posiciones de trading de 15 lotes estándar en ambas direcciones. La posición actual es larga y es igual a 3 lotes. El modelo de trading, en función de su propio sistema de gestión de fondos, quiere abrir una posición larga con un volumen de 18,6 lotes. La función CheckLot() devolverá la cantidad corregida del volumen de la orden. En este caso, será igual a 12 lotes (dado que 3 de los 15 lotes están ya ocupados con otras transacciones). Si la posición abierta actual era corta, y no larga, entonces la función debería haber devuelto 15 lotes en lugar de 18,6. Este es el volumen máximo posible de las posiciones.

Después de colocar 15 lotes de compra, en este caso, la posición neta será de 12 lotes (3 de venta, 15 de compra). Cuando otro modelo sustituye su posición corta inicial de 3 lotes de compra, la posición abierta toma su máximo valor posible, es decir 15 lotes. No se procesarán las otras señales hasta que el modelo sustituya algunos de los 15 lotes de compra o todos. Si se agota el volumen disponible para la transacción solicitada, la función devolverá la constante EMPTY_VALUE . Esta señal debe pasar.

Si la comprobación de la posibilidad de establecer el volumen es satisfactoria, se realizarán los cálculos sobre el valor del margen requerido. Tal vez no hay fondos suficientes en la cuenta para el volumen establecido. Para estos casos, existe la función CheckMargin(). Si el margen no es suficiente, intentará corregir el volumen indicado de la transacción, de modo que el margen libre actual permita su apertura. Si el margen no es suficiente ni siquiera para abrir el volumen mínimo, estamos en el estado de llamada de margen (Margin-Call).

Si en este momento no hay posiciones, y no se usa el margen, solo se trata de un cosa; llamada técnica de margen; un estado en el cual es imposible abrir una transacción. No podemos continuar sin añadir dinero a la cuenta. Si todavía hay algún margen en uso, solo nos queda esperar hasta el cierre de la posición que usa este margen. En cualquier caso, la falta de margen devolverá una constante EMPTY_VALUE.

Por lo general, las funciones para el control del tamaño de los lotes no se usan directamente, se les llama mediante funciones especiales para la gestión de fondos. Estas funciones implementan las fórmulas para la capitalización de las cuentas. El archivo mm.mqh incluye solamente dos funciones básicas de gestión de dinero, la primera se calcula en función de una cuota fija de la cuenta, y la otra en función del método propuesto por Ryan Jones, conocido como el método de las proporciones fijas.

El primer método está diseñado para definir una parte fija de la cuenta que se puede arriesgar. Por ejemplo, si el riesgo permitido es del 2% de la cuenta y la cuenta es de 10.000 dólares, entonces el valor máximo del riesgo es de 200 dólares. Para calcular qué lote hay que usar para una parada (Stop) de 200 dólares, hay que conocer exactamente cuál es la distancia máxima que puede alcanzar el precio con respecto a la posición abierta. Por tanto, para calcular el lote mediante esta fórmula necesitamos determinar con precisión el nivel de Stop Loss y el precio correspondiente a la transacción.

El método propuesto por Ryan Jones es distinto del anterior. Su principio es que la capitalización se hace mediante la función definida para un caso particular de una ecuación de segundo grado.

Esta es la solución:

x=((1.0+MathSqrt(1+4.0*d))/2)*Step;

donde: x es el límite inferior de la transición al siguiente nivel d = (Profit / delta) * 2.0 y Step es el paso de delta, por ejemplo 0,1 lote.

Cuanto menor sea el tamaño de delta, mayor será la persistencia de la función en aumentar el número de posiciones. Para más detalles sobre la implementación de esta función, puede consultar el libro de Ryan Jones: The Trading Game: Playing by the Numbers to Make Millions (El juego del trading: Jugando con números para hacer millones).

Si no está previsto usar las funciones de gestión de dinero, hay que llamar a las funciones de control del lote y del margen directamente.

Hasta ahora hemos repasado todos los elementos de nuestro EA básico. Es el momento de recoger los frutos de nuestros esfuerzos.

Para empezar, vamos a crear cuatro modelos. Dejamos que uno de los modelos opere mediante los parámetros por defecto de EURUSD, el segundo también operará en EURUSD, pero con un período de tiempo de 15 minutos. El tercer modelo se iniciará en el gráfico GBPUSD con los parámetros por defecto. El cuarto en USDCHF con un gráfico de dos horas y con los siguientes parámetros: SlowEMA= 6 FastEMA = 12 SignalEMA = 9. El período para la prueba; H1, el modo de prueba; todos los ticks, el intervalo desde 01.01.2010 hasta 01.09.2010.

Pero antes de ejecutar este modelo en cuatro modos diferentes, primero vamos a intentar de probarlo con cada instrumento y período de tiempo por separado.

La siguiente tabla muestra los principales indicadores de la prueba:

Sistema
Número de transacciones.
Beneficio (dólares)
MACD(9,12,26)H1 EURUSD
123
1092
MACD (9,12,26) EURUSD M15
598
-696
MACD(9,6,12) USDCHF H2
153
-1150
MACD(9,12,26) GBPUSD H1
139
-282
Todos los sistemas
1013
-1032

La tabla muestra que el número total de transacciones para todos los modelos debe ser 1013, y el beneficio total 1032 dólares.

Por consiguiente, estos son los mismos valores que deberíamos obtener si probamos estos sistemas al mismo tiempo. Los resultados no tienen que variar, aunque todavía se producen algunas desviaciones sutiles.

Esta es la prueba definitiva:


Como se puede observar, solo hay una transacción menos y el beneficio varía con solo 10 dólares, que corresponden solamente a 10 puntos de diferencia con un lote de 0,1. En el caso de usar el sistema de gestión de dinero, hay que tener en cuenta que los resultados de la prueba combinada van a ser completamente distintos de los resultados de las pruebas individuales de cada modelo. Esto se debe al efecto del balance dinámico de cada sistema, con lo cual cambiarán los valores calculados de los lotes. 

Aunque no tengamos mucho interés en los resultados en sí, hemos creado una estructura compleja del EA, pero muy flexible y manejable. Repasemos rápidamente su estructura.

Para ello, veamos el siguiente diagrama:

Referencia de la clase

El modelo muestra la estructura básica de la instancia del modelo.

Una vez se crea una instancia de clase del modelo de trading, llamamos a la función sobrecargada Init(). Analiza los parámetros necesarios, prepara los datos, carga los identificadores de los indicadores, si hay que utilizarlos. Todo esto ocurre durante la inicialización del EA, es decir, en la función OnInit(). Tenga en cuenta que los datos incluyen instancias de la clase base, diseñadas para facilitar la operación de trading. Se supone que los modelos de trading requieren el uso activo de estas clases en lugar de las funciones estándar de MQL5. Después de la creación satisfactoria y la inicialización de la clase del modelo, se incluye en la liste de modelos CList. Se lleva a cabo la comunicación con la clase mediante el adaptador universal CObject.

Tras producirse los eventos OnTrade() u OnTick(), se ordenan de manera secuencial todas las instancias de los modelos en la lista. Se hace la comunicación con ellas llamando a la función Processing(). Además, llama a las funciones de trading propias de su modelo (el grupo de funciones azul). Su lista y nombres no están estrictamente definido, pero es más cómodo usar nombres estándar como LongOpened(), ShortClosed(), etc. Estas funciones, en base a su propia lógica, eligen el momento para completar la transacción y a continuación envían una petición especial para la apertura o el cierre de la transacción de la función SendOrder().

Esta última lleva a cabo las comprobaciones necesarias y a continuación envía las órdenes al mercado. Las funciones de trading se basan en las funciones adicionales del modelo (grupo verde) que, a su vez, utilizan las funciones adicionales básicas (grupo púrpura). Se representan todas las clases adicionales como instancias de clases en la sección de datos (grupo rosa). Las flechas de color azul oscuro muestran las interacciones entre los grupos.


El modelo de trading basado en el indicador de las bandas de Bollinger

Ahora que ha quedado clara la estructura general de los datos y métodos, vamos a crear otro modelo de trading, basado en los indicadores de tendencia de las bandas de Bollinger. Como base para este modelo de trading usamos un EA sencillo: Un Expert Advisor basado en las bandas de Bollinger de Andrei Kornishkin. Las bandas de Bollinger representan niveles equivalentes a un determinado tamaño de las desviaciones estándar del promedio móvil simple. Se pueden obtener más detalles acerca de la implementación de este indicador en la sección de ayuda para el análisis técnico, adjunta al terminal de MetaTrader5.

El principio del concepto del trading es sencillo: el precio tiene la propiedad de retorno, es decir, si el precio alcanza un nivel determinado, es muy probable que cambie a la dirección opuesta. Se demuestra esta teoría haciendo una prueba de distribución normal de cualquier instrumento de trading: la curva de la distribución normal se extenderá ligeramente. Las bandas de Bollinger determinan los picos más probables de los niveles de precios. Una vez alcanzadas (las bandas de Bollinger superiores o inferiores), es probable que el precio cambie a la dirección opuesta.

Simplificamos ligeramente nuestra estrategia de trading y no vamos a utilizar el indicador adicional; el promedio móvil exponencial doble (Double Exponential Moving Average, o DEMA). Sin embargo, usaremos topes de protección rigurosos; Stop Loss virtual. Estas medidas harán que el proceso de trading sea más estable y, al mismo tiempo, nos ayudan a entender un ejemplo en el cual cada modelo de trading usa su propio nivel independiente de topes de protección.

Para el nivel de los topes de protección, usamos el precio actual más o menos el valor de volatilidad del indicador ATR. Por ejemplo, si el valor actual de ATR es 68 puntos y hay una señal para vender a un precio de 1.25720, el Stop Loss virtual para esta transacción será igual a 1.25720 + 0.0068 = 1.26400. Del mismo modo, pero en la dirección opuesta, se hace para comprar: 1.25720 - 0.0068 = 1.25040.

El código fuente de este modelo es el siguiente:

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model use Bollinger bands.
//| Buy when price is lower than lower band
//| Sell when price is higher than upper band
//+----------------------------------------------------------------------+  
struct cmodel_bollinger_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               period_bollinger;
   double            deviation;
   int               shift_bands;
   int               period_ATR;
   double            k_ATR;
   double            delta;
};
   
class cmodel_bollinger : public CModel
{
private:
   int               m_bollinger_period;
   double            m_deviation;
   int               m_bands_shift;
   int               m_ATR_period;
   double            m_k_ATR;
   //------------Indicators Data:-------------
   int               m_bollinger_handle;
   int               m_ATR_handle;
   double            m_bollinger_buff_main[];
   double            m_ATR_buff_main[];
   //-----------------------------------------
   MqlRates          m_raters[];
   double            m_current_price;
public:
                     cmodel_bollinger();
   bool              Init();
   bool              Init(cmodel_bollinger_param &m_param);
   bool              Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES TimeFrame, double delta,
                          uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_bollinger_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
   bool              CloseByStopSignal();
};

cmodel_bollinger::cmodel_bollinger()
{
   m_bollinger_handle   = INVALID_HANDLE;
   m_ATR_handle         = INVALID_HANDLE;
   ArraySetAsSeries(m_bollinger_buff_main,true);
   ArraySetAsSeries(m_ATR_buff_main,true);
   ArraySetAsSeries(m_raters, true);
   m_current_price=0.0;
}
//this default loader
bool cmodel_bollinger::Init()
{
   m_magic              = 322311;
   m_model_name         =  "Bollinger Bands Model";
   m_symbol             = _Symbol;
   m_timeframe          = _Period;
   m_bollinger_period   = 20;
   m_deviation          = 2.0;
   m_bands_shift        = 0;
   m_ATR_period         = 20;
   m_k_ATR              = 2.0;
   m_delta              = 0;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param)
{
   m_magic              = 322311;
   m_model_name         = "Bollinger Model";
   m_symbol             = m_param.symbol;
   m_timeframe          = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_bollinger_period   = m_param.period_bollinger;
   m_deviation          = m_param.deviation;
   m_bands_shift        = m_param.shift_bands;
   m_ATR_period        = m_param.period_ATR;
   m_k_ATR              = m_param.k_ATR;
   m_delta              = m_param.delta;
   //if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES timeframe, double delta,
                           uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR)
{
   m_magic           = magic;
   m_model_name      = name;
   m_symbol          = symbol;
   m_timeframe       = timeframe;
   m_delta           = delta;
   m_bollinger_period= bollinger_period;
   m_deviation       = deviation;
   m_bands_shift     = bands_shift;
   m_ATR_period      = ATR_period;
   m_k_ATR           = k_ATR;
   if(!InitIndicators())return(false);
   return(true);
}


/*bool cmodel_bollinger::CheckParam(cmodel_bollinger_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)){
      Print("Symbol ", m_symbol, " select failed. Check valid name symbol");
      return(false);
   }
   if(m_ma == 0){
      Print("Fast EMA must be bigest 0. Set MA = 12 (default)");
      m_ma=12;
   }
   return(true);
}*/

bool cmodel_bollinger::InitIndicators()
{
   m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE);
   if(m_bollinger_handle==INVALID_HANDLE){
      Print("Error in creation of Bollinger indicator. Restart the Expert Advisor.");
      return(false);
   }
   m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period);
   if(m_ATR_handle==INVALID_HANDLE){
      Print("Error in creation of ATR indicator. Restart the Expert Advisor.");
      return(false);
   }
   return(true);
}

bool cmodel_bollinger::Processing()
{
   //if(timing(m_symbol,m_timeframe, m_timing)==false)return(false);
   
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   //m_symbol_info.Name(m_symbol);
   //m_symbol_info.RefreshRates();
   //Copy last data of moving average
 
   GetNumberOrders(m_orders);
   
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   if(m_orders.all_orders!=0)CloseByStopSignal();
   return(true);
}

bool cmodel_bollinger::LongOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   //Print("Model Bollinger: ", m_orders.buy_orders);
   bool rezult, time_buy=true;
   double lot=0.1;
   double sl=0.0;
   double tp=0.0;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   //lot=open_mm.optimal_f(m_symbol,OP_BUY,m_symbol_info.Ask(),sl,delta);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::ShortOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, time_sell=true;
   double lot=0.1;
   double sl=0.0;
   double tp;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {   
      sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_BUY)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_SELL)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::CloseByStopSignal(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   bool rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyRates(m_symbol,m_timeframe,0,3,m_raters);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by stop");
         continue;
      }
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by stop");
         continue;
      }
   }
   return(rez);
}

Como se puede observar, el código del modelo de trading es muy parecido al código fuente de la estrategia de trading anterior. El principal cambio es la aparición de las órdenes de Stop virtual y la función cmodel_bollinger:: CloseByStopSignal(), usadas por estos Stops de protección.

De hecho, en el caso de usar Stops de protección, simplemente se deben pasar sus valores a la función SendOrder(). Y la función introducirá estos niveles en la tabla de órdenes. Cuando el precio cruza o toca estos niveles, la función CloseByStopSignal() cerrará la transacción con una orden contraria y la eliminará de la lista de órdenes activas.


La combinación de modelos de trading, instrumentos y períodos de tiempo en una sola entidad

Ahora que tenemos dos modelos de trading, es el momento de probarlos de forma simultánea. Antes de hacer la representación de los modelos, sería muy útil determinar sus parámetros más eficientes. Para ello, tenemos que optimizar cada modelo por separado.

La optimización permitirá saber en qué mercado y con qué período de tiempo el modelo es más eficiente. Vamos a elegir para cada modelo los dos mejores períodos de tiempo y los tres mejores instrumentos. Como resultado, obtenemos 12 soluciones independientes (2 modelos * 3 instrumentos * 2 períodos de tiempo) que se pondrán a prueba todos juntos. Por supuesto, el método de optimización elegido está sujeto al denominado "ajuste" de los resultados, pero esto no es importante para nuestro objetivo.

En el siguiente gráfico se muestran los mejores resultados:

1.1 MACD EURUSD M30

MACD EURUSD M30

1.2 . MACD EURUSD H3


MACD EURUSD H3

1.3 MACD AUDUSD H4

MACD AUDUSD H4

1.4 . MACD AUDUSD H1


MACD AUDUSD H1

1.5 MACD GBPUSD H12


MACD GBPUSD H12

1.6 MACD GBPUSD H6


MACD GBPUSD H6

2.1 Bollinger GBPUSD M15


Bollinger GBPUSD M15

2.2 Bollinger GBPUSD H1


Bollinger GBPUSD H1

2.3 Bollinger EURUSD M30

Bollinger EURUSD M30

 

2.4  Bollinger EURUSD H4


Bollinger EURUSD H4

 

2.5 Bollinger USDCAD M15


Bollinger USDCAD M15

 

2.6 Bollinger USDCAD H2

Bollinger USDCAD H2

Ahora que conocemos los mejores resultados, solo nos queda un poco más por hacer para recopilar los resultados en una sola entidad.

A continuación se muestra el código fuente de la función de carga, que crea los doce modelos de trading anteriores, con los cuales empieza a operar el EA:

bool macd_default=true;
bool macd_best=false;
bool bollinger_default=false;
bool bollinger_best=false;

void InitModels()
{
   list_model = new CList;             // Initialized pointer of the model list
   cmodel_macd *model_macd;            // Create the pointer to a model MACD
   cmodel_bollinger *model_bollinger;  // Create the pointer to a model Bollinger
   
//----------------------------------------MACD DEFAULT----------------------------------------
   if(macd_default==true&&macd_best==false)
    {
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      // Loading of the parameters was completed successfully
      if(model_macd.Init(129475, "Model macd M15", _Symbol, _Period, 0.0, Fast_MA,Slow_MA,Signal_MA))
      { 
      
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Загружаем модель в список моделей
      }
      else
      {
                                 // The loading of parameters was completed successfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//-------------------------------------------------------------------------------------------
//----------------------------------------MACD BEST------------------------------------------
   if(macd_best==true&&macd_default==false)
   {
      // 1.1 EURUSD H30; FMA=20; SMA=24; 
      model_macd = new cmodel_macd; // Initialize the pointer to the model MACD
      if(model_macd.Init(129475, "Model macd H30", "EURUSD", PERIOD_M30, 0.0, 20,24,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " created successfully");
         list_model.Add(model_macd);// load the model into the list of models
      }
      else
      {// Loading parameters was completed unsuccessfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.2 EURUSD H3; FMA=8; SMA=12; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H3", "EURUSD", PERIOD_H3, 0.0, 8,12,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
       {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.3 AUDUSD H1; FMA=10; SMA=18; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd M15", "AUDUSD", PERIOD_H1, 0.0, 10,18,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// The loading of parameters was unsuccessful                       
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.4 AUDUSD H4; FMA=14; SMA=15; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H4", "AUDUSD", PERIOD_H4, 0.0, 14,15,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else{// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.5 GBPUSD H6; FMA=20; SMA=33; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H6, 0.0, 20,33,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was  unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.6 GBPUSD H12; FMA=12; SMA=30; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H12, 0.0, 12,30,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//----------------------------------------------------------------------------------------------
//-------------------------------------BOLLINGER DEFAULT----------------------------------------
   if(bollinger_default==true&&bollinger_best==false)
   {
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger",_Symbol,PERIOD_CURRENT,0,
                             period_bollinger,dev_bollinger,0,14,k_ATR))
      {
         Print("Model ", model_bollinger.Name(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
//--------------------------------------BOLLLINGER BEST-----------------------------------------
   if(bollinger_best==true&&bollinger_default==false)
   {
      //2.1 Symbol: EURUSD M30; period: 15; deviation: 2,75; k_ATR=2,75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,15,2.75,0,14,2.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
              ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.2 Symbol: EURUSD H4; period: 30; deviation: 2.0; k_ATR=2.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_H4,0,30,2.00,0,14,2.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.3 Symbol: GBPUSD M15; period: 18; deviation: 2,25; k_ATR=3,0;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,18,2.25,0,14,3.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.4 Symbol: GBPUSD H1; period: 27; deviation: 2.25; k_ATR=3.75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_H1,0,27,2.25,0,14,3.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.5 Symbol: USDCAD M15; period: 18; deviation: 2.5; k_ATR=2.00;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_M15,0,18,2.50,0,14,2.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.6 Symbol: USDCAD M15; period: 21; deviation: 2.5; k_ATR=3.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_H2,0,21,2.50,0,14,3.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
}

Vamos a probar los doce modelos de forma simultánea:


La capitalización de los resultados

El gráfico de los resultados es impresionante. No obstante, lo importante no es el resultado, sino el hecho de que todos los modelos están operando de forma simultánea, utilizando todos sus propios Stops de protección y todos independientes los unos de los otros. Vamos a tratar de capitalizar el gráfico resultante. Para ello usaremos las funciones estándar de capitalización: el método de la proporción fija y el método de Ryan Jones.

El denominado f óptimo (optimal f) es un caso especial del método de la proporción fija. El principio de este método es asignar a cada transacción un determinado límite de pérdidas igual a un porcentaje de la cuenta. Las estrategias de trading conservadoras suelen aplicar un límite de pérdidas del 2%. Es decir, para 10.000 dólares, se calcula el tamaño de la posición de modo que la pérdida no exceda los 200 dólares después de superar el Stop Loss. No obstante, existe una determinada función del crecimiento del balance final en función del aumento del riesgo. Esta función tiene la forma de campana. Es decir, que en primer lugar aumenta el beneficio total con el incremento del riesgo. Sin embargo, hay un umbral de riesgo para cada transacción, después del cual empieza a disminuir el balance total del beneficio. Este umbral se denomina f óptima.

Este artículo no está dedicado al tema de la gestión de dinero, todo lo que tenemos que conocer para usar el método de la proporción fija es el nivel de Stops de protección y el porcentaje de la cuenta que podemos arriesgar. La implementación de la fórmula de Ryan Jones es diferente. No requiere Stops de protección para su funcionamiento. Dado que el primero de los modelos propuestos (modelo MACD ) es bastante elemental y no tiene Stops de protección, usaremos este método para la capitalización de este modelo. Para el modelo basado en las bandas de Bollinger, usaremos el método de la proporción fija.

Para empezar a usar la fórmula de capitalización, tenemos que rellenar la variable m_delta incluida en el modelo básico.

Al usar la fórmula de proporciones fijas, esta debe ser igual al porcentaje del riesgo para cada transacción. Al usar el método de Rayn Jones, es igual al denominado incremento delta, es decir, a la cantidad de dinero que hay que ganar para obtener un nivel más alto del volumen de la posición.

A continuación se muestra el gráfico de la capitalización:


Como se puede observar, todos los modelos tienen sus propias fórmulas de capitalización (el método de la proporción fija o el método de Ryan Jones).

En el ejemplo proporcionado, hemos usado los mismos valores del riesgo máximo y de delta para todos los modelos. No obstante, podemos elegir los propios parámetros de capitalización para cada modelo. El ajuste fino de cada uno de los modelos no está incluido en el los puntos tratados en este artículo.


Trabajar con órdenes pendientes

Los modelos de trading anteriores no usan las órdenes pendientes. Son órdenes que se ejecutan únicamente cuando se presentan unas reglas y condiciones concretas.

De hecho, se puede ajustar cualquier estrategia de trading para el uso de órdenes en el mercado mediante las órdenes pendientes. Más bien, las órdenes pendientes son necesarias para una mayor fiabilidad en el funcionamiento del sistema, ya que al hacerse el desglose del robot o el dispositivo en el cual funciona, las órdenes pendientes seguirán ejecutando los Stops de protección, o viceversa, entrando al mercado en base a los precios determinados anteriormente.

El modelo de trading propuesto le permite trabajar con órdenes de trading pendientes, aunque el procesamiento del control de su presentación es mucho más complicado en este caso. Para trabajar con estas órdenes, usamos el método sobrecargado Add (COrderInfo & order_info, double stop_loss, double take_profit) de la clase CTableOrders. En este caso, la variable m_type de esta clase contendrá el tipo de orden pendiente apropiado, por ejemplo, ORDER_TYPE_BUY_STOP u ORDER_TYPE_SELL_LIMIT.

Más tarde, cuando se emitirá la orden pendiente, hay que "capturar" el momento en el cual fue accionada para trabajar, o caducará su plazo de relevancia. Esto no es tan sencillo, ya que no basta con controlar el evento Trade, sino que también hace falta saber que es lo que ha desencadenado este evento.

El lenguaje MQL5 está en continuo desarrollo y en la actualidad se está resolviendo el problema de inclusión de una estructura especial en el mismo para explicar el evento Trade. Pero por el momento tenemos que consultar la lista de órdenes en el historial. Si se encuentra en el historial una orden con el mismo ticket que la orden pendiente en la tabla de órdenes, entonces, ha ocurrido algún evento que hay que reflejar en la tabla. 

El código del modelo básico incluye un método especial CModel:: ReplaceDelayedOrders. Este método funciona según el siguiente algoritmo. En primer lugar, se comprueban todas las órdenes activas de la tabla de órdenes. Se compara el ticket de estas órdenes con el ticket de las órdenes del historial (HistoryOrderGetTicket()). Si el ticket de la orden del historial coincide con la orden pendiente de la tabla de órdenes, pero el estado de la orden se ejecuta (ORDER_STATE_PARTIAL u ORDER_STATE_FILLED), entonces el estado de las órdenes pendientes de la tabla de órdenes cambia también a ejecutado.

Además, si la orden no está vinculada con ninguna orden pendiente, simulando el trabajo de Stop Loss y Take Profit, se saca esta orden y se introducen sus tickets en la tabla de valores apropiada (TicketSL, TicketTP). Y las órdenes pendientes, simulando los niveles Stop Loss y Take Profit, se sacan al precio previamente indicado con la ayuda de las variables m_sl y m_tp. Es decir, hay que conocer esto precios en el momento de la llamada del método ReplaceDelayedOrders.

Cabe señalar que el método está adaptado para manejar la situación dónde se saca una orden del mercado, en el cual las órdenes pendientes requeridas son del tipo Stop Loss y Take Profit.

Por lo general, el funcionamiento del método propuesto no es trivial y su uso requiere algunos conocimientos:

bool CModel::ReplaceDelayedOrders(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int history_orders=HistoryOrdersTotal();
   ulong ticket;
   bool rez=false;
   long request;
   total_elements=ListTableOrders.Total();
   int try=0;
   if(total_elements==0)return(false);
   // View every order in the table
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      switch(t.Type())
      {
         case ORDER_TYPE_BUY:
         case ORDER_TYPE_SELL:
             Therefore, they need to be inputted,
            // using the process for pending orders below:
            // the cycle  keeps going, the exit 'break' does not exist!!!
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to the ticket of the pending order 
               // then the pending order has worked and needs to be put out
               // the pending orders, imitating the work of Stop Loss and Take Profit.
               // It is also necessary to change the pending status of the order in the table
               // of orders for the executed (ORDER_TYPE_BUY или ORDER_TYPE_SELL)
               m_order_info.InfoInteger(ORDER_STATE,request);
               if(t.Ticket()==ticket&&

                  (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED))
                  {
                  // Change the status order in the table of orders:
                  m_order_info.InfoInteger(ORDER_TYPE,request);
                  if(t.Type()!=request)t.Type(request);
                  //------------------------------------------------------------------
                  // Put out the pending orders, imitating Stop Loss an Take Profit:
                  // The level of pending orders, imitating Stop Loss and Take Profit
                  // should be determined earlier. It is also necessary to make sure that
                  // the current order is not already linked with the pending order, imitating Stop Loss
                  // and Take Profit:
                  if(t.StopLoss()!=0.0&&t.TicketSL()==0)
                    {
                     // Try to put out the pending order:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put up a pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
                  if(t.TakeProfit()!=0.0&&t.TicketTP()==0){
                     // Attempt to put out the pending order, imitating Take Profit:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                           break;
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
               }
            }
            break;
         
      }
   }
   return(true);
}

Mediante este método, podemos crear fácilmente un modelo basado en órdenes pendientes.


Conclusión

Desafortunadamente, es imposible tratar en un solo artículo todos los matices, retos y beneficios de la metodología propuesta. No hemos tenido en cuenta la serialización de los datos; es un método que le permite almacenar, registrar y recuperar todas las informaciones necesarias sobre el estado actual de los modelos a partir de los archivos de datos. Más allá de nuestras consideraciones, se han mantenido los modelos de trading basados en el trading de las propagaciones (spreads) virtuales. Estos son temas muy interesantes y, seguramente, tienen sus propias soluciones eficientes para los conceptos propuestos.

Nuestra meta principal es el desarrollo de una estructura de datos completamente dinámica y manejable. El concepto de las listas vinculadas logra una gestión eficiente, hace que las estrategias de trading sean independientes y que se puedan personalizar individualmente. Otra ventaja muy importante de este enfoque es que es completamente genérico.

Por ejemplo, en base a esto, basta con crear dos Expert Advisors y colocarlos en el mismo instrumento. Ambos sistemas trabajan únicamente con sus propias órdenes y su propio sistema de gestión de dinero. Por tanto, este sistema admite la compatibilidad hacia atrás. Todo lo que se puede hacer simultáneamente en un solo EA se puede distribuir entre varios robots. Esta propiedad es extremadamente importante cuando se trata de posiciones netas.

El modelo que presentamos no es solo una teoría. Incluye un sistema avanzado de funciones adicionales, las funciones para la gestión de fondos y la comprobación de los requisitos de control marginales. El sistema de envío de órdenes es robusto frente a las recotizaciones y desviaciones (reqoutes y slippages); cuyos efectos se ven con frecuencia en el trading real.

El motor del trading determina el tamaño máximo de la posición y el volumen máximo de transacciones. Un algoritmo especial separa las peticiones de trading en varias transacciones independientes, que se procesan por separado. Además, el modelo presentado ha demostrado su eficacia en el Automated Trading Championship del 2010; se llevan a cabo todas las transacciones con precisión de acuerdo con el plan de trading, se gestionan los modelos de trading, presentados en el Campeonato, con varios grados de riesgo y en distintos sistemas de gestión de dinero.

El método presentado es prácticamente una solución completa para la participación en los Campeonato, y también para el funcionamiento en paralelo con varios instrumentos y períodos de tiempo y varias estrategias de trading. La única dificultad para familiarizarse con este método reside en su complejidad. 

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

Archivos adjuntos |
files-en.zip (18.6 KB)
Ruben Osvaldo Rodriguez
Ruben Osvaldo Rodriguez | 25 jul. 2022 en 06:59

Estimado autor EA y artículos:

Quiero felicitarte ya que encontré tu diseño excelente. :)

Tengo un problema al cargar el código y tratar de probarlo porque la línea:

resultado=lista_modelos.Add(m_macd); //DESDE ARCHIVO modelo_simple.mq5

da un mensaje de error (

conversion is not accessible because of inheritance access

)

al intentar compilar.

Y no pude resolver este error a pesar de navegar por los foros y probar un par de alternativas.

¿Podrías ayudarme si eres tan amable?

Muchas gracias.

Los indicadores de las tendencias menor, intermedia y principal Los indicadores de las tendencias menor, intermedia y principal
El objetivo de este artículo es investigar las posibilidades de la automatización del trading y el análisis, en base a algunos conceptos del libro de James Hyerczyk "Pattern, Price & Time: Using Gann Theory in Trading Systems" (Modelo, precio y tiempo: el uso de la teoría de Gann en los sistemas de trading), en forma de indicadores y Expert Advisors. Sin querer ser exhaustivo, solo investigamos el "Modelo" en este artículo; la primera parte de la teoría de Gann.
Dibujar los canales; visión interna y externa Dibujar los canales; visión interna y externa
Supongo que no es ninguna exageraci&oacute;n decir que los canales representan la segunda herramienta m&aacute;s popular para el an&aacute;lisis del mercado y la toma de decisiones de trading por detr&aacute;s de los promedios m&oacute;viles. Sin profundizar demasiado en los detalles de las estrategias de trading que usan los canales y sus componentes, vamos a hablar de la base matem&aacute;tica y pr&aacute;ctica de la implementaci&oacute;n de un indicador, que dibuja un canal definido por tres extremos en la pantalla del terminal de cliente.
Análisis de los gráficos mediante métodos econométricos Análisis de los gráficos mediante métodos econométricos
En este artículo se describen los métodos econométricos de análisis, el análisis de la correlación y el análisis de la varianza condicional en particular. ¿Cuáles son les beneficios del método descrito en este artículo? El uso de los modelos GARCH no lineales permite la representación formal de las series analizadas desde un punto de vista matemático y crear predicciones para un número determinado de pasos.
Cálculos paralelos en MetaTrader 5 Cálculos paralelos en MetaTrader 5
El tiempo ha tenido un gran valor a lo largo de la historia de la humanidad, y nos esforzamos en no desperdiciarlo innecesariamente. En este artículo, se le va a mostrar cómo acelerar el funcionamiento de su Expert Advisor si su ordenador dispone de un procesador de núcleo múltiple. Además, la implementación del método propuesto no requiere el conocimiento de ningún otro lenguaje aparte de MQL5.