English Русский Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición (Parte 32): Sistema de órdenes (I)

Desarrollo de un sistema de repetición (Parte 32): Sistema de órdenes (I)

MetaTrader 5Ejemplos | 1 marzo 2024, 16:09
432 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior "Desarrollo de un sistema de repetición (Parte 31): Proyecto Expert Advisor — Clase C_Mouse (V)", desarrollamos la parte base para usar el mouse en el sistema de repetición/simulador. Allí, no agregué el problema de la rueda del mouse, ya que al principio no vi la necesidad de usar dicha función. Con esto ya podemos empezar a trabajar en la parte que, realmente y sin lugar a dudas, es considerablemente bastante complicada. Y eso es, por decir lo menos, lo que realmente tendremos y necesitaremos implementar, tanto en términos de código como en términos de otras cosas relacionadas. Definitivamente y sin lugar a dudas, es lo más desafiante de todo el sistema de repetición/simulador. Realmente no es posible realizar ningún tipo de estudio de forma práctica y sencilla sin contar esta parte: el sistema de órdenes.

De todas las cosas desarrolladas hasta ahora, esta, como seguramente también notarás y con el tiempo estarás de acuerdo, es la más desafiante de todas. Lo que tenemos que hacer es algo simple: hacer que nuestro sistema simule lo que hace un servidor comercial en la práctica. Esto de tener que implementar una forma de simular exactamente lo que haría el servidor comercial parece simple. Al menos en palabras. Pero necesitamos hacer esto de manera que, para el usuario del sistema de repetición/simulación, todo suceda de la manera más invisible o transparente posible. De hecho, el usuario no puede, al utilizar el sistema, diferenciar entre un sistema real y uno simulado. Pero esta no es realmente la parte más complicada. La parte realmente más complicada, y mucho más complicada, es permitir que el Expert Advisor sea el mismo, en cualquier tipo de situación.

Decir que el Expert Advisor debe ser el mismo implica no compilarlo para usarlo en el simulador y luego compilarlo nuevamente para usarlo en el mercado real. Hacer esto sería mucho más fácil y sencillo en términos de programación, pero generaría problemas en cuestiones relacionadas con el hecho de que el usuario tendría que seguir recompilando el EA todo el tiempo. No queremos en absoluto que esto suceda o que sea necesario. Lo que queremos y haremos es crear un Expert Advisor y compilarlo solo una vez. Una vez hecho esto, podremos utilizarlo tanto en el mercado real como en el simulador ya construido.

La parte responsable de utilizar el Expert Advisor en el mercado real, incluso en una cuenta demo, es la parte más fácil de implementar. Entonces comenzaremos con ella. Un detalle: empezaremos por el modelo más básico. Poco a poco iremos complejizando cada vez más el EA, hasta el punto de que podrá hacer lo que realmente queremos, que será usarlo junto con la repetición/simulador, y en una cuenta demo o cuenta real. Lo primero que haremos será tomar prestado gran parte del código que ya he explicado en otros artículos que se han publicado aquí en la comunidad. Estos artículos son míos, por lo que no veo ningún problema al utilizar dicha información. Bien, haremos algunos cambios para que el sistema sea lo más flexible, modular, robusto y estable posible. De lo contrario, podríamos entrar en un laberinto y acabar, en algún momento, en un callejón sin salida, imposibilitando en todos los sentidos que el sistema avance en su desarrollo.

Para empezar y esto será sólo el principio del principio ya que la tarea es muy complicada. En primer lugar, necesitará saber cómo se implementa actualmente el sistema de herencia de clases en el Asesor Experto, que ya estamos recopilando en algunos artículos, en la misma secuencia. Este esquema se puede ver en la figura 01:

Figura 01

Figura 01: Esquema actual de herencia de clases del EA

