English Русский 中文 Deutsch 日本語 Português
preview
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 19): Ajustes necesarios

Desarrollo de un sistema de repetición — Simulación de mercado (Parte 19): Ajustes necesarios

MetaTrader 5Probador | 30 octubre 2023, 17:07
355 0
Daniel Jose
Daniel Jose

Introducción

En los artículos anteriores de esta misma serie, creo que quedó claro que necesitamos implementar algunas cosas adicionales. Estas son absolutamente necesarias para ayudarnos a organizar mejor las cosas, especialmente pensando en algunas mejoras que aún deben realizarse. Si planeas utilizar el sistema de repetición/simulación solo para trabajar con un activo, muchas de las cosas que vamos a implementar no necesariamente deben llevarse a cabo. Puedes dejarlas a un lado,  o mejor dicho, no es necesario que aparezcan necesariamente en el archivo de configuración.

Sin embargo, es muy probable que no solo utilices un activo, sino varios activos diferentes o incluso una base de datos bastante amplia. En este caso, es necesario organizar un poco las cosas, y por lo tanto, será necesario implementar un código adicional para lograrlo. Aunque en algunos casos muy específicos, podríamos simplemente utilizar lo que ya está disponible, es decir, ya ha sido implementado y se encuentra en el código fuente, pero está en forma implícita. Solo necesita ser sacado de la oscuridad y llevado a la luz.

Siempre me gusta mantener las cosas lo más organizadas posible, y creo que muchos también piensan y tratan de hacer lo mismo. Será bueno y muy adecuado saber y entender cómo implementar esta funcionalidad. Además, aprenderás cómo agregar nuevos parámetros al sistema en caso de que necesites un parámetro específico para un activo en particular que desees utilizar para estudios o análisis.

Lo que vamos a hacer aquí es preparar el terreno para que, cuando sea necesario agregar nuevas funciones al código, esto se haga de manera fluida y sencilla. El código actual aún no puede cubrir o manejar algunas cosas que serán necesarias para un progreso significativo. Necesitamos que todo se construya de manera que el esfuerzo de implementar algunas cosas sea lo más mínimo posible. Si esto se hace correctamente, tendremos la posibilidad de contar con un sistema verdaderamente versátil, capaz de adaptarse muy fácilmente a cualquier situación que deba abordarse. Uno de estos aspectos será el tema del próximo artículo. Afortunadamente, gracias a los dos últimos artículos en los que mostré cómo agregar los ticks a la ventana de observación del mercado, todo está bastante encaminado. Si por casualidad no has visto estos artículos, puedes acceder a ellos a través de los siguientes enlaces:  Desarrollo de un sistema de repetición — Simulación de mercado (Parte 17): Ticks y más ticks (I) y Desarrollo de un sistema de repetición — Simulación de mercado (Parte 18): Ticks y más ticks (II). Estos dos artículos contienen información valiosa para lo que haremos en los próximos artículos.

Sin embargo, aún faltan algunos detalles muy específicos, y estos se implementarán en este artículo. Además, hay otros detalles bastante complejos que requerirán otros artículos solo para explicar cómo trabajaremos en ellos y resolver las pendencias adecuadamente. Así que empecemos la implementación del sistema que se verá en este artículo. Comenzaremos con la parte en la que mejoraremos la organización de los datos utilizados.


Implementando un sistema de directorios.

La cuestión aquí no es si realmente necesitamos o no implementar este sistema, sino por qué implementarlo. En el estado actual de desarrollo, puedes usar un sistema de directorios. Sin embargo, tendrás mucho más trabajo al implementar las cosas de la forma en que el servicio de repetición/simulación está en su estado actual. No es que sea imposible, pero el trabajo es considerablemente mayor que simplemente agregar una nueva variable en el archivo de configuración. Para que entiendas realmente de qué estoy hablando, mira las siguientes figuras:

Figura 01

Figura 01 - Forma de acceder a directorios en el sistema actual.


Figura 02

Figura 02 - Forma alternativa de acceder a directorios.


