Tablero de cotizaciones: Versión mejorada

Daniel Jose | 9 enero, 2023

Introducción

En el artículo Tablero de cotizaciones: Versión básica, le mostramos cómo puede crear un indicador, similar a un tablero, que muestre las cotizaciones de los activos en tiempo real. Sin embargo, ustedes habrán notado que no implementamos completamente el indicador en el artículo anterior, no porque no fuera posible, sino porque el objetivo era mostrar el proceso de creación del indicador y cómo es posible hacerlo funcionar con la menor cantidad de código posible, para dar la impresión de que va rotando.

Demostramos que no era necesario crear un código o cálculo específico para hacer que el tablero pasara de derecha a izquierda o viceversa, lo único que tenía que hacer el usuario era indicar si el valor se incrementaba o disminuía. De ese modo, era posible hacer que el indicador se dirigiera a la derecha, a la izquierda o se quedara quieto, mostrándonos sólo los datos del mercado.

Pero esos datos no siempre son suficientes ni representativos de lo que mucha gente quiere ver realmente en un tablero. Mucha gente quiere tener información más completa y detallada, así que aquí haremos eso, además de implementar algunas cosas más en el tablero para hacerlo más útil. El mero hecho de tener un tablero como una cuadrícula de cotizaciones es insuficiente, y sigue siendo completamente inútil, ya que hay otras formas más apropiadas de crear una.

Así pues, lo primero que haremos será modificar el rótulo para añadir una imagen, como el logotipo del activo o alguna otra imagen que facilite la rápida identificación del activo que estamos viendo. Dicen que una imagen vale más que mil palabras, así que veamos si es cierto.


Implementación de un nuevo sistema de tablero

Lo primero que haremos en esta nueva implementación es crear una nueva clase de objeto para abstraer el objeto que representará la imagen o logotipo del activo que queremos observar. Para ello utilizaremos el siguiente código:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
class C_Object_BtnBitMap : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
                void Create(string szObjectName, string szResource1)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "\\Images\\Widget\\Bitmaps\\" + szResource1 + ".bmp");
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false);
                        };
//+------------------------------------------------------------------+
};

Voy a dar una explicación rápida de lo que está pasando aquí antes de explicar por qué estamos haciendo las cosas de esta manera. En primer lugar, creamos un objeto de tipo Bitmap para almacenar la imagen. Utilizamos la clase genérica para facilitar la creación de este objeto. A continuación, indicamos al objeto qué imagen debe utilizarse, pero prestando atención a la ubicación y al nombre de la imagen. Por último, indicamos al objeto que la imagen a mostrar es la de índice 0. De esta forma, el sistema tendrá un nivel de abstracción muy alto.

Bien, ahora vamos a entender por qué hacemos las cosas de esta manera y no de otra.

En primer lugar, debemos entender que existen algunas limitaciones en los objetos, no en lo que se puede hacer, sino en cómo se puede hacer, y el uso de una clase para crear una abstracción del sistema nos permite ocultar lo que realmente está sucediendo al resto del código. Nos importa poco lo que la clase C_Object_Bitmap necesita hacer para mostrar una imagen en la pantalla.

Ahora presten atención a los siguientes hechos, si la imagen no existe, o es de un formato diferente, ni el resto del código será informado de ello, ni tampoco el usuario. Lo único que se hará será mostrar una casilla vacía, indicando que algo es incorrecto. Así que, si usted quiere informar al usuario que algo salió mal, simplemente pruebe el retorno de la función ObjectSetString y si es falso, eso significa que la imagen no pudo ser cargada y el objeto puede ser removido de la lista de objetos. Sin embargo, aquí no lo hice. ¿Por qué?

La razón es que para nosotros no supone la menor diferencia porque todos los activos que se coloquen en el tablero tendrán una imagen que los representará, pero hay otra razón, que quizá sea aún más fuerte.