Pero, aunque este esquema funciona muy bien para lo que se ha hecho hasta ahora, está lejos de ser adecuado a lo que realmente necesitamos. No porque sea complicado agregar la clase C_Orders en este esquema de herencia. En realidad, esto podría hacerse haciendo que la clase C_Orders herede la clase C_Study. Pero no quiero que esto suceda, y la razón se debe a una cuestión muy práctica que a veces es bastante ignorada por la mayoría de los programadores que trabajan con POO (programación orientada a objetos). Esta cuestión se conoce como: Encapsulación. En otras palabras, sólo sepa lo que necesita para hacer su trabajo. Cuando creamos una jerarquía de clases, no debemos (y éste es el término) permitir que algunas clases sepan más de lo que realmente necesitan saber. Siempre debemos dar preferencia a programar todo de tal manera que cada una de las clases sepa sólo lo que realmente necesita saber para poder realizar su tarea. Por eso, mantener la encapsulación y al mismo tiempo agregar la clase C_Orders al esquema mostrado en la figura 01 es prácticamente insostenible. Por lo tanto, la mejor alternativa para solucionar este problema es eliminar la clase C_Terminal del bloque de herencia, como se ve en la figura 01, y enviarla a este mismo bloque como argumento o parámetro que se puede utilizar de una forma mucho más adecuada. Entonces quien tendrá control sobre quién recibirá tal o cual información será el código del Expert Advisor, y esto será importante más adelante, ya que así podremos mantener la encapsulación de la información.

Así, el nuevo esquema de clases, incluido el que vamos a crear en este artículo, tendrá el aspecto que se muestra en la figura 02.

Figura 02

Figura 02: Nuevo esquema de herencia

En este nuevo esquema, las clases sueltas serán realmente accesibles y solo se podrá acceder a ellas si el código del EA lo permite. Como ya te estarás imaginando, necesitarás realizar pequeños cambios en el código existente. Pero estos cambios no afectarán mucho a todo el sistema. De esta manera los revisaré rápidamente, sólo para mostrarles lo que ha cambiado.


Preparación del terreno

Lo primero que hay que hacer es crear una enumeración dentro de la clase C_Terminal. Esto se puede ver en el siguiente fragmento:

class C_Terminal
{

       protected:
      enum eErrUser {ERR_Unknown, ERR_PointerInvalid};

// ... Código interno ...

};

Esta enumeración nos permitirá indicar, a través de la variable _LastError, cuándo se produce un error en el sistema, sea cual sea el motivo. Por el momento, sólo definiremos estos dos tipos de errores.

En esta etapa, vamos a modificar la clase C_Mouse para que ahora tenga algunas cosas en su código que son un poco diferentes. No entraré en demasiados detalles, ya que los cambios no afectan de ninguna manera la forma en que funciona la clase. Esto simplemente dirigirá el flujo de mensajes de una manera ligeramente diferente a la que se veía cuando se usaba el sistema de herencia. Básicamente, puedo destacar los siguientes cambios principales:

