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

Desarrollo de un sistema de repetición (Parte 34): Sistema de órdenes (III)

MetaTrader 5Ejemplos | 3 abril 2024, 15:50
272 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior "Desarrollo de un sistema de repetición (Parte 33): Sistema de órdenes (II)", expliqué cómo vamos a proceder para construir nuestro sistema de órdenes. En ese artículo, expliqué gran parte del código y mostré un poco sobre los desafíos y problemas que tendremos que resolver. Aunque pueda parecer sencillo y fácil de aplicar, dista mucho de serlo. Así que en este artículo, vamos a avanzar un poco más hacia lo que realmente tenemos y necesitamos hacer en términos de implementación. Sin embargo, queda por discutir y explicar un último procedimiento presente en la clase C_Manager, así como comentar el código del Expert Advisor (EA). En el caso del código del EA, me centraré básicamente en el tema de las partes que han sido modificadas. Así, podrás tener una idea de cómo se comportará, sin necesidad de usarlo en la plataforma MetaTrader 5 para probar las cosas. Pero siéntete libre de realizar las pruebas que desees, ya que el código estará en el anexo de este artículo. Dado que el código estará en el anexo de este artículo.

Muchos pueden considerar innecesario probar el sistema tal y como se muestra, con la esperanza de tener un sistema más completo antes de probarlo realmente. Esta idea no es muy adecuada, pues si no entiendes cómo funciona el sistema ahora, tendrás grandes problemas para entender cómo funcionará en el futuro. Y peor aún, no podrás adaptar las cosas de manera que generes algo que te gustaría ver. Sin embargo, por una razón u otra, puede que no muestre cómo hacerlo, o que no me interese desarrollarlo. En cuanto ves el sistema desarrollarse poco a poco, puedes prestar más atención a algunos detalles, poner a prueba otros, o esperar a que el sistema evolucione hasta un punto en el que realmente sientas que es el momento de colocarlo en la plataforma y analizar cómo se comporta.

Sé que a mucha gente de hecho le gusta coger y utilizar un sistema más maduro, mientras que otros disfrutan viéndolo crecer y desarrollarse. Bueno, pero comencemos viendo el procedimiento que quedó pendiente. Y como explicarlo es un tema bastante largo, pasemos a otro.


La función principal de la clase C_Manager: DispatchMessage

Esta función, o mejor dicho procedimiento, es con toda seguridad el núcleo de toda la clase que estamos creando. Se encarga del manejo de eventos que son generados y enviados a nuestro programa, provenientes de la plataforma MetaTrader 5. En cuanto a algunos de estos eventos, le decimos a la plataforma que nos los envíe, como por ejemplo el evento CHARTEVENT_MOUSE_MOVE, mientras que otros son enviados, pero nuestro programa puede ignorarlos por no tener de hecho ninguna utilidad para el proyecto que estamos creando. Un ejemplo sería CHARTEVENT_OBJECT_CLICK.

El hecho de concentrar todo el tratamiento de eventos dentro de las clases en las que estamos trabajando facilita bastante la cuestión de hacer el proyecto en módulos. Aunque pueda parecer algo costoso a primera vista, con el tiempo acabas viendo que esto facilita mucho el traslado de código de un proyecto a otro, agilizando así el desarrollo de nuevos proyectos.

Aquí tenemos dos cuestiones:

  • La primera es que el hecho de concentrar el tratamiento de eventos en un único lugar, además de facilitar la portabilidad y el desplazamiento de códigos entre proyectos, reduce la cantidad de código que necesitamos realmente colocar dentro del código principal, en este caso, el EA. Esto facilita mucho la cuestión de la depuración, porque se reduce la cantidad de errores que pueden suceder, ya sea en el reúso de las clases, como en el manejo de eventos.
  • La segunda cuestión, y esta tal vez sea un poco más complicada y que involucra la forma en cómo cada uno programa va a trabajar, es el hecho de que en algunos tipos de código necesitamos que las cosas ocurran en una secuencia dada. Es muy común que las personas creen un código que necesita ser ejecutado en una secuencia exacta, de lo contrario el mismo fallará o producirá resultados erróneos.