Si observamos con calma, nos daremos cuenta de que el código de esta forma no nos permite utilizar otro tipo de imagen que no sea un archivo de mapa de bits, ni siquiera lograr poner una imagen que tenga un fondo transparente. Esto no significa que sea imposible utilizar este tipo de cosas, como un fondo transparente, pero en algunos casos es posible que desee poner un logotipo o el logotipo del activo y no ese cromo de álbum, y en estos casos, puede ocurrir que la imagen presente información extraña. Entonces sería necesario encontrar otra forma de presentar estas imágenes, además de la que se ve en el código anterior.

Una de estas formas se ve en el artículo Cómo hacer el gráfico más interesante: Adicionando un fondo de pantalla, en ese artículo, mostramos cómo podemos utilizar un método diferente para construir una imagen directamente a través del recurso. Aunque seguimos utilizando un archivo de mapa de bits, porque tiene un modelo de codificación interna más sencillo, nada nos impide utilizar otros formatos, como un archivo GIF con una animación. El punto principal es que, utilizando este método, podemos crear y utilizar una imagen con un fondo transparente, lo que no es posible aquí.

Debido a esto, esta abstracción aquí es tan importante, porque si queremos utilizar otro formato de imagen o incluso utilizar una imagen de fondo transparente, no tendremos que hacer ningún cambio en el resto del código, sólo tenemos que cambiar el código de esta clase con el fin de implementar lo que necesitamos y queremos.

Por esta razón, hicimos este código como una clase, a pesar de que es tan corto y pequeño, porque de esta manera podemos ocultarle a las otras partes del tablero lo que estará sucediendo aquí.

Además de esta adición al código, también tenemos otro cambio, éste se encuentra en la clase C_Object_Edit. Veamos lo que ha cambiado en la clase:

template < typename T >
void Create(string szObjectName, color corTxt, color corBack, T InfoValue, string szFont = "Lucida Console", int iSize = 10)
        {
                C_Object_Base::Create(szObjectName, OBJ_EDIT);
                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, szFont);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, iSize);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_LEFT);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, corBack);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, corBack);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
                SetTextValue(szObjectName, InfoValue, corTxt);
        };

Como ahora tendremos más información sobre el tablero, necesitamos que la clase encargada de poner el texto en el gráfico sea un poco más compleja que la que existía originalmente. Con esto ahora, también tendremos control sobre qué fuente y de qué tamaño es esta fuente, y haciendo esto, podemos poner cosas mucho más diversas de lo que era posible antes. Esta mayor diversidad también nos lleva a cambios en otra función de la clase.

template < typename T >
void SetTextValue(string szObjectName, T InfoValue, color cor = clrNONE, const string szSufix = "")
        {
                color clr = (cor != clrNONE ? cor : (color)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR));
                string sz0;
                
                if (typename(T) == "string") sz0 = (string)InfoValue; else
                if (typename(T) == "double")
                {
                        clr = (cor != clrNONE ? cor : ((double)InfoValue < 0.0 ? def_ColorNegative : def_ColoPositive));
                        sz0 = Terminal.ViewDouble((double)InfoValue < 0.0 ? -((double)InfoValue) : (double)InfoValue) + szSufix;
                }else   sz0 = "?#?";
                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, sz0);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clr);
        };

De nuevo, vean cómo la abstracción marca la diferencia, aquí podemos indicar varias cosas mucho más de lo que era posible antes. Así que veamos lo que estamos haciendo realmente en este procedimiento de arriba:

Cuando se especifica un color, éste se utilizará; si no tenemos un valor introducido, utilizaremos el color que ya existe en el objeto, esto ayuda mucho, porque puede que sólo queramos cambiar el texto, y en este caso se mantendrá el color que ya tenía el objeto. En este punto, comprobamos si el tipo es una string, esto se hace en RUN TIME, por lo que debemos asegurarnos de que las cosas son correctas.