A pesar de que la Figura 01 tiene el mismo comportamiento que la Figura 02 desde el punto de vista del sistema de repetición/simulación, pronto notarás que es mucho más práctico configurar las cosas utilizando un archivo de configuración basado en la Figura 02. Esto se debe a que solo necesitas informar una vez el directorio donde estarán los datos y el sistema de repetición/simulación se encargará del resto. El hecho de poder utilizar la Figura 02 evita que, en caso de que estemos utilizando una base de datos muy extensa para cubrir el uso de un promedio móvil, por ejemplo, olvidemos o cometamos algún error al indicar dónde deben buscarse los datos. Si cometes este error de escritura, pueden ocurrir dos situaciones:

  • La primera es que el sistema simplemente emita una advertencia, informando que los datos no podrán ser accedidos. 
  • Y la otra situación, y esta es más grave, es que utilices datos incorrectos.

Pero al tener la posibilidad de ajustar el directorio en un solo lugar, este tipo de error se vuelve más difícil de ocurrir. No es que esto no vaya a suceder en absoluto, pero será más difícil. Recuerda que puedes organizar las cosas en directorios aún más específicos, combinando así la Figura 01 y la Figura 02. Sin embargo, aquí mantendré las cosas en un nivel más básico. Siéntete libre de implementar todo de una manera que cubra los datos de la manera que te sea más adecuada y se adapte a tu estilo de organización.

Muy bien, mensaje entregado, es hora de ver cómo hacerlo en la práctica. La forma de hacerlo es relativamente simple y fácil de entender, al menos en comparación con lo que aún tendremos que hacer. En primer lugar, vamos a crear una nueva variable privada de la clase. Esta se muestra en el fragmento a continuación:

private :
    enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
    string m_szPath;

Al agregar esta variable en esta posición, será visible para todos los procedimientos internos de la clase. Sin embargo, no se podrá acceder a ella desde fuera de la clase. Esto garantiza que no se modifique indebidamente (al menos en principio). Esto se debe a que en algún procedimiento interno de la clase, puedes modificar el valor de la variable sin darte cuenta. Al hacerlo, puedes tener dificultades para entender por qué el código no funciona como se esperaba.

Una vez hecho esto, debemos decirle a nuestra clase que comience a reconocer un nuevo comando en el archivo de configuración. Este procedimiento se realiza en un punto muy específico, pero puede variar según lo que estés agregando. En nuestro caso, se hará en el procedimiento que se muestra a continuación:

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "POINTSPERTICK",
                                "PATH"
                                };
        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_PointsPerTick = StringToDouble(szRet[1]);
                    return true;
                case 1:
                    m_szPath = szRet[1];
                    return true;                                    
            }
            Print("Variável >>", szRet[0], "<< não definida.");
        }else
            Print("Definição de configuração >>", szInfo, "<< invalida.");
                                
        return false;
    }

Observa lo mucho más simple que es hacerlo cuando todo el código está estructurado para recibir mejoras. Aun así, debes tener cuidado. Si tomas precauciones, no tendrás problemas para agregar cualquier cosa que necesites hacer en el código.

Lo primero que hacemos es agregar el nombre o etiqueta del comando que se utilizará en el archivo de configuración dentro de la matriz de datos secuenciales. Pero ten en cuenta que esto debe estar todo en mayúsculas. Podríamos hacer que las cosas sean sensibles a mayúsculas y minúsculas, pero eso complicaría las cosas para el usuario al escribir y colocar el comando en el archivo de configuración. Si eres el único que utiliza el sistema y deseas utilizar la misma etiqueta pero con significados diferentes, tal vez sea una buena idea usar el sistema que distingue entre mayúsculas y minúsculas. De lo contrario, esta idea solo puede complicar innecesariamente todo el trabajo. Personalmente, creo que usar la misma etiqueta para significados diferentes solo sirve para complicar nuestras vidas. Por eso no lo hago.

Una vez que la etiqueta haya sido agregada a la matriz de comandos, debemos implementar su funcionalidad. Esto se hace exactamente en este punto. Así de simple. Dado que es el segundo de la cadena y la cadena comienza en cero, usamos el valor 1 para indicar que estamos implementando esa funcionalidad específica. Dado que la idea es tener solo el nombre del directorio, el comando es bastante simple. Para finalizar, devolveremos el valor verdadero al autor de llamada, indicando así que el comando ha sido reconocido e implementado con éxito.