Teniendo en cuenta estas dos cuestiones, podemos ver el código del procedimiento, así logrando saber y entender por qué se está haciendo, como podrás notar a continuación:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      static double price = 0;
      bool bBuy, bSell;
                                
      def_AcessTerminal.DispatchMessage(id, lparam, dparam, sparam);
      def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
      switch (id)
      {
         case CHARTEVENT_KEYDOWN:
            if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
            {
               if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  ToMarket(ORDER_TYPE_BUY);
               if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);
            }
            break;
         case CHARTEVENT_MOUSE_MOVE:
            bBuy = def_AcessMouse.CheckClick(C_Mouse::eSHIFT_Press);
            bSell = def_AcessMouse.CheckClick(C_Mouse::eCTRL_Press);
            if (bBuy != bSell)
            {
               if (!m_Objects.bCreate)
               {
                  def_AcessTerminal.CreateObjectGraphics(def_LINE_PRICE, OBJ_HLINE, m_Objects.corPrice, 0);
                  def_AcessTerminal.CreateObjectGraphics(def_LINE_STOP, OBJ_HLINE, m_Objects.corStop, 0);
                  def_AcessTerminal.CreateObjectGraphics(def_LINE_TAKE, OBJ_HLINE, m_Objects.corTake, 0);
                  EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
                  m_Objects.bCreate = true;
               }
               ObjectMove(def_InfoTerminal.ID, def_LINE_PRICE, 0, 0, def_InfoMouse.Position.Price);
               ObjectMove(def_InfoTerminal.ID, def_LINE_TAKE, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceTake, m_Infos.Leverage) * (bBuy ? 1 : -1)));
               ObjectMove(def_InfoTerminal.ID, def_LINE_STOP, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceStop, m_Infos.Leverage) * (bSell ? 1 : -1)));
               if ((def_AcessMouse.CheckClick(C_Mouse::eClickLeft)) && (price == 0)) CreateOrder((bBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), price = def_InfoMouse.Position.Price);
            }else if (m_Objects.bCreate)
            {
               EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
               ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
               m_Objects.bCreate = false;
               price = 0;
            }
            break;
         }
      }

    No te asustes de este código. Aunque parezca complicado a primera vista, en realidad, no debería darte miedo. Todo lo que estamos haciendo son cosas bastante simples y relativamente comunes. Sin embargo, la apariencia del código nos da la primera impresión de ser algo muy complejo y de difícil comprensión. Entonces, vamos por partes, como diría Jack el Destripador. Comenzaremos entendiendo las primeras llamadas vistas en el código. Y para explicar con más calma, dividiré el mismo en fragmentos. Así, creo que la explicación será bastante fácil y será mejor asimilada. Ya que nos enfocaremos en pocos puntos, no necesitarás estar desplazándote por la página para ver en qué punto de la explicación nos encontraremos.

    def_AcessTerminal.DispatchMessage(id, lparam, dparam, sparam);
    def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
    

    ¿Recuerdan que hace poco, hablé de que en algunos momentos necesitamos que las cosas se hagan en una secuencia determinada de eventos? Pues bien, estas dos líneas arriba hacen justamente eso. Ellas garantizarán que no nos olvidemos, o peor, que coloquemos en el código del EA el manejo de eventos en una secuencia incorrecta. No es que esto vaya a traer cualquier influencia ahora, en esta etapa de codificación. Pero cuanto menos código tengamos que preocuparnos por tener que colocar en el EA, mejor será en el futuro. Ya que el olvido o ausencia de un determinado tratamiento de eventos puede hacer que todo nuestro proyecto trabaje de manera completamente inesperada o diferente a lo que quisiéramos.

    Muy bien. Hechas estas observaciones, podemos comenzar observando el evento CHARTEVENT_KEYDOWN. Este se encargará los disparos que se hagan al presionar alguna tecla.

    case CHARTEVENT_KEYDOWN:
            if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
            {
                    if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  ToMarket(ORDER_TYPE_BUY);
                    if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);
            }
            break;
    

    Aquí tenemos una situación que puede parecerte un poco confusa, ya que por la documentación, la variable lparam contendrá el código de la tecla presionada. Esto de hecho sucede, pero el problema aquí es que necesitamos hacer la cosa de una manera un poco diferente. Al presionar una tecla, el sistema operativo generará un evento. Si MetaTrader 5 está recibiendo el foco del sistema operativo, este pasará el evento generado. Como nuestro código cuenta con un manejador específico para tratar las teclas presionadas, este aislará el tratamiento de las teclas de otros tipos de eventos.

    Nota: El código visto arriba de hecho no necesita ser colocado en el evento CHARTEVENT_KEYDOWN; podría ser colocado fuera de este evento. Sin embargo, al colocarlo en el evento CHARTEVENT_KEYDOWN, evitamos un tipo de situación un poco embarazosa. Tal situación ocurre cuando ejecutamos el análisis de condición de las teclas, es decir, un evento CHARTEVENT_KEYDOWN, cuando la plataforma nos está alertando de algún otro tipo de evento que fue disparado por alguna razón.

    Piensa en la extraña cuestión de procesar el estado del teclado en que se ha disparado un evento, por ejemplo, CHARTEVENT_CHART_CHANGE, que se activa cuando ocurre algún cambio en el gráfico. Y nuestro programa allí, verificando el estado del teclado. Este tipo de cosa no tiene ningún sentido práctico, además de costar tiempo de ejecución. Por eso es que estoy aislando el análisis del estado del teclado en un evento CHARTEVENT_KEYDOWN.

    Pero volviendo al código, podrás notar que estoy utilizando la función TerminalInfoInteger con el fin de saber y aislar un cierto código de teclado. Si no se hace de esta manera, tendríamos un sobreesfuerzo para verificar si la tecla CTRL se presionó al mismo tiempo que otra tecla, en este caso FLECHA HACIA ARRIBA o FLECHA HACIA ABAJO. Y es precisamente esto lo que estamos haciendo. Necesitamos una combinación de teclas para que nuestro programa, en este caso el EA, sepa qué debe hacerse en términos de programación. En el caso de presionar la combinación CTRL + FLECHA HACIA ARRIBA, el EA deberá entender esto como un deseo nuestro de hacer una compra a precio de mercado. Si la combinación es CTRL + FLECHA HACIA ABAJO, el EA procederá a vender a valor de mercado. Vean que, aunque tengamos los valores de las teclas siendo indicados de forma individual en la variable lparam, esto no nos favorece al punto de poder trabajar con combinaciones de teclas. Pero haciendo de la manera como se está haciendo, el hecho de presionar solo una de las teclas de la combinación no hará de ninguna manera que el EA reciba la indicación de operar usando el precio de mercado.

    Si crees que esta combinación puede entrar de alguna forma en conflicto con algo que estés utilizando, bastará con modificarla. Pero ten cuidado de mantener el código dentro del evento CHARTEVENT_KEYDOWN para poder aprovechar el hecho de que solo se ejecutará cuando MetaTrader 5 dispare un evento de teclado, evitando así que el código se ejecute sin necesidad. Otra cosa es que el código de teclado visible en la variable lparam sigue una tabla que varía de región a otra, lo que complica bastante las cosas si quieres vender o poner a disposición el ejecutable. Pero usando de la forma como estoy mostrando, tal tabla no será de hecho usada.

    Ahora vamos a ver el próximo manejador de eventos, CHARTEVENT_MOUSE_MOVE. Pero para facilitar la explicación, lo dividiré en pequeños fragmentos, siguiendo obviamente el orden en que aparecen en el código de la clase.

    case CHARTEVENT_MOUSE_MOVE:
            bBuy = def_AcessMouse.CheckClick(C_Mouse::eSHIFT_Press);
            bSell = def_AcessMouse.CheckClick(C_Mouse::eCTRL_Press);
    

    Observen aquí una cosa. Aquí estamos usando la clase C_Study con el fin de tener acceso a la clase C_Mouse. No olvides este detalle. Pero observen, a diferencia del manejador de eventos visto arriba, CHARTEVENT_KEYDOWN, aquí estamos capturando el estado de teclas. Pero éstas se refieren al ratón. ¿Te confundió? En realidad, estas teclas realmente se refieren al ratón y no al teclado alfanumérico en sí. Pero, ¿por qué? ¿Estás intentando confundirme? Nada de eso, mi querido lector. El hecho de que puedas presionar las teclas SHIFT y CTRL en el teclado alfanumérico, y aún así conseguir trabajar esto dentro de la clase C_Mouse, se debe al hecho de que no es bien así como las cosas funcionan. Estas teclas SHIFT y CTRL de hecho se refieren al ratón. Pero no a cualquier ratón. Estoy hablando de un tipo muy específico, más o menos como el visto en la imagen 01, justo abajo:

    Figura 01

    Imagen 01

    Este es el tipo de ratón del que estamos hablando, ya que cuenta con botones extras en su cuerpo. Entonces, para el sistema operativo y, por consecuencia, para la plataforma y para nuestro programa, las teclas SHIFT y CTRL a las que nos estamos refiriendo en realidad son parte del ratón. Pero como puede ser que tu ratón no cuente con este tipo de botones extras, el sistema operativo te permite hacer uso del teclado, y por esto, la plataforma, así como el programa, hará que el código sea interpretado de la manera correcta. Entonces, no confundas las teclas SHIFT y CTRL del evento CHARTEVENT_KEYDOWN con estas usadas aquí en el evento CHARTEVENT_MOUSE_MOVE.

    Pues bien, ahora que tenemos el estado de las teclas SHIFT y CTRL, podemos ver el resto del código de evento. Este puede ser visto en el fragmento justo abajo.

            if (bBuy != bSell)
            {
                    if (!m_Objects.bCreate)
                    {
                            def_AcessTerminal.CreateObjectGraphics(def_LINE_PRICE, OBJ_HLINE, m_Objects.corPrice, 0);
                            def_AcessTerminal.CreateObjectGraphics(def_LINE_STOP, OBJ_HLINE, m_Objects.corStop, 0);
                            def_AcessTerminal.CreateObjectGraphics(def_LINE_TAKE, OBJ_HLINE, m_Objects.corTake, 0);
                            EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
                            m_Objects.bCreate = true;
                    }
                    ObjectMove(def_InfoTerminal.ID, def_LINE_PRICE, 0, 0, def_InfoMouse.Position.Price);
                    ObjectMove(def_InfoTerminal.ID, def_LINE_TAKE, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceTake, m_Infos.Leverage) * (bBuy ? 1 : -1)));
                    ObjectMove(def_InfoTerminal.ID, def_LINE_STOP, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceStop, m_Infos.Leverage) * (bSell ? 1 : -1)));
                    if ((def_AcessMouse.CheckClick(C_Mouse::eClickLeft)) && (price == 0)) CreateOrder((bBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), price = def_InfoMouse.Position.Price);
            }else if (m_Objects.bCreate)
            {
                    EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
                    ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
                    m_Objects.bCreate = false;
                    price = 0;
            }
    

    Aunque este código parezca complicado, en realidad está haciendo tres cosas bastante simples. Estas podrían estar aisladas en alguna rutina o procedimiento privado dentro de la clase. Pero, por ahora, quedarán aquí, de manera de simplificar un poco las cosas, al menos en lo que se refiere a las llamadas. Lo primero que hay que hacer es indicarle a la plataforma, a través del EA, que queremos enviar una orden pendiente. Pero antes de hacerlo de hecho, queremos ver dónde estarán los límites y dónde se posicionará la orden. Para hacer esto, hacemos uso de tres objetos que son creados en este punto del código. Observen que junto con esta creación, estaremos enviando un evento al sistema de clase C_Mouse, con el fin de decirle a la clase C_Mouse que el ratón deberá ser ocultado en los próximos instantes. Esta es la primera fase.

    Una vez que tenemos los objetos que necesitamos en el gráfico, los movemos de una forma muy concreta. Esto se hace usando este conjunto de funciones. Pero si el usuario nos informa de que el punto deseado para enviar la orden es el que se ve en el gráfico a través de los objetos creados, ejecutaremos una solicitud de colocación de orden pendiente. Noten cómo se están haciendo las pruebas para que podamos saber qué está pasando con el ratón y en el gráfico.

    Ya en el tercer y último punto, las cosas se desarrollan de la siguiente manera: Primero se envía un evento al sistema de clase C_Mouse para que el ratón vuelva a ser visible en el gráfico. Inmediatamente después, procederemos a remover los objetos que fueron creados.

    Ahora hay algo importante en este código arriba. Algo muy importante y en lo que siempre debes estar muy atento al programar tus propios códigos. Si estás empezando en el campo de la programación, puede que no te hayas dado cuenta de algo interesante, pero al mismo tiempo peligroso, en el código anterior. Esto, si no se hace de forma adecuada. Estoy hablando de la RECURSIÓN. Y sí, en este código de arriba estamos haciendo uso de recursión, y si esto no está bien planeado, terminarás entrando en un bucle infinito. Tan pronto como el sistema entre en la región del código que hace uso de la recursión, podría nunca más salir de allí.

    Para entender cómo se produce esta recursión, mira la figura 02, justo abajo:

    Figura 02

    Figura 02: Flujo de mensajes internos.

    La flecha verde en la figura 02 es precisamente donde ocurre la recursión. Pero, ¿cómo está ocurriendo esto realmente en el código? Para verlo, basta observar el fragmento abajo, donde estoy mostrando el procedimiento DispatchMessage, presente en la clase C_Manager.

    void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
       {
    
    // ...                          
    
          def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
          switch (id)
          {
    
    // ...
    
             case CHARTEVENT_MOUSE_MOVE:
    // ...
                if (bBuy != bSell)
                {
                   if (!m_Objects.bCreate)
                   {
    // ...
                      EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
                   }
    
    // ...
    
                }else if (m_Objects.bCreate)
                {
                   EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
    
    // ...
                }
                break;
             }
          }
    

    Al observar la figura 02, tal vez no quede muy claro para ti la recursión, y solo mirar el código arriba también puede no ser suficientemente claro. Pero cuando unimos la figura 02 con el código arriba, podemos notar cómo ocurre la recursión. Si esto no está bien planeado, tendrás serios problemas. Entonces vamos a la explicación, para que tú, aspirante, logres comprender el poder de la recursión al mismo tiempo que su peligro. Siempre que necesites hacer algo muy complicado, sea lo que sea, es posible dividir el proceso en pasos menores o más simples. Estos pasos pueden repetirse una cierta cantidad de veces, con el fin de que tengamos como resultado algo mucho más complejo, pero que seguirá un concepto simple. Este es el poder de la recursión. Pero no se usa solo y exclusivamente en este tipo de escenario. A pesar de que, en la gran mayoría de las veces, está vinculada a esto, podemos usarla en otras cuestiones.

    El fragmento arriba es justamente una de estas cuestiones. Para seguir la explicación, te pido que observes también la figura 02. Todo comienza cuando el usuario genera un evento que hace que la plataforma MetaTrader 5 dispare una llamada a la función OnChartEvent. Al hacer esto, esta función llama a la rutina DispatchMessage dentro de la clase C_Manager. En este punto, tenemos la llamada al procedimiento de tratamiento de eventos, presente por motivo de herencia, en la clase C_Mouse, en especial la rutina DispatchMessage presente en esta clase. Cuando la rutina retorna, el programa continúa de donde se detuvo y entramos en el tratamiento de evento con el fin de verificar si el usuario está queriendo o no que las líneas auxiliares sean creadas o eliminadas. Quien decide esto es el programa. Pero de cualquier manera, tendremos en algún momento la ejecución de una llamada EventChartCustom. En este momento, se activa la recursión.

    Lo que de hecho sucede es que esta llamada hará que la plataforma MetaTrader 5 efectúe un nuevo llamado a la función OnChartEvent, que hará que esta sea ejecutada nuevamente y se llame a la rutina DispatchMessage de la clase C_Manager. Esta, a su vez, realizaría nuevamente la llamada, por motivo de herencia, a la clase C_Mouse, con el fin de ejecutar el procedimiento personalizado, que hará que el indicador del ratón aparezca o desaparezca, dependiendo del caso. Pero debido a la recursión, el código no volverá como muchos pueden imaginar inicialmente. De hecho, volverá, pero hará nuevamente que todo el código presente en la rutina DispatchMessage en la clase C_Manager se ejecute. Y aquí es donde reside el peligro: si colocas una llamada de modo que el evento personalizado, que fue tratado en la clase C_Mouse, también aparezca en la clase C_Manager, este también será tratado en la clase C_Manager. Y si, por casualidad, haces que el evento vuelva a ser tratado en la clase C_Mouse, haciendo uso de la función EventChartCustom, terminaremos cayendo en un bucle infinito.

    Pero como la clase C_Manager no tiene el mismo evento y esto es importante, que lo maneja la clase C_Mouse, volveremos a la función OnChartEvent, que será ejecutada integralmente. Cuando la función OnChartEvent haya sido ejecutada, volverá al punto donde la llamada EventChartCustom, que puede ser vista en el fragmento arriba, fue ejecutada, lo que hará que todo el resto del código del procedimiento DispatchMessage, presente en la clase C_Manager, se ejecute. Cuando este termine, volveremos nuevamente a la función OnChartEvent, donde será nuevamente ejecutada integralmente, liberando así finalmente a la plataforma para poder ejecutar otro tipo de evento cuando este ocurra.

    Entonces, al hacer una llamada EventChartCustom, tendremos al menos la ejecución completa de dos veces el código presente en la función OnChartEvent, esto debido a la recursión. Parece ser poco eficiente, pero el hecho es que el código, por ser simple, no trae mucho costo para el rendimiento general de la plataforma. Pero es bueno que siempre estés consciente de lo que está de hecho ocurriendo. El costo de la recursión en este caso es bastante bajo frente al hecho de tener un código más modular. Pero en algunas situaciones, puede ser que este costo no compense y haga que el código sea muy lento. En este caso, tendríamos que tomar algún otro tipo de medida, pero no es el caso en este momento.

    Creo haber explicado en detalle este procedimiento DispatchMessage presente en la clase C_Manager. Y aunque parezca ser algo bastante complicado, el hecho es que estamos lejos de tener algo realmente complicado aquí, pues el sistema aún no es capaz de trabajar con un modelo de órdenes cruzadas. Para hacer esto, este procedimiento DispatchMessage deberá pasar por grandes cambios, pero eso quedará para el futuro.

    Entonces vamos a ver cómo quedó el código del EA, para poder utilizar el sistema de manera adecuada.


    Analizar las actualizaciones en el EA

    Aunque, por el momento, el EA ya pueda enviar órdenes y nos ayude a operar dentro de un marco de tiempo, el mismo no ha sufrido así grandes cambios en su código. Pero aun así, existe una parte del código que merece algún destaque. Por eso, tendremos una explicación, con el fin de que todos puedan comprender qué está ocurriendo allí. Este punto que merece destacarse está vinculado a la interacción con el usuario y al código presente en el evento OnInit. Pero vamos a empezar con la parte referente a la interacción con el usuario. Esto puede ser visto en el fragmento abajo:

    input group "Mouse";
    input color     user00 = clrBlack;      //Price Line
    input color     user01 = clrPaleGreen;  //Positive Study
    input color     user02 = clrLightCoral; //Negative Study
    input group "Trade";
    input uint      user10 = 1;             //Leverage
    input double    user11 = 100;           //Take Profit ( Finance )
    input double    user12 = 75;            //Stop Loss ( Finance )
    input bool      user13 = true;          //Is Day Trade
    //+------------------------------------------------------------------+
    input group "Control of Time"
    input string    user20  = "00:00 - 00:00";      //Sunday
    input string    user21  = "09:05 - 17:35";      //Monday
    input string    user22  = "10:05 - 16:50";      //Tuesday
    input string    user23  = "09:45 - 13:38";      //Wednesday
    input string    user24  = "11:07 - 15:00";      //Thursday
    input string    user25  = "12:55 - 18:25";      //Friday
    input string    user26  = "00:00 - 00:00";      //Saturday
    

    Este fragmento arriba es responsable de interactuar con el usuario. Aquí tenemos dos nuevos grupos de información que pueden ser accedidos y configurados por el usuario. En el primer grupo, el usuario puede informar cómo será realizada la operación, sea ella a precio de mercado o pendiente. Los valores configurados aquí son, en resumen, sencillos de entender. El valor que representa la apalancamiento (user 10) deberá ser llenado de modo que represente la cantidad de veces que utilizarás el volumen mínimo exigido para negociar el activo. En el caso de fórex, muy probablemente usarás un valor de 100, o algo por el estilo, con el fin de operar buscando un buen margen para trabajar. De lo contrario, estarás trabajando en el orden de centavos, lo que hará que las líneas de límite queden muy lejanas del punto donde esperabas verlas. En el caso de estar operando en la bolsa y en de forma fraccionada, deberás informar el número de acciones a ser utilizadas. Ahora, si no es así, deberás informar la cantidad de lotes. Para operaciones de futuros, deberás informar la cantidad de contratos. En fin, algo trivial. En la parte sobre el take profit (user 11) y stop loss (user 12), no deberás informar la cantidad de puntos, sino el valor financiero a ser utilizado. Este valor será ajustado de manera adecuada por el código para representar la posición correcta en el precio del activo. La última variable (user 13) sirve solo para informar si estás configurando una posición más larga o si será algo corto, que terminará al final de la sesión de negociación.

    Nota importante: Sin embargo, prueba este mecanismo con cautela, ya que algunos corredores de bolsa tienen servidores solo para day trading y otros para colocar órdenes. Busca informarte previamente sobre tal cosa junto a tu corredor de bolsa.

    Ahora, en el segundo grupo, tenemos algo que necesitarás poner a prueba antes de configurar de manera adecuada. No es que sea algo complejo o de difícil comprensión, sino porque necesitas entender que estos valores determinarán desde qué momento hasta qué momento el EA permitirá que envíes órdenes o colocar órdenes pendientes. La cuestión sobre gestionar, terminar o incluso modificar las órdenes ya no dependerá necesariamente del EA. Eso será responsabilidad de la plataforma MetaTrader 5, por lo menos por ahora.

    Entonces, puedes configurar una ventana de 1 hora donde el EA permitirá que operes utilizando los recursos presentes en él. Esto debe ser hecho considerando que la configuración se hace enfocada a la semana, y no a un día específico o una fecha especial.

    Para entender esto, es necesario ver el código OnInit. Este puede ser visto justo abajo:

    int OnInit()
    {
            string szInfo;
            
            terminal = new C_Terminal();
            study    = new C_Study(terminal, user00, user01, user02);
            manager  = new C_Manager(terminal, study, user00, user02, user01, def_MagicNumber, user12, user11, user10, user13);
            
            if (_LastError != ERR_SUCCESS) return INIT_FAILED;
            
            for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
            {
                    switch (c0)
                    {
                            case SUNDAY     : szInfo = user20; break;
                            case MONDAY     : szInfo = user21; break;
                            case TUESDAY    : szInfo = user22; break;
                            case WEDNESDAY  : szInfo = user23; break;
                            case THURSDAY   : szInfo = user24; break;
                            case FRIDAY     : szInfo = user25; break;
                            case SATURDAY   : szInfo = user26; break;
                    }
                    (*manager).SetInfoCtrl(c0, szInfo);
            }
    
            MarketBookAdd(def_InfoTerminal.szSymbol);
            OnBookEvent(def_InfoTerminal.szSymbol);
            EventSetMillisecondTimer(500);
    
            return INIT_SUCCEEDED;
    }
    

    La parte que realmente nos interesa en este código OnInit está resaltada en el código arriba. Lo que estamos haciendo aquí es darle una visión completa de cómo el EA debe comportarse en el transcurso de una semana. No solo en un día específico, sino durante toda la semana. Existen activos o mercados, en este caso el fórex, donde las operaciones son prácticamente continuas y no se finalizan en ningún momento del día. Si pensaras en usar un sistema de control basado en marcos de tiempo en un EA que podría estar encendido 24 horas al día, terminarías teniendo problemas en la transición del día. Es decir, justo cuando fueran las 23:59:59, el EA tendría que ser detenido y volver al siguiente segundo para saber cuál sería el nuevo marco de tiempo. Pero utilizando este método arriba, el mismo puede permanecer encendido 24 horas al día, 7 días a la semana, por 52 semanas al año, sin que llegue a perderse sin saber cuál sería el marco de tiempo que debería ser utilizado. Sé que solo observando este código, muchos pueden no conseguir entender cómo esto de hecho ocurre. Por eso, es necesario poner a prueba el EA para comprender el funcionamiento de este sistema. Pero este sistema no es una novedad. Ya se ha explorado anteriormente:  Cómo construir un EA que opere automáticamente (Parte 10): Automatización (II), para más detalles ve el artículo mencionado.


    Conclusión

    A pesar de que el sistema parece bastante estable y versátil, empezó a ser afectado por una falla. Esta es aparentemente bastante extraña y desconcertante, ya que no tiene sentido que tal falla aparezca. El motivo es que la falla apareció en un punto que no sufrió ningún tipo de modificación, edición o algo que justificara su surgimiento. Pero incluso en estas condiciones, hablaré sobre esto en el próximo artículo. En el anexo, tendrás acceso al código en su actual etapa de desarrollo, pudiendo así ser estudiado y analizado en detalle. Dado que en el artículo anterior no se mostraron las mejoras realizadas. 

    Observa el hecho de lo que se comentó en el artículo pasado, donde hablé sobre el sistema de seleccionar objetos, dando solo un clic, diferente del modo estándar de la plataforma que es usando doble clic. Pero de cualquier manera, lo ideal es ver el sistema funcionando en tu plataforma y así sacar tus propias conclusiones al verlo en acción. Justo ahí, donde puedes hacer tus propios tests.



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

    Archivos adjuntos |
    Files_-_BOLSA.zip (1358.24 KB)
    Files_-_FOREX.zip (3743.96 KB)
    Files_-_FUTUROS.zip (11397.51 KB)
    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.
    Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 1): Como usar RestAPIs en MQL5 Cómo desarrollar un agente de aprendizaje por refuerzo en MQL5 con Integración RestAPI (Parte 1): Como usar RestAPIs en MQL5
    Este artículo aborda la importancia de las APIs (application programming interface) en la comunicación entre diferentes aplicaciones y sistemas de software. En él, se destaca el papel de las API a la hora de simplificar la interacción entre aplicaciones, ya que les permiten compartir datos y funcionalidades de forma eficiente.
    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.
    Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 3): Prefijos/sufijos de símbolos y sesión comercial Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 3): Prefijos/sufijos de símbolos y sesión comercial
    Últimamente, he recibido comentarios de varios compañeros tráders sobre cómo usar el asesor multidivisa que estamos analizando con brókeres que utilizan prefijos y/o sufijos con nombres de símbolos, así como sobre la forma de implementar zonas horarias comerciales o sesiones comerciales en el asesor.