#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Mouse : public C_Terminal
{
   protected:
//+------------------------------------------------------------------+
// ... Fragmento Interno ....
//+------------------------------------------------------------------+
   private :
//+------------------------------------------------------------------+
// ... Fragmento interno ...
      C_Terminal *Terminal;
//+------------------------------------------------------------------+

Se agregaron dos nuevas definiciones para evitar repetir código todo el tiempo. Esto ajusta muchas cosas. Además, se añadió una variable global privada para que podamos acceder correctamente a la clase C_Terminal. Esto se suma al hecho de que ya no utilizaremos la herencia de la clase C_Terminal, como se puede ver en el código indicado arriba.

Bueno, como ya no utilizamos la herencia, debemos realizar otros dos cambios que vale la pena destacar. El primero se ve en el constructor de la clase C_Mouse:

C_Mouse(C_Terminal *arg, color corH, color corP, color corN)
	:C_Terminal()
   {
      Terminal = arg;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      ZeroMemory(m_Info);
      m_Info.corLineH  = corH;
      m_Info.corTrendP = corP;
      m_Info.corTrendN = corN;
      m_Info.Study = eStudyNull;
      m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
      ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
      ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
   }

Eliminamos la llamada del constructor de la clase C_Terminal del constructor de la clase C_Mouse. Ahora tenemos que recibir un nuevo parámetro para inicializar el puntero a la clase. Por razones de seguridad, no queremos que nuestro código se rompa en una situación inapropiada. Realizaremos una prueba para comprobar que el puntero que nos permite utilizar la clase C_Terminal efectivamente fue inicializado correctamente. 

Esto se hace usando la función CheckPointer, pero al igual que el constructor, no nos permite devolver ninguna información de error. Indicaremos una condición de error utilizando un valor predefinido en la enumeración presente en la clase C_Terminal. Sin embargo, como no podemos modificar directamente el valor de la variable _LastError, debemos utilizar la llamada SetUserError. De esta manera, podremos luego comprobar la variable _LastError para saber qué ocurrió.

Pero cuidado, si la clase C_Terminal no se ha inicializado correctamente, este constructor de la clase C_Mouse regresará sin hacer absolutamente nada, ya que no podrá utilizar la clase C_Terminal al no haber sido inicializada.

La otra modificación que merece cierta atención es en relación a la función que se muestra a continuación:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      int w = 0;
      static double memPrice = 0;
                                
      C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
      switch (id)
      {
//....

El código indicado debe agregarse al Expert Advisor para poder manejar los eventos reportados por la plataforma MetaTrader 5. Como puedes ver, si no se hace esto, tendremos problemas con algunos tipos de eventos, que podrían terminar comprometiendo el posicionamiento de un elemento en el gráfico. Por ahora, simplemente eliminamos el código de esta ubicación. Incluso podríamos permitir que la clase C_Mouse realice la llamada al sistema de mensajería de la clase C_Terminal. Pero, como aquí no utilizamos la herencia, esto dejaría el código con una dependencia algo extraña.

De la misma forma como se hizo en la clase C_Mouse, lo haremos en la clase C_Study. Pero esto se hará de tal manera que la única función que realmente merezca atención sea el constructor de clases, que se puede ver a continuación:

C_Study(C_Terminal *arg, color corH, color corP, color corN)
        :C_Mouse(arg, corH, corP, corN)
   {
      Terminal = arg;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise);
      CreateObjectInfo(2, 53, def_ExpansionBtn2);
      CreateObjectInfo(58, 53, def_ExpansionBtn3);
   }

Ten en cuenta que recibimos el parámetro que nos indica el puntero a la clase C_Terminal y lo pasamos a la clase C_Mouse. Como lo heredamos, debemos inicializarlo correctamente. Pero de una forma u otra, haremos las mismas pruebas que hicimos en el constructor de la clase C_Mouse, para asegurarnos de que estamos usando un puntero adecuado. Ahora debemos prestar atención a una cosa: si te diste cuenta, en ambos constructores, tanto en C_Mouse como en C_Study, estamos comprobando el valor de _LastError para saber si algo no está como se esperaba. Sin embargo, dependiendo del activo que se utilice, es posible que la clase C_Terminal tenga que inicializar su nombre para que el Expert Advisor pueda saber realmente qué activo se encuentra actualmente en el gráfico.

Si por casualidad esto ocurre, la variable _LastError contendrá el valor 4301 ( ERR_MARKET_UNKNOWN_SYMBOL ), indicando que el activo no fue detectado correctamente. Pero esto no será cierto, ya que la clase C_Terminal puede, dentro de lo ya programado, acceder al activo correcto. Para evitar que este error impida que el Expert Advisor permanezca en el gráfico, debemos realizar un pequeño cambio en el constructor de la clase C_Terminal. Puedes verlo a continuación:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      CurrentSymbol();
      m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
      m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
      m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
      m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
      m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
      m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
      m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
      m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
      m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint;
      ResetLastError();
   }

Agregando el código resaltado, diremos que no hay error inicial. Por lo tanto, al hacer uso del sistema constructor para inicializar las clases en el código del Expert Advisor, en realidad, no será necesario agregarle esta línea. Esto se debe a que, en algunos casos, es posible que termines olvidándote de realizar esta adición. O peor aún, hacerlo en el punto equivocado, lo que haría que el código sea completamente inestable e inseguro de usar.


La clase C_Orders