La secuencia para hacer cualquier adición en el sistema es precisamente la que se ha mostrado. Una vez que esto se haya hecho, podemos utilizar los datos que se han proporcionado en el archivo de configuración. Sin embargo, hay un punto que olvidé mencionar, algo tan simple pero en lo que siempre debes estar atento. Puede dar la impresión en algunos casos de que un nuevo recurso añadido está generando problemas, cuando en realidad podría ser simplemente porque no se ha inicializado correctamente. En este caso, cada vez que agregues una variable global privada, asegúrate de que se inicialice correctamente en el constructor de la clase. Puedes ver esto en el código a continuación, donde inicializamos la nueva variable.

C_ConfigService()
	:m_szPath(NULL)
	{
	}

Al hacerlo, garantizamos que tendremos un valor conocido en una variable que aún no ha tenido ningún valor asignado. Este tipo de detalle puede parecer insignificante en algunos escenarios, pero evita grandes problemas y pérdida de tiempo en otros, además de ser considerado una buena práctica de programación. Una vez que se haya realizado este trabajo y la variable se haya inicializado en el constructor de la clase, y ya hayamos establecido una forma de asignarle un valor según lo que se informa en el archivo de configuración, llega el momento de usar ese valor. Y este valor se utilizará en un solo procedimiento, que es precisamente el encargado de gestionar la carga de la base de datos.

Entonces, veamos cómo se implementó esto:

bool SetSymbolReplay(const string szFileConfig)
    {
        #define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)
        int        file,
                iLine;
        char    cError,
                cStage;
        string  szInfo;
        bool    bBarsPrev;
        C_FileBars *pFileBars;
        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
            Print("Falha na abertura do arquivo de configuração [", szFileConfig, "]. Serviço sendo encerrado...");
            return false;
        }
        Print("Carregando dados para replay. Aguarde....");
        ArrayResize(m_Ticks.Rate, def_BarsDiary);
        m_Ticks.nRate = -1;
        m_Ticks.Rate[0].time = 0;
        iLine = 1;
        cError = cStage = 0;
        bBarsPrev = false;
        while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
        {
            switch (GetDefinition(FileReadString(file), szInfo))
            {
                case Transcription_DEFINE:
                    cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
                    break;
                case Transcription_INFO:
                    if (szInfo != "") switch (cStage)
                    {
                        case 0:
                            cError = 2;
                            break;
                        case 1:
                            pFileBars = new C_FileBars(macroFileName);
                            if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true;
                            delete pFileBars;
                            break;
                        case 2:
                            if (LoadTicks(macroFileName) == 0) cError = 4;
                            break;
                        case 3:
                            if ((m_dtPrevLoading = LoadTicks(macroFileName, false)) == 0) cError = 5; else bBarsPrev = true;
                            break;
                        case 4:
                            if (!BarsToTicks(macroFileName)) cError = 6;
                            break;
                        case 5:
                            if (!Configs(szInfo)) cError = 7;
                            break;
                    }
                break;
            };
            iLine += (cError > 0 ? 0 : 1);
        }
        FileClose(file);
        switch(cError)
        {
            case 0:
                if (m_Ticks.nTicks <= 0)
                {
                    Print("Não existem ticks para serem usados. Serviço esta sendo encerrado...");
                    cError = -1;
                }else if (!bBarsPrev) FirstBarNULL();
                break;
            case 1  : Print("O comando na linha ", iLine, " não é reconhecido pelo sistema...");    break;
            case 2  : Print("O sistema não esperava o conteudo da linha ", iLine);                  break;
            default : Print("Existe um erro na linha ", iLine);
        }
                                                        
        return (cError == 0 ? !_StopFlag : false);
#undef macroFileName
    }

Dado que estaremos utilizando esto de una manera única y, al mismo tiempo, en más de un lugar diferente, preferí utilizar una macro para ayudar en la codificación. Todos los puntos en amarillo recibirán exactamente el código contenido en la macro. Esto facilita mucho las cosas, ya que no es necesario escribir lo mismo varias veces y correr el riesgo de generar algún tipo de error en caso de mantenimiento o modificación de un código que se utiliza en varios lugares diferentes. Ahora echemos un vistazo más de cerca a lo que hace la macro.

#define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)

¿Recuerdas que inicializamos la variable con un valor específico? Bueno, en el momento en que intentemos utilizar la variable, probaremos exactamente eso, cuál es el valor que contiene. Si es el mismo con el que inicializamos en el constructor, tendremos un cierto camino. Si es el que se encuentra en el archivo de configuración, tendremos otro camino. Pero de una forma u otra, al final tendremos el nombre con el que podremos acceder al archivo.