También podemos estar utilizando un tipo double, que se utiliza principalmente para indicar el precio del activo, y si esto se observa en RUN TIME, estableceremos los valores de una manera adecuada y con un color adecuado también. Ahora tenemos un dato curioso, que nos permite hablar un poco más del valor, y para ello utilizaremos un sufijo que será informado durante la llamada. Si no se entienden las cosas, también tenemos un formato para indicarlo. A continuación, mostramos el texto y ajustamos su color.

Sólo con las modificaciones que hemos hecho, ya podemos crear información de la siguiente manera:


Es decir, tenemos una imagen en la esquina izquierda, el código del activo se puede ver en la parte superior izquierda, todavía en la parte superior, sólo en el lado derecho, tenemos la variación del activo en el día, en la parte inferior, en la esquina izquierda, tenemos una flecha que indica si el activo está teniendo una variación positiva o negativa, y en la esquina inferior derecha, tenemos la última cotización del activo.

Pero para poder realmente presentar toda esta información, tenemos que hacer algunos cambios en la clase C_Widget, así que vamos a ver cómo resultó.


La nueva clase C_Widget

Al abrir el archivo de cabecera C_Widget.mqh, observamos algunas diferencias respecto a la versión más básica de este sistema. Vamos a entender lo que ha cambiado:

#include "Elements\C_Object_Edit.mqh"
#include "Elements\C_Object_BackGround.mqh"
#include "Elements\C_Object_BtnBitMap.mqh"
//+------------------------------------------------------------------+
C_Terminal Terminal;
//+------------------------------------------------------------------+
#define def_PrefixName  "WidgetPrice"
#define def_MaxWidth    160
//+------------------------------------------------------------------+
#define macro_MaxPosition (Terminal.GetWidth() >= (m_Infos.nSymbols * def_MaxWidth) ? Terminal.GetWidth() : m_Infos.nSymbols * def_MaxWidth)
#define macro_ObjectName(A, B) (def_PrefixName + (string)Terminal.GetSubWin() + CharToString(A) + "#" + B)

Ahora tenemos la inclusión de la clase C_Object_BtnBitMap.mqh, este archivo de cabecera es lo que va a soportar las imágenes que se utilizarán como logotipos. Además, también podemos ver que se ha modificado la anchura de la estructura, lo que nos permite albergar más información.

Quizás lo que más nos llame la atención es la macro que crea el nombre que se utilizará en los objetos, prestemos atención a ella porque se utilizará varias veces.

Una vez hecho esto, entramos en la parte donde tenemos las declaraciones de todas las cosas privadas de la clase del objeto:

class C_Widget
{
        protected:
                enum EventCustom {Ev_RollingTo};
        private :
                enum EnumTypeObject {en_Background = 35, en_Symbol, en_Price, en_Percentual, en_Icon, en_Arrow};
                struct st00
                {
                        color CorBackGround,
                              CorSymbol;
                        int   nSymbols,
                              MaxPositionX;
                        struct st01
                        {
                              string szCode;
                        }Symbols[];
                }m_Infos;

// ... Restante do código

Aunque parece que aquí no ha habido cambios, sí que han surgido y están presentes, así que vamos a hacer un repaso rápido, ya que aparecerán mucho más adelante, y se podrán entender mejor en los momentos en que se utilicen. Lo primero que llama la atención es la enumeración de los tipos de objetos. En realidad no indica qué tipo de objeto estamos creando, sino cuál es el uso del objeto. Sin embargo, no se preocupe, a medida que avancemos en el código de la clase, esta enumeración será más fácil de entender.

Pero entonces cabe preguntarse: ¿por qué no utilizar definiciones en lugar de esta enumeración? La razón es que debemos garantizar que cada objeto sea único, y utilizando una enumeración lo garantizamos, pero si utilizáramos definiciones, correríamos el riesgo de tener dos definiciones con el mismo valor, y en este caso los objetos creados seguramente no serían únicos. Recuerde este detalle cuando programe.

Ahora comencemos a ver los cambios que ocurrieron en el sistema, en cuanto a los procedimientos internos, comencemos con la rutina que crea el fondo del tablero.

void CreateBackGround(void)
        {
                C_Object_BackGround backGround;
                string sz0 = macro_ObjectName(en_Background, "");
                                
                backGround.Create(sz0, m_Infos.CorBackGround);
                backGround.Size(sz0, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH), Terminal.GetHeight());
        }

