English Русский Português
preview
Desarrollo de un sistema de repetición (Parte 36): Haciendo retoques (II)

Desarrollo de un sistema de repetición (Parte 36): Haciendo retoques (II)

MetaTrader 5Ejemplos | 4 abril 2024, 14:29
160 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior "Desarrollo de un sistema de repetición (Parte 35): Haciendo retoques", hemos solucionado y corregido algunas cosas que hacían que el sistema funcionara de una manera bastante inusual. Aunque las correcciones introducidas en ese artículo han mejorado la experiencia del usuario, aún no hemos realizado un ajuste completo de todo lo relacionado con el sistema de repetición/simulador.

El caso es que, aunque la experiencia del usuario ha mejorado, seguimos teniendo un pequeño problema. Y, en este artículo, resolveremos este problema que, a pesar de ser relativamente sencillo en teoría, será todo un reto resolverlo correctamente. Aumentaremos nuestro conocimiento de MetaTrader 5 y de cómo utilizar el lenguaje MQL5 de una manera un poco más elaborada.


Reflejar el tipo de mercado

Cuando el Expert Advisor se posicione en el gráfico, informará del tipo de cuenta detectado. Esto es importante para saber cómo debe actuar el Expert Advisor. Pero, aunque esto funciona muy bien cuando el sistema se inicia en el gráfico de un activo, que está en una cuenta REAL o en una cuenta DEMO, el sistema no utiliza lo que rige el sistema de repetición/simulación, y no es informado el tipo de cuenta al que pertenece el activo, sino el tipo de cuenta en el que se está ejecutando la plataforma. Se trata de un problema que, aunque pequeño, nos causa algunas molestias.

Podrías pensar: Resolver esto no es ni mucho menos complicado, ni siquiera un reto. La solución es muy sencilla. Asegurémonos de que el sistema de repetición/simulador te dice de alguna manera qué tipo de cuenta es la correcta. Esto depende, por supuesto, del activo que se utilice. De hecho, la idea es sencilla de hablar e imaginar. Pero ponlo en práctica. Bueno, esa es otra historia. La verdad es que hacer que el sistema de repetición/simulador nos diga qué tipo de cuenta utilizar no es tan sencillo. Pero, afortunadamente, la plataforma MetaTrader 5 nos ofrece una forma de implementar una solución adecuada y plausible de utilizar realmente.

Pero no lo haremos al azar, ni de forma imprudente y descuidada. Vamos a aplicar la solución de forma que se ponga freno a estas cosas en este tipo de cuentas. Ya que esta información será importante para nosotros cuando creemos el sistema de órdenes. Para empezar, pensemos en lo que realmente tenemos entre manos: el sistema de repetición/simulador podrá utilizar activos de distintos mercados. El hecho de que esto ocurra puede hacer que utilicemos activos que se utilizan en cuentas NETTING o HEDGING.

Nota: Dado que el tipo EXCHANGE se parece mucho al tipo NETTING, en realidad, no vamos a aplicar este tipo de EXCHANGE. Este tipo se considerará un tipo NETTING.

Como usuario del sistema de repetición/simulador, usted sabe qué tipo de mercado, o más bien qué tipo de cuenta, utiliza el activo. A continuación, podemos añadir una forma para que el usuario informe de esto al sistema de repetición/simulador. Esta es la parte fácil, ya que todo lo que tenemos que hacer es añadir un nuevo comando al archivo de configuración del activo. Pero hacer esto no garantiza que quienes realmente necesitan esta información la tengan disponible. ¿Y quién utiliza esta información en nuestro sistema de repetición/simulación? El Expert Advisor hace uso de esta información, concretamente, de la clase C_Manager junto con la clase C_Orders. Los que realmente hacen un uso masivo de esta información específica. Aunque de momento no se haga, hay que pensar en términos más globales y generales. Donde podemos usar algo visto en:  Cómo construir un EA que opere automáticamente

Es precisamente por este hecho, que en esa serie de artículos, el Expert Advisor necesitaba saber qué tipo de cuenta se estaba utilizando, que tenemos que hacer que el sistema de repetición/simulador sea capaz de informar al Expert Advisor de esto también. De lo contrario, el tipo de funcionalidad vista en la serie de artículos se perderá y no podrá ser transportada a este sistema desde aquí. De acuerdo. Ya tenemos una idea de cómo hacer que el servicio de repetición/simulador sepa qué tipo de cuenta utiliza un determinado activo. Pero la cuestión es:  ¿Cómo introduzco esta información en el Expert Advisor?