Este sistema es tan versátil que puedes cambiar el directorio en cualquier momento sin necesidad de tocar nada más dentro del sistema, que ya está listo y compilado. Por lo tanto, no necesitarás volver a compilar todo el código solo para ajustar algo que se puede hacer modificando el archivo de configuración. Lo único que debes hacer para cambiar el directorio en el que deseas trabajar es usar el siguiente fragmento dentro del archivo de configuración:

[Config]
Path = < NEW PATH >

Donde <NEW PATH> contendrá la nueva dirección que se utilizará a partir de ese punto en el archivo de configuración. Es realmente muy agradable, ya que el trabajo se reducirá enormemente al trabajar con bases de datos que pueden contener una estructura de directorios. Recuerda que debes organizar y catalogar los datos de manera que sea más fácil encontrar el archivo que buscas.

Una vez hecho esto, podemos pasar al siguiente paso, donde ajustaremos algunos detalles que necesitan ser implementados. Esto se aborda en el próximo tema.


Ajustando los datos del activo personalizado

Para implementar nuestro sistema de órdenes, inicialmente necesitaremos tres valores básicos: Volumen Mínimo, Valor del tick y Tamaño del tick. Sin embargo, de esta información, solo uno está actualmente implementado. Y su implementación no está exactamente como debería ser, ya que puede suceder que el valor no haya sido configurado en el archivo de configuración. Esto dificulta mucho tu trabajo para crear un activo sintético, destinado únicamente a simular movimientos probables en el mercado.

Si no realizas esta configuración, el sistema podría tener datos inconsistentes cuando comencemos a trabajar en el sistema de órdenes. Debemos asegurarnos de que estos datos estén debidamente configurados. Esto evitará que tengamos problemas tratando de implementar algo en un sistema ya densamente codificado. Así que comenzaremos corrigiendo lo que está mal configurado para que no tengamos problemas en la próxima etapa de nuestro trabajo. Si llegamos a tener problemas, que sean de otra naturaleza. El sistema de órdenes no se comunicará realmente con el servicio que se utiliza para generar la repetición/simulación del mercado. La única información con la que entrará en contacto es el gráfico y el nombre del activo, nada más. Al menos, esa es mi intención en este momento. No sé si realmente será posible hacerlo.

Pensando de esta manera, lo primero que necesitamos hacer es inicializar los 3 valores que necesitamos obligatoriamente. Sin embargo, estos estarán configurados con datos en cero. Pero vayamos con calma. Primero corrijamos el problema que estamos teniendo. Para hacerlo, tendremos el siguiente código:

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Aquí, establecemos un valor inicial de cero, pero como bonus, también proporcionamos una descripción para nuestro símbolo personalizado. Esto no es obligatorio, pero puede ser interesante en caso de que abras la ventana que contiene la lista de activos y veas uno con un nombre peculiar. Bueno, como habrás notado y podrías estar pensando, ya no utilizaremos esa variable que existía anteriormente. Aquella que se declaraba en un lugar específico, como se muestra en el código a continuación:

class C_FileTicks
{
    protected:
        struct st00
        {
            MqlTick  Info[];
            MqlRates Rate[];
            int      nTicks,
                     nRate;
            bool     bTickReal;
        }m_Ticks;
        double       m_PointsPerTick;
    private :
        int          m_File;

Todos los puntos en los que aparecía esta variable ahora deben hacer referencia al valor contenido y definido en el activo. Entonces, ahora tenemos un nuevo código. Pero básicamente, este valor se referenciaba en realidad en dos lugares en todo el sistema de repetición/simulación. El primero se ve a continuación:

inline long RandomWalk(long pIn, long pOut, const MqlRates &rate, MqlTick &tick[], int iMode)
    {
        double vStep, vNext, price, vHigh, vLow, PpT;
        char i0 = 0;

        PpT = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
        vNext = vStep = (pOut - pIn) / ((rate.high - rate.low) / PpT);
        vHigh = rate.high;
        vLow = rate.low;
        for (long c0 = pIn, c1 = 0, c2 = 0; c0 < pOut; c0++, c1++)
        {
            price = tick[c0 - 1].last + (PpT * ((rand() & 1) == 1 ? -1 : 1));
            price = tick[c0].last = (price > vHigh ? price - PpT : (price < vLow ? price + PpT : price));
            switch (iMode)
            {
                case 0:
                    if (price == rate.close)
                        return c0;
                        break;
                case 1:
                    i0 |= (price == rate.high ? 0x01 : 0);
                        i0 |= (price == rate.low ? 0x02 : 0);
                        vHigh = (i0 == 3 ? rate.high : vHigh);
                        vLow = (i0 ==3 ? rate.low : vLow);
                        break;
                case 2:
                    break;
            }
            if ((int)floor(vNext) < c1)
            {
                if ((++c2) <= 3) continue;
                vNext += vStep;
                if (iMode == 2)
                {
                    if ((c2 & 1) == 1)
                    {
                        if (rate.close > vLow) vLow += PpT; else vHigh -= PpT;
                    }else
                    {
                        if (rate.close < vHigh) vHigh -= PpT; else vLow += PpT;
                    }
                } else
                {
                    if (rate.close > vLow) vLow = (i0 == 3 ? vLow : vLow + PpT); else vHigh = (i0 == 3 ? vHigh : vHigh - PpT);
                }
            }
        }

        return pOut;
    }

Dado que no quiero repetir el mismo código en varios puntos, usamos una variable local para ayudarnos. Pero el principio es el mismo: hacer referencia a un valor que está definido dentro del activo. Ahora, el segundo punto donde se hace referencia a este valor es precisamente en la clase C_Replay. Sin embargo, por razones prácticas, haremos algo un poco diferente de lo que se vio anteriormente. A diferencia del momento en que creamos un random walk. La presentación y el uso de la información en el gráfico tienden a degradar el rendimiento debido a un exceso de llamadas innecesarias. Esto se debe a que en el momento en que se está creando el random walk, cada barra generará 3 llamadas a él.

Pero una vez que haya sido creado, puede haber miles de ticks presentes, todo esto habiendo sido creado con solo 3 llamadas. En el momento de la presentación y construcción del gráfico, esto tiende a degradar ligeramente el rendimiento. Pero vamos a entender cómo esto realmente se manifestaría en la práctica. Cuando estamos utilizando un archivo de ticks reales, es decir, haciendo una repetición, esta degradación no ocurriría. Esto se debe a que al utilizar datos reales, no se requeriría información adicional para que el sistema construya las barras de 1 minuto y transmita la información al gráfico de ticks en la ventana de observación de mercado. Esto se vio en los dos artículos anteriores.

Pero cuando vamos a utilizar barras de 1 minuto para generar los ticks, es decir, hacer una simulación, necesitaremos conocer el tamaño del tick. Para que esta información pueda ayudar al servicio a crear un modelo de movimiento adecuado. Este movimiento se verá en la ventana de observación del mercado. Pero la creación de las barras no requiere de esta información, ya que la conversión se realizó en la clase C_FileTicks.

Sabiendo este detalle, debemos considerar la función que crea dicho gráfico. Y así, verificar cuántos accesos sufrirá durante la ejecución. En el caso de estar haciendo la simulación, esta función se muestra a continuación:

inline void CreateBarInReplay(const bool bViewMetrics, const bool bViewTicks)
        {
#define def_Rate m_MountBar.Rate[0]
        
                bool bNew;
        MqlTick tick[1];
        static double PointsPerTick = 0.0;
        
        if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
        {
                        PointsPerTick = (PointsPerTick == 0.0 ? SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) : PointsPerTick);                    
                        if (bViewMetrics) Metrics();
                        m_MountBar.memDT = (datetime) macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                        def_Rate.real_volume = 0;
                        def_Rate.tick_volume = 0;
        }
        bNew = (def_Rate.tick_volume == 0);
        def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
        def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
        def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
        def_Rate.low = (bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
        def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
        def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
        def_Rate.time = m_MountBar.memDT;
        CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
        if (bViewTicks)
        {
                        tick = m_Ticks.Info[m_ReplayCount];
                        if (!m_Ticks.bTickReal)
                        {
                                static double BID, ASK;
                                double  dSpread;
                                int     iRand = rand();
                        
                                dSpread = PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? PointsPerTick : 0 ) : 0 );
                                if (tick[0].last > ASK)
                                {
                                        ASK = tick[0].ask = tick[0].last;
                                        BID = tick[0].bid = tick[0].last - dSpread;
                                }
                                if (tick[0].last < BID)
                                {
                                        ASK = tick[0].ask = tick[0].last + dSpread;
                                        BID = tick[0].bid = tick[0].last;
                                }
                        }
                        CustomTicksAdd(def_SymbolReplay, tick); 
        }
                m_ReplayCount++;