Bueno, lo visto hasta ahora sirve para ayudarnos a preparar el terreno para la siguiente etapa. Aunque necesitaremos realizar algunos cambios más en la clase C_Terminal, algunos se realizarán más adelante en este artículo. Pero pasemos a crear la clase C_Orders para poder comunicarnos con el servidor comercial. En este caso, este será el servidor REAL, al que se accederá a través del corredor de bolsa. Sin embargo, puedes usar una cuenta DEMO para probar el sistema. No es recomendable utilizar el sistema directamente en una cuenta Real.

El código para esta clase comenzará de la siguiente manera:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "..\C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Orders
{

Aquí, para facilitar la codificación, definimos algunas cosas para acceder a la clase C_Terminal. Dichas definiciones ya no existirán al final del archivo de clase, sino, durante el resto del tiempo, dentro del código de clase. Esta será la forma de acceder a la clase C_Terminal, en caso de que realicemos algún cambio futuro. No necesitamos modificar el código de clase punto por punto, todo lo que necesitamos hacer es modificar solo estas definiciones. Ten en cuenta que la clase no está heredando nada. Es importante tener esto en cuenta para no confundirse al programarla, ni al codificar las otras clases que vendrán más adelante.

Lo siguiente que tenemos son las declaraciones de las primeras variables globales, internas a la clase. Esto se puede ver en el siguiente fragmento:

   private :
//+------------------------------------------------------------------+
      MqlTradeRequest m_TradeRequest;
      ulong           m_MagicNumber;
      C_Terminal      *Terminal;

Ten en cuenta el hecho de que dichas variables globales se declaran como privadas, es decir, no se puede acceder a ellas fuera del código de clase. Un detalle que merece mi atención es cómo se está declarando la variable que dará acceso a la clase C_Terminal. Ten en cuenta que, de hecho, se declara como un puntero, aunque el uso de punteros no se realiza de la misma manera en MQL5 que en C/C++.

ulong ToServer(void)
   {
      MqlTradeCheckResult     TradeCheck;
      MqlTradeResult          TradeResult;
      bool bTmp;
                                
      ResetLastError();
      ZeroMemory(TradeCheck);
      ZeroMemory(TradeResult);
      bTmp = OrderCheck(m_TradeRequest, TradeCheck);
      if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
      if (_LastError != ERR_SUCCESS) PrintFormat("Order System - Error Number: %d", _LastError);
      return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
   }

Esta función anterior, que será una función privada, sirve para centralizar llamadas. La decisión de centralizar las llamadas se debe a que así nos resultará más sencillo adaptar el sistema posteriormente. Esto es para poder utilizar el mismo esquema, tanto para trabajar con un servidor real como con un servidor simulado. Esta función anterior fue eliminada, así como otras que también veremos aquí, de la serie de artículos, donde el último artículo en este momento es:  "Cómo construir un EA que opere automáticamente (Parte 15): Automatización (VII)". Allí se explicó cómo se desarrolla un Expert Advisor automatizado a partir de un Expert Advisor manual. Tomaremos varias rutinas presentes en esa serie para poder agilizar un poco el trabajo que se realizará aquí. De esta manera, si desea utilizar esos mismos conceptos, puede probar utilizando el sistema de repetición/simulador, un Expert Advisor automático, sin necesidad de utilizar el simulador de estrategias presente en MetaTrader 5.

Básicamente, la función anterior verificará algunas cosas con el servidor del corredor de bolsa. Si todo está en orden, enviará una solicitud al servidor comercial para ejecutar una solicitud realizada por el usuario o el Expert Advisor, ya que esto se puede operar automáticamente.

inline void CommonData(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      double Desloc;
                                
      ZeroMemory(m_TradeRequest);
      m_TradeRequest.magic        = m_MagicNumber;
      m_TradeRequest.symbol       = def_InfoTerminal.szSymbol;
      m_TradeRequest.volume       = NormalizeDouble(def_InfoTerminal.VolumeMinimal + (def_InfoTerminal.VolumeMinimal * (Leverage - 1)), def_InfoTerminal.nDigits);
      m_TradeRequest.price        = NormalizeDouble(Price, def_InfoTerminal.nDigits);
      Desloc = def_AcessTerminal.FinanceToPoints(FinanceStop, Leverage);
      m_TradeRequest.sl           = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), def_InfoTerminal.nDigits);
      Desloc = def_AcessTerminal.FinanceToPoints(FinanceTake, Leverage);
      m_TradeRequest.tp           = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), def_InfoTerminal.nDigits);
      m_TradeRequest.type_time    = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
      m_TradeRequest.stoplimit    = 0;
      m_TradeRequest.expiration   = 0;
      m_TradeRequest.type_filling = ORDER_FILLING_RETURN;
      m_TradeRequest.deviation    = 1000;
      m_TradeRequest.comment      = "Order Generated by Experts Advisor.";
   }