Aquí utilizamos la primera de las enumeraciones, por lo que vale la pena explicar por qué las enumeraciones comienzan con el valor 35 y no, con otro valor, pero primero terminemos de ver una última cosa importante aquí, cuando cambiamos las dimensiones del gráfico, se genera una llamada a OnChartEvent, de esta manera podemos comprobar cual es la nueva dimensión para poder actualizar las cosas.

Pero en cierto modo, podemos evitar cambiar las dimensiones del fondo del tablero, ya que la altura del tablero siempre será fija, y más adelante veremos dónde fijamos esta altura, pero entonces, para fijar la anchura y así poder ignorar los cambios en la anchura del tablero, utilizamos este método, que aunque parezca no tan efectivo, nos garantiza que el fondo siempre será lo suficientemente grande como para servirnos.

Veamos ahora por qué empezamos las enumeraciones con 35, aunque podríamos utilizar un valor menor, pero no cualquier valor.

Si nos fijamos en la macro que genera el nombre de los objetos, nos daremos cuenta de una cosa:

#define macro_ObjectName(A, B) (def_PrefixName + (string)Terminal.GetSubWin() + CharToString(A) + "#" + B)

El primer argumento de la macro será la enumeración que definimos en la clase. Esta enumeración se transformará en una string correspondiente, es decir, de valor, tendremos un carácter y éste se utilizará para hacer del nombre un dato único. Ahora, si miramos la tabla ASCII, veremos que el primer valor válido es 32, que es el valor del carácter espacio. Por lo tanto, podemos inicializar la enumeración con el valor 32, pero inicializarla con un valor inferior no es apropiado.

Pero este hecho sólo ocurre por la razón de que estamos convirtiendo un valor en un carácter, si el valor se convirtiera en su string correspondiente, podríamos inicializar la enumeración como 0, ya que entonces, la conversión no traería ningún problema, pero como estamos convirtiendo cosas, el valor mínimo apropiado es 32.

Veamos ahora la rutina responsable de añadir los objetos a utilizar.

void AddSymbolInfo(const string szArg, const bool bRestore = false)
        {
                C_Object_Edit edit;
                C_Object_BtnBitMap bmp;
                string sz0;
                const int x = 9999;

                bmp.Create(sz0 = macro_ObjectName(en_Icon, szArg), szArg);
                bmp.PositionAxleX(sz0, x);
                bmp.PositionAxleY(sz0, 15);
                edit.Create(sz0 = macro_ObjectName(en_Symbol, szArg), m_Infos.CorSymbol, m_Infos.CorBackGround, szArg);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 10);
                edit.Size(sz0, 56, 16);
                edit.Create(sz0 = macro_ObjectName(en_Percentual, szArg), m_Infos.CorSymbol, m_Infos.CorBackGround, 0.0, "Lucida Console", 8);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 10);
                edit.Size(sz0, 50, 11);
                edit.Create(sz0 = macro_ObjectName(en_Arrow, szArg), m_Infos.CorSymbol, m_Infos.CorBackGround, "", "Wingdings 3", 10);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 26);
                edit.Size(sz0, 20, 16);
                edit.Create(sz0 = macro_ObjectName(en_Price, szArg), 0, m_Infos.CorBackGround, 0.0);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 26);
                edit.Size(sz0, 60, 16);
                if (!bRestore)
                {
                        ArrayResize(m_Infos.Symbols, m_Infos.nSymbols + 1, 10);
                        m_Infos.Symbols[m_Infos.nSymbols].szCode = szArg;
                        m_Infos.nSymbols++;
                }
        }