Aquí es donde realmente tenemos que pararnos a pensar. Si miras el código, podrás averiguar el tipo de cuenta. Verás el siguiente fragmento:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                
      switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
         case ACCOUNT_MARGIN_MODE_EXCHANGE      : szInfo = "EXCHANGE";           break;
      }
      Print("Detected Account ", szInfo);
   }

En esta sección destacada, se puede ver dónde el Expert Advisor puede decir qué tipo de cuenta se está utilizando. Pero aquí hay un problema, y es la función AccountInfoInteger. Esta función no es realmente el problema, ya que informa exactamente de lo que le pedimos que nos diga. El problema es que, al utilizar el servicio de reproducción/simulación, el resultado de la función AccountInfoInteger será información relativa al tipo de cuenta en el servidor de negociación. En otras palabras, si está conectado a un servidor NETTING, el resultado será que estamos trabajando en una cuenta NETTING, incluso si el activo de reproducción/simulador es del tipo HEDGING.

Ese es el fallo. Ahora las ideas: se podría decir que podríamos hacer que el Expert Advisor leyera el archivo de configuración del activo de reproducción/simulador, y eso solucionaría el problema. Bueno, eso sería apropiado en cierto modo. Sí, y solo si se utilizara un único archivo para ello, que no es el caso. Puedes pensar: Así que hagamos que el servicio de repetición/simulación pase esta información de tipo de cuenta. De esta manera, el Expert Advisor sabrá qué tipo es el correcto. Sí, así se hace. Pero aquí hay un pequeño detalle. A diferencia de C/C++, MQL5 no tiene ciertos tipos de construcciones de código. No es que seas definitivamente incapaz de utilizar algunos tipos de codificación para transferir información entre programas. Ya hemos visto que esto es posible, y no solo una vez, sino varias. Sin embargo, el problema es que nuestra información debe estar contenida en un bloque de 8 bytes. De esta forma, podemos utilizar variables terminales globales para transportar información entre programas. Recuerda que haremos esto usando MQL5. Hay otras formas de hacerlo. Pero aquí quiero utilizar las características de la plataforma y el lenguaje MQL5.

Volvamos a nuestro problema. Realmente podemos hacer uso del método que hemos estado aplicando durante mucho tiempo. Para ello, se utilizó el archivo InterProcess.mqh, con el fin de promover la vinculación y la traducción que necesitamos. Sin embargo, este mismo archivo contiene un problema, que vamos a resolver en este artículo. Para entender el problema, veamos cómo está codificado actualmente este archivo.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

El problema son estos booleanos de aquí. A diferencia de C/C++, donde podemos hacer que cada booleano esté contenido en un bit, aquí en MQL5, cada booleano ocupará ocho bits. Tal vez no entiendas lo que esto significa en realidad. Pero se utilizará un byte entero cuando solo necesitamos un bit para almacenar lo que necesitamos. La ignorancia de este hecho significa que tenemos un GRAN problema. Recuerde que el tipo ushort ocupará dos bytes para transferir la información. La estructura st_0 ocupa en realidad cuatro bytes y no tres como se esperaba. Si añadimos cuatro más, y solo cuatro booleanos a esta estructura st_0, estaremos en el límite de ocho bytes posibles para nuestro uso.

Este tipo de cosas complican un poco la parte de programación. Así que la idea de hacer las cosas puramente dentro de MQL5 es un poco más complicada de lo que parece. Si las cosas siguen complicándose, o necesitamos pasar más datos en modo booleano, tendremos que modificar radicalmente esta estructura. Y esto es una pesadilla logística. Pero a pesar de ello, hasta que llegue el momento, podemos mantener esta misma forma de estructura. Pero estaría bien que los desarrolladores del lenguaje MQL5 nos permitieran utilizar el mismo modo de programación utilizado en C/C++. Al menos en el caso de los tipos booleanos. De esta forma, podríamos definir el número de bits que utilizaría cada variable, y el compilador haría todo el trabajo de organizar, separar y agrupar las variables. Nos ahorra tener que hacer la programación de bajo nivel que habría que hacer para conseguir este tipo de cosas. Con una diferencia, si la programación la realizaran los desarrolladores y mantenedores de MQL5, este trabajo se haría de forma mucho más eficiente. Ya que sería posible organizar y construir mediante código ensamblador, o algo muy parecido al lenguaje máquina. Si este mismo trabajo lo hiciéramos nosotros, solo los desarrolladores, la eficacia del código no sería tan grande y requeriría mucho más trabajo.