De manera similar, la función anterior también se está importando de la misma serie de artículos. Sin embargo, aquí tuvimos que adaptarla al nuevo sistema que se está implementando. La forma en que funciona es básicamente la misma que se ve en la serie de automatización. Pero para aquellos que no han visto o leído los artículos de la serie, echemos un vistazo rápido a los diferentes códigos presentes en esta función, que se pueden ver en los puntos resaltados. Este código lo que hace es traducir el valor financiero en un valor en términos de puntos. Esto es para que usted, como usuario, no tenga que preocuparse por ajustar la cantidad de puntos dado un apalancamiento determinado. De esta manera, no tendrás muchas más finanzas de las deseadas. Hacer esto manualmente es muy propenso a errores y fallas, pero usar esta función es bastante sencillo realizar la conversión. Funciona independientemente del activo con el que se negocie. No importa el activo, la conversión siempre se realizará de forma correcta y adecuada.

Entonces echemos un vistazo a esta función. Está presente en la clase C_Terminal. Su código se puede ver a continuación:

inline double FinanceToPoints(const double Finance, const uint Leverage)
   {
      double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
                                
      return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
   };

El gran secreto de esta función anterior es precisamente el valor que se está resaltando en el código, si lo buscas verás que se calcula como se muestra en el fragmento a continuación:

m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;

Ten en cuenta que todos los valores presentes anteriormente y que se utilizan en FinanceToPoints dependen del activo que se gestiona y se utiliza para operar. De esta manera, cuando FinanceToPoints realice la conversión, en realidad se estará adaptando al activo que usemos en el gráfico. Por lo tanto, al Expert Advisor no le importa qué activo o en qué mercado se puso en funcionamiento. Podrá funcionar en cualquier persona de forma completamente similar a la que tengamos que adaptarnos para poder utilizarlo en un mercado u otro. Ahora que hemos visto la parte privada de la clase, veamos la parte pública. Comenzaremos con el constructor, que se ve a continuación:

C_Orders(C_Terminal *arg, const ulong magic)
         :m_MagicNumber(magic)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   }

De forma sencilla y eficaz, hacemos que el constructor de la clase tenga acceso a la clase C_Terminal. Pero, observe cómo sucede esto realmente: cuando el Expert Advisor crea el objeto C_Terminal para utilizar la clase, también crea un objeto que se pasará a todas las demás clases que necesiten dicho objeto. Esto se hace de la siguiente manera: la clase recibe el puntero creado por el Expert Advisor para poder tener acceso a la clase ya inicializada. Luego, almacenamos el valor en nuestra variable global privada para poder acceder, cuando sea necesario, a algún dato o función de la clase C_Terminal. En realidad, si dicho objeto, en este caso una clase, no apunta a algo útil, esto se indicará como un error. Dado que el constructor no puede devolver ningún valor, usamos este método que colocará un valor en la variable _LastError. De esa manera, podremos probar esto más tarde.

Con esto, podemos ver las dos últimas funciones presentes en la clase en este punto del desarrollo. El primero de ellos lo podemos ver a continuación:

ulong ToMarket(const ENUM_ORDER_TYPE type, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      CommonData(type, SymbolInfoDouble(def_InfoTerminal.szSymbol, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID)), FinanceStop, FinanceTake, Leverage, IsDayTrade);
      m_TradeRequest.action   = TRADE_ACTION_DEAL;
      m_TradeRequest.type     = type;

      return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
   };