Aquí podemos darnos cuenta de algo que para muchos es extraño, pero para nosotros no es más que una broma, nos gusta jugar y explorar las posibilidades de las cosas. Y nos preguntamos: ¡¿Por qué hemos hecho esto?! 

¿Por qué declaramos una variable constante y luego la aplicamos a las llamadas? La razón es que nos gusta jugar y explorar las posibilidades de las cosas. Es una forma divertida de utilizar el lenguaje y ver lo que nos permite hacer.

De hecho, no sería necesario declarar dicha constante, se podría utilizar una definición de compilación (#define), o simplemente poner el valor de la constante en cada una de las llamadas sería lo mismo. Pero independientemente de eso, observe el resto de la función, y verá que estamos añadiendo los elementos de forma que sus nombres sean siempre únicos, es decir, no habrá dos elementos con el mismo nombre, y los estamos creando en la siguiente secuencia: BitMap, código del activo, porcentaje de variación diaria, flecha indicativa de la variación y precio actual del activo.

Tenemos que tener en cuenta una cosa, y es algo importante: Los mapas de bits, que se utilizan en el activo, deben tener 32 x 32 bits, si utilizamos una dimensión diferente, tendremos que cambiar los valores, no aquí, sino en otro lugar que mostraremos poco después. Pero debemos tener en cuenta que las dimensiones de todos los demás objetos deben ajustarse aquí, luego si queremos un signo más grande o más pequeño que el disponible en el anexo, basta con ajustar los valores en estas posiciones. Recordando que cada una de estas funciones están vinculadas al objeto, que fue declarado inmediatamente antes de la llamada.

Veamos ahora cómo se presentan realmente los datos.

inline void UpdateSymbolInfo(int x, const string szArg)
        {
                C_Object_Edit edit;
                C_Object_BtnBitMap bmp;
                string sz0;
                double v0[], v1;
                                
                ArraySetAsSeries(v0, true);
                if (CopyClose(szArg, PERIOD_D1, 0, 2, v0) < 2) return;
                v1 = ((v0[0] - v0[1]) / v0[(v0[0] > v0[1] ? 0 : 1)]) * 100.0;
                bmp.PositionAxleX(sz0 = macro_ObjectName(en_Icon, szArg), x);
                x += (int) ObjectGetInteger(Terminal.Get_ID(), sz0, OBJPROP_XSIZE);
                edit.PositionAxleX(macro_ObjectName(en_Symbol, szArg), x + 2);
                edit.PositionAxleX(sz0 = macro_ObjectName(en_Arrow, szArg), x  + 2);
                edit.SetTextValue(sz0, CharToString(v1 >= 0 ? (uchar)230 : (uchar)232), (v1 >= 0 ? def_ColoPositive : def_ColorNegative));
                edit.SetTextValue(sz0 = macro_ObjectName(en_Percentual, szArg), v1 , clrNONE, "%");
                edit.PositionAxleX(sz0, x + 62);
                edit.SetTextValue(sz0 = macro_ObjectName(en_Price, szArg), v0[0] * (v1 >= 0 ? 1 : -1));
                edit.PositionAxleX(sz0, x + 24);
        }

Aquí nos aseguraremos de que la serie de valores vaya siempre del más reciente al más antiguo. Una vez que tenemos esta garantía, podemos capturar los valores de los precios de cierre, pero tenga en cuenta que estamos haciendo la lectura diariamente, ya que la variación que queremos es de hecho la del día. Un detalle, si la cantidad de datos devueltos es menor que la esperada, salimos de la función inmediatamente, porque si continuamos, acabaremos rompiendo el indicador, que está informado en la caja de herramientas de MetaTrader 5. Y no queremos romper el indicador sólo por un fallo que suele ser temporal.

Ahora atención al siguiente cálculo, él es el encargado de asegurar la correcta información del porcentaje de variación del activo, creo que no nos será un problema entender este cálculo, ya que es un cálculo muy utilizado para decir cuál es el porcentaje alcista o bajista sobre un precio.

Ahora vamos a colocar las cosas en la pantalla, y lo primero que hay que hacer es colocar la imagen. Ahora recordamos que en el momento de la creación de los objetos, se dijo que el tamaño de la imagen debe ser informado en otro lugar. Bueno, el lugar está aquí, aunque la única información que necesitamos es el ancho de la imagen, pero como he dicho, es preferible utilizar 32 x 32, ya que un cambio, especialmente para más, implicará que necesitarán ajustar otros valores.

Sin embargo, en caso de que la diferencia sea hacia abajo, no tendremos problemas, ya que el propio código se ajustará a un tamaño de imagen menor. Pero para un tamaño mayor, sobre todo en el ancho, podría significar, si no ajustamos el tamaño informado en def_MaxWidth, que al rotar la información lo haga de forma extraña. Basta entonces con aumentar el valor en la definición de la construcción. Pero cuidado, si ponemos un valor demasiado alto, tendremos que esperar mucho más hasta que la información vuelva a aparecer de lo que tardaríamos si el valor fuera el adecuado.

Todos los valores siguientes dependerán de este valor contenido en el ancho de la imagen, así que eso es lo que definirá las cosas. Pero tenemos una pequeña cosa que, para algunos, no tiene mucho sentido, ya que tenemos un valor y está siendo convertido a carácter, y el carácter no aparece en la pantalla de la manera que pensabas. ¿Qué está pasando en este momento? Aquí estamos creando las flechas, que indican si el precio bajará o subirá, utilizando la variación diaria. La cosa en sí parece confusa, pero funciona super bien, recordando que no estamos usando cualquier fuente, sino Wingdings 3. Pueden utilizar otro tipo de letra, pero tendrán que ajustar estos valores aquí para que la representación siga siendo correcta.

Del mismo modo, tenemos otro punto curioso. Debemos notar que estamos enviando valores string y double a la misma función, por lo que sería de esperar que encontráramos dos funciones diferentes, pero observando el código del objeto, veríamos una sola función, esto se debe a que está sobrecargada, pero la sobrecarga la crea el propio compilador y enlazador, para ello hemos tenido que hacer las pruebas dentro de la función, pero si nos fijamos con más atención, veríamos que tenemos un carácter muy utilizado para designar valores en porcentaje (%), ahora vamos a la función dentro del objeto Edit, y veremos dónde se añade este carácter de porcentaje, y cómo hemos hecho esto.

Y para terminar este procedimiento, tenemos un punto en el que ajustamos el precio para que su color se muestre correctamente en el tablero.

Notamos que todos los cambios ocurrieron dentro del procedimiento mismo, no tuvimos ninguna modificación en contra de cómo debe ser llamado el sistema en realidad, por lo que cerramos todas las modificaciones necesarias dentro de la clase C_Widget. Pero tenemos algunos pequeños cambios en el código del indicador, así que echemos un vistazo a esos cambios.

#property indicator_separate_window
#property indicator_plots 0
#property indicator_height 32
//+------------------------------------------------------------------+
#include <Widget\Rolling Price\C_Widget.mqh>
//+------------------------------------------------------------------+
input string user00 = "Config.cfg";  //Arquivo de configuração
input int    user01 = -1;            //Deslocamento
input int    user02 = 10;            //Pausa em milissegundos
input
 color  user03 = clrWhiteSmoke; //Cor do Ativo
input color  user04 = clrBlack;      //Cor de Fundo
//+------------------------------------------------------------------+
C_Widget Widget;
//+------------------------------------------------------------------+
int OnInit()
{
        if (!Widget.Initilize(user00, "Widget Price", user03, user04))
                return INIT_FAILED;
        EventSetMillisecondTimer(user02);
        
        return INIT_SUCCEEDED;
}

El único punto que realmente merece ser mencionado en el código del indicador es este donde establecemos un valor para la altura del indicador, no existiendo ningún otro punto diferente en relación al código de la versión básica.

Con esto podemos decir que el indicador esta completo, pero ¿que tal si añadimos alguna funcionalidad extra, y que sea de alguna manera útil, para ser utilizada durante el periodo de trading? Pues este será el tema del próximo artículo.


Funciones adicionales

¿Qué tal si ponemos en el tablero los activos que nos gusta comerciar? Y no sólo eso, vamos a dar un paso más allá de este punto. Ahora sí.

¿Qué tal si cuando hacemos clic en un activo en el tablero, el gráfico de este activo se abre de inmediato, por lo que podemos seguir las cosas, y comenzar una operación comercial tan pronto como nos demos cuenta de que el activo está entrando en una posible tendencia, ya sea bajista o alcista. ¡Y esto de una forma súper sencilla y fácil, sin tener que teclear absolutamente nada?!

Nos gusta la idea, ¿verdad? Bueno, deben estar pensando que esto es algo ultra super mega difícil de hacer. Que hay que ser un programador formado, con experiencia y conocimientos en FÍSICA NUCLEAR, o alguien con conocimientos sobre tecnología alienígena. Pero no. Es algo super simple y sencillo de hacer, utilizando MQL5 junto con la plataforma MetaTrader 5.

Para ello, sólo tenemos que añadir un pequeño fragmento de código al sistema de gestión de mensajes, así que veamos qué hay que añadir para que funcione.

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
        {
                static int tx = 0;
                string szRet[];
                                                        
                switch (id)
                {

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

                        case CHARTEVENT_OBJECT_CLICK:
                                if (StringSplit(sparam, '#', szRet) == 2)
                                {
                                        SymbolSelect(szRet[1], true);
                                        szRet[0] = ChartSymbol(Terminal.Get_ID());
                                        if (ChartSetSymbolPeriod(Terminal.Get_ID(), szRet[1], PERIOD_CURRENT)) SymbolSelect(szRet[0], false);
                                        else SymbolSelect(szRet[1], false);
                                }
                                break;
                }
        }

Cuando creamos los objetos, no los creamos de cualquier manera, los creamos siguiendo un formato muy específico, y gracias a eso, podemos analizar las cosas de una manera para saber en qué objeto se hizo clic y, en consecuencia, a qué activo estaba vinculado. Y aquí es exactamente donde reside nuestro verdadero interés, saber a qué activo estaba vinculado el objeto. Para saberlo, utilizamos una función muy interesante, por cierto. 

StringSplit puede separar las cosas dependiendo de cómo se haya dado formato a los datos, así que si todo es como se espera, podemos tener como resultado la indicación de a qué activo estaba realmente vinculado el objeto pulsado, y debido a ello, podemos decirle a la plataforma MetaTrader 5 que cargue el gráfico del activo por nosotros.

Pero para que esto funcione, el activo necesita estar en observación del mercado, así que ejecutamos esta línea, para que aparezca en la ventana de observación del mercado. Pero antes de eliminar el activo actual de la ventana, capturamos su código, e intentamos que el activo pulsado aparezca en el gráfico, si lo conseguimos, intentamos eliminar el activo, que estaba en el gráfico, de la ventana de observación del mercado, pero puede ser que esto no ocurra de hecho, pero si se produce un fallo, en el intento de cambiar los activos, el activo que se colocaría en el gráfico, sería eliminado de la ventana observación del mercado.

Conviene recordar algunos detalles sobre este cambio de activos a través del tablero. Lo primero que hay que observar, de hecho, es que el activo a abrir se abrirá en el mismo marco temporal del activo anterior, podemos cambiar el marco temporal más adelante, pero en principio, se abrirá en el mismo marco temporal. Otra cosa igual de importante es que debemos seleccionar los activos para que el tablero no tenga una gran cantidad de activos, porque tardarán mucho tiempo en volver a verse, y en cada cambio de marco, o cambio de activos, el tablero empezará siempre por el primer activo de la lista.

En el siguiente vídeo puede ver cómo funciona el sistema en la práctica.