Así que aquí hay una pista de una mejora para MQL5. Algo aparentemente trivial, pero que ayudará mucho en algunos trabajos de programación con el lenguaje.

Así que volvamos a nuestro código, ya que todavía podemos trabajar con lo que tenemos a mano. El nuevo código para el archivo Interprocess.mqh se muestra a continuación:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                bool    isHedging;      // If true we are in a Hedging account, if false the account is Netting...
                bool    isSync;         // If true indicates that the service is synchronized...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

Ten en cuenta que lo único que hemos hecho aquí es añadir dos nuevas variables booleanas. Mas esto nos bastará para resolver el problema inmediato. Pero esto nos acerca mucho al límite de ocho bytes. Aunque sólo estoy usando cuatro booleanos, que serían cuatro bits, el hecho de que cada booleano ocupe ocho bits significa que la estructura st_0 tiene un tamaño de seis bytes, no de tres como se esperaba.

NOTA IMPORTANTE: Para los que no sepan cómo se haría este mismo código si el lenguaje MQL5 utilizara un modelado similar al de C/C++ para definir estructuras booleanas, para que el compilador hiciera el trabajo de ajustar el código por nosotros, ten en cuenta que la lectura, así como la fase de codificación, no se verían comprometidas en modo alguno. Pero el mismo código visto anteriormente, que ocupa seis bytes, ocuparía tres si se pudiera codificar de la siguiente manera:

union u_Interprocess
{
        union u_0
        {
                double  df_Value;      // Value of the terminal global variable...
                ulong   IdGraphic;     // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {           
    		char  isPlay    : 1// Indicates whether we are in Play or Pause mode...
    		char  isWait    : 1// Tells the user to wait...                
    		char  isHedging : 1;   // If true we are in a Hedging account, if false the account is Netting...                
    		char  isSync    : 1// If true indicates that the service is synchronized...
                ushort  iPosShift;     // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};

El código anterior no es entendido por el compilador de lenguaje MQL5, al menos no en el momento de escribir estas líneas. Pero aquí le estamos diciendo al compilador que sólo vamos a utilizar un bit en cada una de las declaraciones. Es trabajo del compilador, no del programador, ajustar la variable adecuadamente para que cuando accedamos al bit, podamos hacerlo de una forma mucho más controlada, y utilizar una etiqueta para esto, y no algún tipo de directiva de definición de compilación.

 Aunque todavía es posible tener una construcción de este tipo utilizando MQL5 nativo. Hacer esto mediante directivas de compilación es muy propenso a errores y fallos, además de dificultar mucho el mantenimiento del código. Me gustaría dejar esto claro, para que no pienses que el lenguaje no nos permite hacer ciertas cosas.

Pero si el mundo te da limones, hagamos limonada. Y no nos quejemos de que el mundo no es como esperamos que sea. Realice estos cambios en el archivo Interprocess.mqh. Tenemos que pasar a otro punto. Ahora modificaremos el archivo C_ConfigService.mqh. Dentro de este archivo, haremos algunos cambios, empezando por el código que se muestra a continuación:

        private :
                enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
                enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
                struct st001
                {
                        C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
                        int     Line;
                }m_GlPrivate;
                string  m_szPath;
                bool    m_AccountHedging;

Añadimos esta variable privada a la clase C_ConfigService. Esto nos da una memoria de lo que procesaremos a continuación. En primer lugar, debemos inicializar esta variable. Esto se hace en el constructor de la clase, como se puede ver en el siguiente punto:

C_ConfigService()
        :m_szPath(NULL),
         m_ModelLoading(1),
         m_AccountHedging(false)
   {
   }

Bien, el hecho de que necesitemos una variable que nos sirva de memoria es porque necesitamos que el Indicador de Control nos cree la variable global terminal, que recibirá los datos ya configurados. Sin embargo, esta variable terminal global sólo aparecerá cuando la plantilla cargue el indicador de control en el gráfico. Y el gráfico sólo aparecerá una vez que el servicio de repetición/simulador haya terminado de configurar todo el sistema. Si el valor que vamos a configurar, indicado por el usuario, no estuviera almacenado en algún sitio, no podríamos decirle al Expert Advisor el tipo de cuenta. Pero con una programación cuidadosa, podemos pasar el tipo de cuenta configurada en el servicio de repetición/simulador al Expert Advisor. Así sabrá cómo trabajar. Eso si utilizas los conocimientos que te hemos dado sobre cómo crear un Expert Advisor automático. Y ya estás pensando en cómo probar la aplicación que has desarrollado.

Muy bien, ya que la variable es una variable privada y la clase C_Replay configurará la variable terminal global. Necesitamos un método para que la clase C_Replay pueda acceder a la variable tipo de cuenta. Para ello utilizaremos el siguiente procedimiento:

inline const bool TypeAccountIsHedging(void) const
   {
      return m_AccountHedging;
   }

Esto nos devolverá el valor. Ahora tenemos que asegurarnos de que la configuración realizada por el usuario puede ser capturada y convertida en algo útil para nuestro código. Para ello, tendremos que añadir algo de código nuevo al sistema de captura. Esto se puede ver a continuación:

inline bool Configs(const string szInfo)
   {
      const string szList[] = {
                               "PATH",
                               "POINTSPERTICK",
                               "VALUEPERPOINTS",
                               "VOLUMEMINIMAL",
                               "LOADMODEL",
                               "ACCOUNT"
                              };
      string  szRet[];
      char    cWho;
                                
      if (StringSplit(szInfo, '=', szRet) == 2)
      {
         StringTrimRight(szRet[0]);
         StringTrimLeft(szRet[1]);
         for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
         switch (cWho)
         {
            case 0:
               m_szPath = szRet[1];
               return true;
            case 1:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
               return true;
            case 2:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
               return true;
            case 3:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
               return true;
            case 4:
               m_ModelLoading = StringInit(szRet[1]);
               m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading);
               return true;
            case 5:
               if (szRet[1] == "HEDGING") m_AccountHedging = true;
               else if (szRet[1] == "NETTING") m_AccountHedging = false;
               else
               {
                  Print("Entered account type is not invalid.");                                                          
                  return false;
               }
               return true;                    
         }
         Print("Variable >>", szRet[0], "<< not defined.");
      }else
         Print("Configuration definition >>", szInfo, "<< invalidates.");
                                        
      return false;
   }

Todo el código resaltado se ha añadido para que podamos saber qué cuenta se utilizará en el archivo de configuración. Verás, hacer esto era extremadamente sencillo pero perfectamente funcional. Ahora, si miras en el archivo de configuración verás algo como lo siguiente:

[Config]
Path = Forex\EURUSD
PointsPerTick = 0.00001
ValuePerPoints = 1.0
VolumeMinimal = 0.01
Account = HEDGING

o algo muy parecido a lo que se muestra a continuación:

[Config]
Path = Petrobras PN
PointsPerTick = 0.01
ValuePerPoints = 1.0
VolumeMinimal = 100.0
Account = NETTING

Ten en cuenta que tenemos diferentes activos que pueden ser utilizados por el servicio de repetición/simulador. Pero como usuario, puede definir el tipo de cuenta que se utilizará. Esto es bastante sencillo y sin complicaciones. De este modo, podrá ampliar sus estudios a cualquier tipo de mercado. Le permite utilizar cualquier metodología que conozca o desarrolle. Ahora vamos a ver cómo hacer que la clase C_Replay defina estos datos por nosotros. El trabajo sobre la clase C_ConfigService ha terminado. Para que esto ocurra, y para que la configuración dada por el usuario sea vista por todo el sistema de repetición/simulador, no tendremos mucho trabajo que hacer. Todo lo que tenemos que hacer se muestra en el siguiente código:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = ULONG_MAX;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      info.s_Infos.isHedging = TypeAccountIsHedging();
      info.s_Infos.isSync = true;
      GlobalVariableSet(def_GlobalVariableReplay, info.u_Value.df_Value);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

La línea señalada ha sido eliminada del código y la razón es que necesitamos que la clase C_Replay cargue el valor de la variable que fue contabilizada por el Indicador de Control, para que podamos saber con qué estamos tratando. Recuerda: No hagas suposiciones. Puede que en el futuro tengamos que pasar valores al servicio en cuanto empiece a funcionar el indicador de control. Una vez hecho esto, contabilizamos los valores que el usuario ha definido e indicamos que el sistema se ha sincronizado. Después de esto, enviamos el valor a MetaTrader 5, haciendo que los datos estén disponibles a través de la variable global del terminal. La parte sobre el servicio de repetición/simulador termina aquí. Pero la parte de codificación no ha terminado todavía, ahora tenemos que modificar el Expert Advisor para que pueda saber cómo la repetición/simulación ha sido configurada por el usuario del sistema. Pero para explicar esto con más detalle, pasemos al siguiente tema.


Hacer que el Expert Advisor conozca el tipo de cuenta que se está utilizando.

Antes de entender realmente cómo hacer que el Expert Advisor reconozca el tipo de cuenta definido por el usuario, hay que entender cómo se inicializa todo el sistema. Para ello, echa un vistazo a la imagen 01, que resume cómo tiene lugar el proceso de inicialización.

Figura 01

Figura 01 - Proceso de inicialización del sistema de repetición/simulación.

¿Por qué es tan importante entender esta cifra 01? La razón es que la clase C_Terminal es una clase compartida entre el Indicador de Control y el Expert Advisor. Si no entiende esto, no podrá comprender cómo y por qué el sistema consigue establecerse en la plataforma MetaTrader 5, hasta el punto de seguir lo que el usuario ha configurado. Es más, si no entiendes esta inicialización, puedes acabar haciendo el ridículo editando el archivo que configurará la repetición/simulación. Esto significa que el sistema no funciona como debería. Así que, en lugar de tener una experiencia cercana a lo que sería el mercado real, puedes acabar con una idea falsa de cómo debes actuar y comportarte cuando pongas en práctica el estudio que has hecho en la repetición/simulación. Así que entiende la cifra 01.