#undef def_Rate
        }

Aquí declaramos una variable local estática. Esta se utilizará para evitar un exceso de llamadas a la función que captura el tamaño de los ticks. Esta captura ocurrirá solo una vez durante todo el tiempo de vida y ejecución del servicio, pero esta variable solo se utilizará en estos lugares. Por lo tanto, de alguna manera, no tiene sentido extenderla fuera de esta función. Pero observe que este lugar donde la variable realmente se utiliza solo se accederá si estamos utilizando el modo de simulación. En el modo de repetición, esta variable no tiene ninguna utilidad práctica.

Con esto, resolvemos el problema del tamaño del tick. Aún quedan dos problemas más por resolver. Pero el problema del tick aún no se ha resuelto por completo. Queda un problema pendiente: la inicialización. Pero resolveremos esto al mismo tiempo que resolvemos los otros dos problemas, ya que el enfoque será el mismo.


Últimos detalles por crear

La cuestión ahora es realmente saber qué debemos ajustar de hecho. Es cierto que puedes ajustar varias cosas en un activo personalizado. Pero para nuestro propósito, muchas de estas cosas no son en absoluto necesarias. Necesitamos centrarnos solo en lo que realmente necesitamos. Y ajustarlo para que cuando necesitemos esta información, la tengamos de manera simple y lo más versátil posible. Lo digo porque la intención real es comenzar a crear un sistema de órdenes pronto. No estoy seguro de si esto realmente ocurrirá tan rápido. Pero de todos modos, quiero que el Asesor Experto que se creará sea compatible con la repetición/simulador y que también se pueda utilizar en el mercado real. Ya sea en una cuenta demo o en una cuenta real. Para esto, necesitamos que las cosas tengan el mismo nivel de información necesaria que se encontraría en el mercado real.

Dicho esto, necesitamos inicializar las cosas con valores en cero. Esto asegurará que el activo personalizado que se utilizará tenga estos valores compatibles con los que realmente se encontrarían en el activo real. Además, inicializar los valores en cero significa que podremos probarlos más tarde. Esto facilita mucho el trabajo de implementación para verificar posibles fallos en la configuración del activo.

C_Replay(const string szFileConfig)
    {
        m_ReplayCount = 0;
        m_dtPrevLoading = 0;
        m_Ticks.nTicks = 0;
        Print("************** Serviço Market Replay **************");
        srand(GetTickCount());
        GlobalVariableDel(def_GlobalVariableReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Aquí comenzamos los valores que realmente necesitaremos en el futuro, de manera más inmediata. Estos valores son el tamaño del tick, el valor del tick y el volumen (en este caso, el paso). Pero dado que el paso, muchas veces, para no decir siempre, corresponde al volumen mínimo que se debe utilizar, no veo ningún problema en configurarlo en lugar del volumen mínimo. También porque el paso es más importante para nosotros en la próxima etapa. Pero también hay una razón por la cual intenté ajustar el volumen mínimo y, por alguna razón, no tuve éxito al hacerlo. MetaTrader 5 simplemente ignoraba el hecho de que debía ajustar el volumen mínimo.

Hecho esto, necesitamos hacer otra cosa. Comprobar si de hecho estos valores han sido inicializados. Esto se hará 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("Configuração do ativo não esta completa, falta declarar o tamanho do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o valor do ticket.");
   if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
        macroError("Configuração do ativo não esta completa, falta declarar o volume mínimo.");
   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("Aguardando permissão do indicador [Market Replay] para iniciar replay ...");
   info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
   ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
   ChartRedraw(m_IdReplay);
   GlobalVariableDel(def_GlobalVariableIdGraphics);
   GlobalVariableTemp(def_GlobalVariableIdGraphics);
   GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
   while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
   
   return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
  }

Para evitar repetir las mismas cosas, utilizaremos una macro para ayudarnos. Mostrará un mensaje de error y finalizará informando que el sistema ha fallado. Lo que hacemos aquí es probar uno por uno los valores que deben declararse e inicializarse obligatoriamente en el archivo de configuración. Si alguno de estos no se ha inicializado, se informará al usuario para que realice la configuración correcta del activo personalizado. Sin esto, el servicio de repetición/simulación no continuará. A partir de este momento, se considerará funcional y capaz de proporcionar al sistema de órdenes, en este caso el EA que se construirá, los datos necesarios para que la simulación o repetición del mercado ocurra de manera adecuada. Esto te permitirá simular el envío de órdenes.

Bueno, pero para inicializar estos valores, necesitamos realizar algunas adiciones al sistema. Estas se muestran a continuación:

inline bool Configs(const string szInfo)
    {
        const string szList[] = {
                                "PATH",
                                "POINTSPERTICK",
                                "VALUEPERPOINTS",
                                "VOLUMEMINIMAL"
                                };
        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;                                    
            }
            Print("Variável >>", szRet[0], "<< não definida.");
        }else
            Print("Definição de configuração >>", szInfo, "<< invalida.");

        return false;
    }