Esta función se encarga de enviar una solicitud de ejecución a precio de mercado. Aquí utilizamos prácticamente todo el código visto anteriormente. De esta manera, podremos hacer una mejor reutilización del mismo, promoviendo una mayor seguridad y rendimiento en el tiempo. Ya que a medida que se mejore cualquier parte del sistema que se esté reutilizando, todo el código se beneficiará de esto. Algunos detalles que necesitan atención al usar este código anterior:

  • Primero, estaremos indicando los límites (take-profit y stop-loss) como valores financieros y no mediante el uso de puntajes.
  • En segundo lugar, le decimos al servidor que ejecute la orden inmediatamente, al mejor precio posible disponible en el momento en que se ejecuta la orden.
  • En tercer lugar, aunque tenemos acceso a más tipos de órdenes, aquí sólo podemos utilizar estos dos tipos, para poder informar si estaremos comprando o vendiendo. Si no se hace esto, el pedido no será enviado.

Estos detalles son importantes para que realmente pueda utilizar este sistema. No saber o ignorar dichos detalles te dará muchos dolores de cabeza y generará muchas dudas en la siguiente fase de desarrollo.

Con esto llegamos a la última rutina de esta clase C_Orders. Para la etapa actual de desarrollo, esto se puede ver a continuación:

ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      double  bid, ask;
                                
      bid = SymbolInfoDouble(def_InfoTerminal.szSymbol, (def_InfoTerminal.ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : SYMBOL_BID));
      ask = (def_InfoTerminal.ChartMode == SYMBOL_CHART_MODE_LAST ? bid : SymbolInfoDouble(def_InfoTerminal.szSymbol, SYMBOL_ASK));
      CommonData(type, def_AcessTerminal.AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
      m_TradeRequest.action   = TRADE_ACTION_PENDING;
      m_TradeRequest.type     = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
							  (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));                                
                                
      return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
   };

Aquí tenemos algunas cosas muy similares a la función de ejecución a precio de mercado, como el hecho de que los límites están en términos económicos y que tenemos que indicar si compraremos o venderemos utilizando solo uno de estos dos valores. A pesar de esto, si observa, notará que está haciendo algo un poco diferente a la gran mayoría de códigos presentes en Expert Advisor. Normalmente, cuando estamos creando un Expert Advisor, estará orientado a ser utilizado en un tipo de mercado muy concreto, ya sea el mercado Forex o la Bolsa. Dado que en MetaTrader 5 podemos hacer uso de ambos tipos de mercados, necesitamos realizar algún tipo de estandarización para hacernos la vida más fácil. Pero, ¿por qué esto? ¿No sería lo mismo trabajar en Forex que en Bolsa? Desde el punto de vista del usuario , pero desde el punto de vista de la programación NO. Si miras, verás que estamos probando qué tipo de trama se está utilizando actualmente. Haciendo este test podemos saber si estamos trabajando con un sistema que funciona con el último precio o si se basa en valores BID y ASK. Saber esto es importante, no para posicionar la orden, sino para saber qué tipo de orden utilizar. En el futuro, necesitaremos implementar este tipo de órdenes en el sistema para simular lo que haría el servidor comercial. Pero en este momento lo único que necesitas saber es que el tipo de orden es tan importante como el precio al que se ejecutará. Si pones el precio en el lugar correcto, pero te equivocas con el tipo de orden, habrá un problema, ya que la orden se ejecutará en un momento diferente al que esperabas o imaginaste que realmente sería ejecutada por el servidor.

Es muy común que los usuarios nuevos en MetaTrader 5 cometan errores a la hora de rellenar órdenes pendientes. No en cualquier tipo de mercado, ya que con el tiempo acaba acostumbrándose al mercado y no cometerá errores tan fácilmente. Pero cuando saltas de un mercado a otro, las cosas se complican. Si el sistema de trazado es del tipo BID-ASK, la forma de ajustar el tipo de orden es diferente a un sistema de trazado LAST. Las diferencias son sutiles pero existen, y hacen que la orden no quede pendiente, sino que se ejecute a precio de mercado, incluso cuando la intención sería colocar una orden pendiente.


Consideraciones finales