  • Todas las flechas en AZUL indican la primera etapa de inicialización. Esto ocurre en el mismo momento en que usted, como usuario, inicia el servicio en MetaTrader 5.
  • Una vez hecho esto, pasamos a la segunda etapa, que se ejecuta cuando el servicio crea el gráfico y utiliza la plantilla para inicializar el Expert Advisor y el Indicador de Control. Esto está representado por la flecha AMARILLA.
  • La etapa siguiente, representada por las flechas VERDES, consiste en la creación de la variable terminal global por el indicador de control y la configuración de esta variable por el servicio de reproducción/simulación. En este punto, puede observar que la clase C_Terminal envía datos al indicador de control, pero no a la clase C_Manager, que está siendo inicializada en este momento por el Expert Advisor.
  • La última etapa, que son las flechas VIOLETA, es la configuración, a partir de los datos proporcionados por el usuario en el fichero que dicta cómo funcionará la repetición/simulación, de la información que será necesaria para que el Expert Advisor sepa realmente qué tipo de cuenta se está utilizando.

Aunque pueda parecer confuso, tenemos que tener el máximo cuidado para que la clase C_Terminal permita al Indicador de Control hacer su trabajo. Pero, al mismo tiempo, no permita que el Expert Advisor haga nada antes de que la clase C_Manager esté correctamente configurada. ¿Y por qué digo que la gestión debe hacerse en la clase C_Terminal y no en la clase C_Manager? Quizás sería más sencillo hacerlo en la clase C_Manager. ¡¡¡Por qué no!!! La razón no es simplemente elegir qué clase guardará las cosas. La verdadera razón es la clase C_Orders. Si el sistema de gestión está en la clase C_Manager, la clase C_Orders no podría acceder a los datos que necesitamos. Pero el hecho de que hayamos puesto la gestión en la clase C_Terminal significa que, aunque más adelante utilicemos otra clase para gestionar las órdenes, ésta seguirá pudiendo acceder a los datos que necesitamos. Pero no podemos, como se ve en la figura 01, hacer el trabajo de cualquier manera; tenemos que asegurarnos de que se hace lo mejor posible. De lo contrario, todo el sistema podría bloquearse nada más ponerse en marcha. Sé que es difícil entender cómo pudo chocar. Pero créeme, si la programación no se hace con suficiente rigor, el sistema se colapsará. Así podemos ver cómo se ha implantado el sistema. Empezando por el código siguiente:

class C_Terminal
{
        protected:
                enum eErrUser {ERR_Unknown, ERR_PointerInvalid};
                enum eEvents {ev_Update};
//+------------------------------------------------------------------+
                struct st_Terminal
                {
                        ENUM_SYMBOL_CHART_MODE   ChartMode;
                        ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
                        long    ID;
                        string  szSymbol;
                        int     Width,
                                Height,
                                nDigits;
                        double  PointPerTick,
                                ValuePerPoint,
                                VolumeMinimal,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
                st_Terminal m_Infos;
                struct mem
                {
                        long    Show_Descr,
                                Show_Date;
                        bool    AccountLock;
                }m_Mem;

Esta variable dará acceso, por los medios ya implementados antes de este artículo, al tipo de cuenta. Con esto, no necesitaremos implementar ningún código nuevo en la clase solo para obtener esta información, lo cual es muy bueno. Pero también tenemos otra variable. Ésta servirá como BLOQUEO, pero pronto entenderás por qué y cómo se utilizará. Ahora tenemos que ver el constructor de la clase. Esto se puede ver a continuación:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      m_Mem.AccountLock = false;
      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_EVENT_OBJECT_CREATE, 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.ValuePerPoint / m_Infos.PointPerTick;
      m_Infos.ChartMode     = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
      if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
      ResetLastError();
   }

Aquí inicializamos la variable lock para indicar a la clase C_Terminal que el valor del tipo de cuenta aún no ha sido implementado. Pero estamos haciendo algo en este sentido, y eso es importante. Cuando el sistema detecta que el activo NO ES EL ACTIVO DE REPRODUCCIÓN, la clase C_Terminal inicializará los datos de la cuenta. Si se trata del activo de reproducción, los datos no se inicializarán ahora, sino más tarde, en la clase C_Manager. Entiendan esto, y entiéndanlo definitivamente, porque será de suma importancia muy pronto. La clase C_Manager inicializa los datos cuando el activo es un activo de reproducción. Cuando el activo NO es un activo de reproducción, se inicializa en este punto. Pero en cualquier caso, se llamará a un procedimiento, y esto se ve a continuación:

inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
   {
      if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
      m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
   }

Este procedimiento es extremadamente importante. Sin él, se desconocería el tipo de cuenta que se está utilizando. Ya que solo supondría que se está utilizando tal o cual tipo. No obstante, es importante evitar modificar la variable de tipo de cuenta una vez inicializada. Para ello, utilizamos este candado de aquí. Ten en cuenta que si, y solo si, la variable de bloqueo se establece en false, podemos inicializar el valor basado en el argumento pasado al procedimiento. Una vez hecho esto, la variable no podrá modificarse durante el resto de la ejecución del código. Otro detalle es que vamos a limitarnos a los tipos NETTING y HEDGING. La razón es que el tipo EXCHANGE funciona de forma muy parecida al tipo RED. Como estamos utilizando el sistema para operar muy cerca del mercado real, no veo ningún problema en limitarnos a los tipos NETTING y HEDGING. Con esto concluye la parte de código de la clase C_Terminal. Ahora vamos a ver lo que había que cambiar en la clase C_Manager. Básicamente, en esta clase, C_Manager, todo lo que tuvimos que hacer fue modificar el código del constructor. Ahora aparecerá como se muestra a continuación:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
      u_Interprocess info;
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                                                                        
      if (def_InfoTerminal.szSymbol == def_SymbolReplay)
      {
         do
         {
            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag)) Sleep(750);
         }while ((!info.s_Infos.isSync) && (!_StopFlag));
         def_AcessTerminal.SetTypeAccount(info.s_Infos.isHedging ? ACCOUNT_MARGIN_MODE_RETAIL_HEDGING : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
      };
      switch (def_InfoTerminal.TypeAccount)
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
      }
      Print("Detected Account ", szInfo);
   }