Observa que agregar cosas al sistema es bastante simple. Aquí hemos agregado dos nuevos valores que se pueden configurar simplemente editando el archivo que configura la repetición o simulación de cualquier activo. Estos son: el valor del tick, que generará una llamada a una función que espera un valor adecuado, y el valor del tamaño del paso, que también llamará a una función interna que ajusta este valor. Cualquier adición que desees hacer debe seguir estos pasos.


Consideraciones finales

Por ahora, aún no estoy realizando pruebas para verificar si los valores son adecuados o no. Por lo tanto, ten cuidado al editar el archivo de configuración para evitar errores durante el uso del sistema de órdenes.

De una forma u otra, verifica cómo están las cosas en los activos personalizados adjuntos.

Un detalle importante: A pesar de haber dicho que el sistema está prácticamente funcional, esto no es del todo cierto. Dado que actualmente no es posible hacer ni repetir ni simular utilizando datos de FOREX. La razón es que el mercado de divisas utiliza algunas cosas con las que el sistema aún no puede trabajar realmente. Si intentas hacer esto, se generarán errores de rango en las matrices del sistema, ya sea en la repetición o en la simulación. Pero estoy trabajando en las correcciones para que pueda trabajar con datos del mercado de divisas.

En el próximo artículo, comenzaremos a abordar este tema: FOREX.


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

Archivos adjuntos |
Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD Desarrollando un cliente MQTT para MetaTrader 5: metodología de TDD
El presente artículo representa el primer intento de desarrollar un cliente MQTT nativo para MQL5. El MQTT es un protocolo de comunicación "publicación-suscripción". Es ligero, abierto, simple y está diseñado para implementarse con facilidad, lo cual permite su uso en muchas situaciones.
Desarrollo de un factor de calidad para los EAs Desarrollo de un factor de calidad para los EAs
En este artículo, te explicaremos cómo desarrollar un factor de calidad que tu Asesor Experto (EA) pueda mostrar en el simulador de estrategias. Te presentaremos dos formas de cálculo muy conocidas (Van Tharp y Sunny Harris).
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 20): FOREX (I) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 20): FOREX (I)
La intención inicial de este artículo no será cubrir todas las características de FOREX, sino más bien adaptar el sistema de manera que puedas realizar al menos una repetición del mercado. La simulación quedará para otro momento. Sin embargo, en caso de que no tengas los ticks y solo tengas las barras, con un poco de trabajo, puedes simular posibles transacciones que podrían haber ocurrido en FOREX. Esto será hasta que te muestre cómo adaptar el simulador. El hecho de intentar trabajar con datos provenientes de FOREX dentro del sistema sin modificarlo conlleva errores de rango.
Teoría de categorías en MQL5 (Parte 12): Orden Teoría de categorías en MQL5 (Parte 12): Orden
El artículo forma parte de una serie sobre la implementación de grafos utilizando la teoría de categorías en MQL5 y está dedicado a la relación de orden (Order Theory). Hoy analizaremos dos tipos básicos de orden y exploraremos cómo los conceptos de relación de orden pueden respaldar conjuntos monoides en las decisiones comerciales.