A pesar de todo, no habrá ningún código presente en el anexo de este artículo, y la razón es el hecho de que en realidad no implementamos un sistema de órdenes, simplemente creamos la clase (BASIC) para implementar dicho sistema. Es posible que hayas notado que faltan varias rutinas y procedimientos al comparar el código que se muestra aquí en la clase C_Orders. Esto es en comparación con los códigos presentes en artículos anteriores, donde expliqué y hablé sobre el sistema de pedidos.

El hecho de que esto esté sucediendo se debe a mi decisión de dividir y repartir este sistema de órdenes en varias partes, unas más grandes y otras más pequeñas. Esto es para que sea posible explicar de forma clara y sencilla cómo se integrará el sistema con el servicio de repetición/simulador. Créeme, hacer esto no es una de las tareas más sencillas, al contrario, es algo bastante complejo e involucra muchos conceptos que no sé si realmente tienes. Por eso será necesario nivelar las cosas poco a poco, para que los artículos se entiendan bien y su contenido no se convierta en un completo caos.

En el próximo artículo, veremos cómo hacer que este sistema de órdenes comience a comunicarse con el servidor comercial. Al menos la física, para que puedas utilizar el Expert Advisor en una cuenta DEMO o REAL. Entonces, comencemos a comprender cómo funcionan los tipos de órdenes, para que podamos ingresar al sistema simulado. Si se hace lo contrario, o si se colocan juntos el sistema simulado y el real, la confusión que esto provocará no permitirá que todos puedan seguir las explicaciones. ¡Nos vemos en el próximo artículo!


Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/11393

Archivos adjuntos |
Anexo.zip (130.63 KB)
Interfaz gráfica: consejos y recomendaciones para crear una biblioteca gráfica en MQL Interfaz gráfica: consejos y recomendaciones para crear una biblioteca gráfica en MQL
Hoy abarcaremos los conceptos básicos de las bibliotecas GUI para comprender cómo funcionan estas o incluso comenzar a crear bibliotecas propias.
Desarrollo de un sistema de repetición (Parte 31): Proyecto Expert Advisor — Clase C_Mouse (V) Desarrollo de un sistema de repetición (Parte 31): Proyecto Expert Advisor — Clase C_Mouse (V)
Desarrollar una manera de poner un cronómetro, de modo que durante una repetición/simulación, éste pueda decirnos cuánto tiempo falta, puede parecer a primera vista una tarea simple y de rápida solución. Muchos simplemente intentarían adaptar y usar el mismo sistema que se utiliza cuando tenemos el servidor comercial a nuestro lado. Pero aquí reside un punto que muchos quizás no consideran al pensar en tal solución. Cuando estás haciendo una repetición, y esto para no hablar del hecho de la simulación, el reloj no funciona de la misma manera. Este tipo de cosa hace complejo construir tal sistema.
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 2): Señales del indicador - Parabolic SAR de marco temporal múltiple Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 2): Señales del indicador - Parabolic SAR de marco temporal múltiple
En este artículo, entenderemos por asesor multidivisa un asesor o robot comercial que puede comerciar (abrir/cerrar órdenes, gestionar órdenes, por ejemplo, trailing-stop y trailing-profit, etc.) con más de un par de símbolos de un gráfico. Esta vez usaremos solo un indicador, a saber, Parabolic SAR o iSAR en varios marcos temporales, comenzando desde PERIOD_M15 y terminando con PERIOD_D1.
Desarrollo de un sistema de repetición (Parte 30): Proyecto Expert Advisor — Clase C_Mouse (IV) Desarrollo de un sistema de repetición (Parte 30): Proyecto Expert Advisor — Clase C_Mouse (IV)
Aquí te mostraré una técnica que puede ayudarte mucho en varios momentos de tu vida como programador. En contra de lo que muchos dicen, lo limitado no es la plataforma, sino los conocimientos del individuo que lo dice. Lo que se explicará aquí es que con un poco de sentido común y creatividad, se puede hacer que la plataforma MetaTrader 5 sea mucho más interesante y versátil, sin tener que crear programas locos ni nada por el estilo puedes crear un código sencillo, pero seguro y fiable. Utiliza tu ingenio para domar el código con el fin de modificar algo que ya existe, sin eliminar ni añadir una sola línea al código original.