Puedes mirar y nada ha cambiado. O peor aún, puede que no entiendas lo que está pasando, lo cual no sería bueno. Ya que aquí tenemos un bucle donde, bajo ciertas condiciones, puede entrar en un bucle infinito. Pero gran parte del código sigue siendo idéntico al de antes, con una pequeña diferencia, que es precisamente este punto. Aquí probábamos todo tipo de cuentas; ahora probaremos sólo dos. Y la información para saber qué tipo de cuenta está en la variable creada en la clase C_Terminal. Pero veamos más de cerca la parte realmente nueva del código. Esto comienza cuando se identifica que el activo utilizado es el mismo que el utilizado en el sistema de repetición/simulación. Si esta prueba se supera, entraremos en un bucle doble, donde tenemos un primer bucle exterior y otro bucle anidado en este primero.

En este bucle anidado, esperaremos a que el Indicador de Control cree la variable terminal global para que el servicio de repetición/simulador pueda configurarla. Si el programa es terminado por el usuario o el indicador de control ha creado la variable terminal global, saldremos de este bucle anidado y entraremos en el bucle exterior. Este bucle exterior sólo se cerrará en dos situaciones: la primera es que el servicio de repetición/simulador ha configurado la variable terminal global; la segunda situación sería cuando el usuario cierra el programa. Aparte de estas dos situaciones, el bucle no se cerrará y volverá a entrar en el bucle anidado. Si todo va bien y se ha cerrado el bucle exterior, el valor que se utilizará como tipo de cuenta se enviará a la clase C_Terminal. 


Conclusión

En este artículo hemos resuelto un problema que, a pesar de ser pequeño, nos iba a causar muchos quebraderos de cabeza en el futuro. Espero que haya comprendido la importancia de este procedimiento, pero sobre todo cómo fue posible resolver el problema utilizando MQL5. El apéndice le da acceso al código fuente, así como los archivos actualizados que se utilizarán en la repetición/simulador. Si ya está utilizando el servicio, aunque no pueda formarse con él, no olvides actualizar también los archivos responsables de configurar el servicio de repetición/simulador para que el activo pertenezca al tipo de cuenta adecuado. En caso contrario, el sistema adoptará por defecto el tipo de cuenta HEDGING.

Esto podría causarle problemas en el futuro. Así que no te olvides de actualizar a partir de ahora.


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

Archivos adjuntos |
Files_-_FOREX.zip (3744 KB)
Files_-_BOLSA.zip (1358.28 KB)
Files_-_FUTUROS.zip (11397.55 KB)
Desarrollo de un sistema de repetición (Parte 37): Pavimentando el terreno (I) Desarrollo de un sistema de repetición (Parte 37): Pavimentando el terreno (I)
En este artículo, vamos a empezar a hacer algo que ojalá hubiera hecho hace mucho más tiempo. Sin embargo, debido a la falta de "terreno firme", no me sentía seguro para presentarlo públicamente. Ahora, tengo las bases para poder hacer lo que vamos a empezar a hacer a partir de ahora. Es una buena idea centrarse al máximo en comprender el contenido de este artículo, y no lo digo para que lo leas por leer. Quiero y necesito recalcar que, si no entiendes este artículo en concreto, puedes abandonar por completo cualquier esperanza de comprender el contenido de los siguientes.
Redes neuronales: así de sencillo (Parte 63): Entrenamiento previo del Transformador de decisiones no supervisado (PDT) Redes neuronales: así de sencillo (Parte 63): Entrenamiento previo del Transformador de decisiones no supervisado (PDT)
Continuamos nuestra análisis de la familia de métodos del Transformador de decisiones. En artículos anteriores ya hemos observado que entrenar el transformador subyacente en la arquitectura de estos métodos supone todo un reto y requiere una gran cantidad de datos de entrenamiento marcados. En este artículo, analizaremos un algoritmo para utilizar trayectorias no marcadas para el entrenamiento previo de modelos.
Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 2): Funciones MQL5 para interacción HTTP con API REST del juego de tres en raya Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 2): Funciones MQL5 para interacción HTTP con API REST del juego de tres en raya
Este artículo detalla cómo MQL5 puede interactuar con Python y FastAPI, utilizando llamadas HTTP en MQL5 para comunicarse con un juego de tres en raya en Python. En él se discute la creación de una API con FastAPI para esta integración e se incluye un script de prueba en MQL5, resaltando la versatilidad del MQL5, la simplicidad del Python y la eficiencia del FastAPI en la conexión de diferentes tecnologías para soluciones innovadoras.
Desarrollo de un sistema de repetición (Parte 35): Haciendo retoques (I) Desarrollo de un sistema de repetición (Parte 35): Haciendo retoques (I)
Tenemos que arreglar algunas cosas antes de poder continuar de verdad. Pero no es necesariamente una corrección, sino una mejora en la forma de gestionar y utilizar la clase. La razón es que hay fallos debidos a algún tipo de interacción dentro del sistema. A pesar de los intentos de comprender la razón de algunos de los fallos, para ponerles fin, todos ellos se vieron frustrados, ya que algunos no tenían sentido. Cuando usamos punteros o recursión en C / C++, y el programa empieza a fallar.