English Русский 中文 Deutsch 日本語 Português
preview
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)

MetaTrader 5Probador | 1 noviembre 2023, 09:31
466 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, "Desarrollo de un sistema de repetición — Simulación de mercado (Parte 19): Ajustes necesarios" implementamos algunas cosas cuya necesidad se volvía más urgente. Sin embargo, a pesar de que el enfoque desde el inicio de esta serie ha sido principalmente el mercado de valores, no quiero dejar de intentar cubrir también el mercado de divisas (FOREX). El motivo de mi falta de interés inicial en FOREX se debe al hecho de que las operaciones en él ocurren de manera continua. No tendría mucho sentido tener una repetición/simulador para pruebas o aprendizaje.

Podrías simplemente utilizar la cuenta demo para hacerlo. Sin embargo, existen cuestiones propias de este mercado que no se replican en el mercado de valores. Por esta razón, se vuelve interesante mostrar cómo hacer los ajustes necesarios en el sistema para que puedas utilizar este conocimiento. Y quién sabe, adaptar el sistema a otros tipos de mercados, como por ejemplo, el mercado de criptoactivos, en caso de que tengas acceso a los datos de este mercado específico.

De esta manera, quedará claro cuán versátil y adecuada puede ser la plataforma MetaTrader 5 para muchas más cosas de las que originalmente los creadores propusieron mantener y desarrollar. Aquí, tu imaginación y tu conocimiento sobre un mercado específico serán los únicos limitantes para lo que puedes lograr.


Conozcamos algunos aspectos del mercado de FOREX

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. A pesar de que se intenten evitar tales errores, siempre ocurrirán. Sin embargo, es posible superar estos errores y así lograr generar una repetición de mercado FOREX. Pero para hacerlo, tendremos que realizar varios ajustes y cambiar algunos de los conceptos que se han estado trabajando hasta ahora. Creo que valdrá la pena, ya que esto hará que el sistema sea mucho más flexible para trabajar con datos mucho más exóticos.

En el anexo de este artículo, encontrarás un activo, o más conocido como par de divisas, del mercado FOREX. Esto estará con ticks reales para que puedas visualizar las cosas. Sin duda, este no es un mercado sencillo para trabajar con simulaciones y repetición. A pesar de que verás el mismo tipo básico de información, en el mercado FOREX existen algunas peculiaridades propias. Lo que lo hace interesante de observar y analizar.

Este mercado cuenta con una serie de características propias. Pero aquí, para poder implementar el sistema de repetición, tendré que explicarte algunas de estas características para que puedas entender de qué estamos hablando y cuánto puede ser interesante conocer otros mercados.


Cómo se realiza la negociación

En el mercado de divisas, la negociación generalmente se lleva a cabo sin que realmente exista un spread entre el valor BID y ASK. En la mayoría de los casos, estos dos valores pueden ser iguales. ¿Pero cómo es eso posible? ¿Cómo pueden ser iguales? A diferencia del mercado de valores, donde siempre hay un spread entre BID y ASK, en el mercado FOREX no es así. Aunque a veces ocurre un spread, y en ocasiones es considerablemente grande. Pero por lo general, el valor de BID y ASK puede ser igual. Esto comienza a confundir a quienes provienen del mercado de valores y desean ingresar al mercado FOREX, ya que las estrategias de negociación a menudo necesitarán modificaciones significativas.

Otra cosa a tener en cuenta es que en el mercado FOREX, los principales actores son los Bancos Centrales. Quienes operan en la B3 (Bolsa de Brasil) ya han visto y saben muy bien lo que el Banco Central a veces hace con el dólar. Por esta razón, muchos evitan operar este activo debido al temor a una posible intervención del Banco Central en la moneda. Esto puede hacer que una posición que antes era ganadora pase rápidamente a ser una posición fuertemente perdedora. Muchos operadores inexpertos suelen quebrar en estos momentos, perdiendo todo su capital y, en algunos casos, incluso enfrentando demandas judiciales por parte de la bolsa y el corredor de bolsa. Esto puede ocurrir en una de las intervenciones que el banco central puede llevar a cabo, sin previo aviso y sin piedad hacia quienes tienen posiciones.

Pero para nosotros, eso no importa. Lo que nos interesa es el programa en sí. Entonces, en el mercado FOREX, la representación de los precios se basa en el valor del precio BID, como se puede ver en la figura 01.


Figura 01 

Figura 01: Representación del gráfico en el mercado FOREX

Lo que es diferente, por ejemplo, del sistema de representación de la B3, que utiliza el precio de la última operación realizada, se puede ver en la figura 02 a continuación, donde tenemos los datos del contrato de futuros del dólar en vigencia en el momento en que se escribió este artículo.

Figura 02 

Figura 02: Contrato de Mini-Dólar negociado en la Bolsa de Brasil (B3)

El sistema de repetición/simulador se desarrolló con el propósito de promover el uso de este tipo de análisis. Es decir, cuando utilizas el último precio negociado, tendremos una diferencia en la forma en que estarán dispuestos los datos en el archivo de ticks negociados. Y no solo eso, incluso podemos tener una gran diferencia en el tipo de información que realmente estará disponible en el archivo de ticks o en las barras de 1 minuto. Debido a estas variaciones, me centraré únicamente en mostrarte cómo hacer la repetición ahora. Pues la simulación implica otras cuestiones aún más complicadas. Pero como se mencionó al principio de este artículo: puedes utilizar los datos de las barras de 1 minuto para simular lo que probablemente sucedió durante las operaciones. Bueno, para no quedarnos solo en palabras, veamos la diferencia de información entre el mercado de divisas y el mercado de valores, en el caso de la B3, que es la Bolsa de Brasil, para la cual se ha diseñado el sistema de repetición/simulación. En la figura 03, tenemos información sobre uno de los pares de divisas del mercado de divisas.

Figura 03

Figura 03: Información de ticks reales del mercado de divisas

En la figura 04, tenemos el mismo tipo de información. Pero esta vez, de uno de los contratos de futuros del mini dólar que se negocia en la B3 (Bolsa de Brasil).

Figura 04

Figura 04: Información de ticks reales de la B3

Es decir, las cosas son bastante diferentes. En el mercado de divisas, no existen los valores del último precio ni el volumen negociado. En cambio, en la B3, estos valores están disponibles y muchos modelos de negociación hacen uso exactamente de estos valores: el volumen negociado y el último precio negociado. Todo lo que se ha dicho hasta ahora es simplemente para mostrar que la forma en que el sistema está diseñado no permite atender a otros tipos de mercado sin realizar algunos cambios reales. Consideré separar las cosas en términos de mercado, pero de alguna manera, esto no sería realmente práctico. No en términos de programación, ya que esa separación facilitaría mucho la programación. Sino en términos de usabilidad, ya que siempre tendrías que ajustar las cosas para uno u otro mercado. Sin embargo, podemos intentar llegar a un punto intermedio. Pero el nacimiento del sistema no ocurrirá sin algún dolor. La verdad es que intentaré minimizar al máximo esos dolores de parto, ya que no quiero ni pretendo recrear todo el sistema desde cero.


Comenzando la implementación para cubrir el FOREX

Lo primero que debemos hacer es corregir el sistema de numeración de punto flotante. ¿Pero el sistema de punto flotante ya no funciona debido a los cambios realizados en el artículo anterior, verdad? Sí, es cierto. Pero no es adecuado para el mercado de divisas (FOREX). Esto se debe a que está limitado a una precisión de 4 decimales. Necesitamos indicarle al sistema que vamos a utilizar un conjunto con más decimales. Entonces, tendremos que corregir esto para evitar otros problemas más adelante. Esta corrección se realiza en 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);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
        CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
        CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
        CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
        m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
    }

Justo aquí, le informaremos a MetaTrader 5 que queremos un número mayor de decimales en nuestro sistema de punto flotante. En este caso, utilizaremos 8 decimales, lo cual es más que suficiente para cubrir una amplia gama de condiciones. Un detalle importante: la B3 se maneja bien con 4 decimales, pero para trabajar en el FOREX, necesitamos 5 decimales. Al usar 8, dejamos el sistema libre. Sin embargo, esto no será así para siempre. Tendremos que cambiarlo más adelante debido a un detalle que no podemos explicar en este momento, pero por ahora será suficiente.

Hecho esto, vamos a empezar a facilitar nuestra vida de alguna manera. Comenzaremos considerando el siguiente escenario: las barras previas que estarán en nuestro gráfico serán barras de 1 minuto. En cuanto a los ticks que utilizaremos, serán ticks reales que se encuentran en otro archivo. De esta manera, regresamos al sistema más básico. Sin embargo, avanzaremos rápidamente hacia un sistema más amplio.


Trabajemos con los conceptos básicos

Para que no tengamos que obligar al usuario a seleccionar el tipo de mercado que se está analizando o, mejor dicho, cuál es el tipo de mercado del cual provienen los datos para poder realizar la repetición, vamos a utilizar el hecho de que en algunos casos no tendremos el valor del último precio ni del volumen negociado, mientras que en otros casos sí los tendremos. Para que el sistema pueda verificar esto por nosotros, tendremos que agregar algunas cosas al código.

En primer lugar, agreguemos lo siguiente:

class C_FileTicks
{
    protected:
        enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
        struct st00
        {
            MqlTick   Info[];
            MqlRates  Rate[];
            int       nTicks,
                      nRate;
            bool      bTickReal;
            ePlotType ModePlot;
        }m_Ticks;

//... Restante do código da classe ...

Esta enumeración nos ayudará a evitar una gran confusión en algunos puntos. Básicamente, estaremos reduciendo las cosas a dos tipos de mercados. No te preocupes, pronto entenderás la razón de esto. Y para evitar un exceso de llamadas a funciones, agregamos una nueva variable al sistema. Ahora las cosas comienzan a tomar forma. Pero necesitamos que el sistema sea capaz de reconocer cuándo estamos utilizando un modo u otro de representación. No quiero complicar la vida del usuario con este tipo de cuestiones. Para lograrlo, realizamos un pequeño cambio en el código a continuación:

inline bool ReadAllsTicks(const bool ToReplay)
        {
#define def_LIMIT (INT_MAX - 2)
#define def_Ticks m_Ticks.Info[m_Ticks.nTicks]

                string   szInfo;
                MqlRates rate;

                Print("Carregando ticks de replay. Aguarde...");
                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                m_Ticks.ModePlot = PRICE_FOREX;
                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                {
                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                        def_Ticks.time = StringToTime(StringSubstr(szInfo, 0, 19));
                        def_Ticks.time_msc = (def_Ticks.time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                        def_Ticks.bid = StringToDouble(FileReadString(m_File));
                        def_Ticks.ask = StringToDouble(FileReadString(m_File));
                        def_Ticks.last = StringToDouble(FileReadString(m_File));
                        def_Ticks.volume_real = StringToDouble(FileReadString(m_File));
                        def_Ticks.flags = (uchar)StringToInteger(FileReadString(m_File));
                        m_Ticks.ModePlot = (def_Ticks.volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
                        if (def_Ticks.volume_real > 0.0)
                        {
                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                m_Ticks.nRate += (BuiderBar1Min(rate, def_Ticks) ? 1 : 0);
                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                        }
                        m_Ticks.nTicks++;
                }
                FileClose(m_File);
                if (m_Ticks.nTicks == def_LIMIT)
                {
                        Print("Excesso de dados no arquivo de tick.\nNão é possivel continuar...");
                        return false;
                }
                return (!_StopFlag);
#undef def_Ticks
#undef def_LIMIT
        }

Comenzamos indicando que el tipo de representación será el del modelo de FOREX. Sin embargo, si durante la lectura del archivo de ticks se encuentra algún tick que contenga volumen negociado, este modelo se cambiará al tipo BOLSA. Es importante que entiendas que esto sucede sin ninguna intervención del usuario. Pero ahora surge una cuestión importante: este sistema solo funcionará para los casos en los que la lectura se realice cuando se esté ejecutando una repetición. En el caso de la simulación, la situación será diferente. Aún no nos preocuparemos por la simulación.

Por esta razón, hasta que se haya creado el código de simulación, NO debes utilizar solo archivos de barras. Debes utilizar obligatoriamente archivos de ticks, ya sean reales o simulados. Existen formas de crear archivos de ticks simulados, pero no entraré en detalles ya que eso iría más allá del alcance de este artículo. Sin embargo, no debemos dejar a los usuarios completamente en la oscuridad. A pesar de que el sistema puede analizar las cosas, podemos mostrar al usuario qué tipo de representación se está utilizando. Así, al abrir la ventana de Activo, podrá verificar la forma de representación. De la misma manera que se muestra en la figura 01 y la figura 02.

Para que esto sea posible, debemos agregar algunas cosas más a nuestro código. Entre ellas, las líneas que se muestran en el procedimiento a continuación:

datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
    {
        int      MemNRates,
                 MemNTicks;
        datetime dtRet = TimeCurrent();
        MqlRates RatesLocal[];

        MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
        MemNTicks = m_Ticks.nTicks;
        if (!Open(szFileNameCSV)) return 0;
        if (!ReadAllsTicks(ToReplay)) return 0;
        if (!ToReplay)
        {
            ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
            ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
            CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
            dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
            m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
            m_Ticks.nTicks = MemNTicks;
            ArrayFree(RatesLocal);
        }else
        {
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
            CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
        }
        m_Ticks.bTickReal = true;

        return dtRet;
    };

Con la adición de estas dos líneas, que se destacan, tendremos información más adecuada sobre el activo. Pero quiero que prestes mucha atención a lo que voy a explicar, ya que si no comprendes alguno de los conceptos aquí, pensarás que el sistema te está tomando el pelo. En la figura 05, puedes ver lo que hace este código de arriba. Pruébalo con otros activos para comprenderlo. La primera cosa se refiere a esta línea específica. Mostrará qué tipo de cálculo podremos o estaremos utilizando en el activo. Es cierto que existen más formas de cálculo de activos, pero como la idea aquí es simplificar al máximo mientras permitimos que funcione, hemos resumido todo a solo estos dos tipos de cálculo.

Si deseas obtener más detalles o implementar otros tipos de cálculos, puedes consultar la documentación y revisar SYMBOL_TRADE_CALC_MODE. Allí encontrarás una descripción detallada de cada uno de los modos de cálculo. Aquí estaremos trabajando solo en el modo más básico de la cosa. Ahora, el detalle, que puede volverte loco, es precisamente esta segunda línea resaltada. En esta línea, simplemente estamos indicando el tipo de modo de representación. Básicamente, existen solo estos dos tipos. El problema aquí no es esta línea en sí misma. El problema está en el archivo de configuración, o mejor dicho, en cómo se lee el archivo de configuración en la etapa actual de desarrollo.

De la forma en que el sistema está codificado actualmente, si lees un archivo de barras y luego un archivo de ticks, tendrás problemas. No es porque estés haciendo algo mal, todo lo contrario, estás siguiendo la lógica correcta. Sin embargo, el hecho de que esta línea se ejecute después de que el archivo de barras se haya cargado en el gráfico hace que cualquier contenido presente en el gráfico sea eliminado. Este problema se debe a la falta de un búfer de datos, ya que van directamente al gráfico. Así que tenemos que resolver esto. Sin embargo, esta solución se verá más adelante en el artículo.

Personalmente, si el sistema fuera solo para mi uso personal, todo se haría de manera diferente. Solo generaríamos algún tipo de alerta para que se leyera el archivo de ticks antes que el archivo de barras. Pero dado que el sistema será utilizado a menudo por personas que no tienen conocimientos de programación, considero apropiado resolver este tipo de problema. Es incluso bueno hacerlo, ya que aprenderás a hacer un truco muy interesante y bastante útil.

Figura 05

Figura 05: Visualización del reconocimiento automático de la lectura de ticks

Ahora que el sistema puede identificar algunas cosas, necesitamos que se adapte en algunos otros aspectos a nuestro sistema de representación.

class C_Replay : private C_ConfigService
{
    private :
        long         m_IdReplay;
        struct st01
        {
            MqlRates Rate[1];
            datetime memDT;
            int      delay;
        }m_MountBar;
        struct st02
        {
            bool     bInit;
        }m_Infos;

// ... Restante do código ...

Bueno, para que las cosas se configuren y permanezcan configuradas, agregaremos en este primer momento esta variable. Esta indicará si el sistema ha sido inicializado por completo. Pero presta atención a cómo la utilizaremos. Primero, la inicializaremos con un valor adecuado. Esto se hace en el siguiente fragmento de código:

C_Replay(const string szFileConfig)
{
    m_ReplayCount = 0;
    m_dtPrevLoading = 0;
    m_Ticks.nTicks = 0;
    m_Infos.bInit = false;
    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");
    CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
    m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
}

¡¿Por qué estamos inicializando la variable como falsa?! La razón es que el sistema aún no se ha inicializado. Pero una vez que carguemos el gráfico en la pantalla, estará inicializado. Entonces, podrías pensar que indicaremos esto en la rutina que inicializa el gráfico. ¿Correcto? Incorrecto. No podemos hacerlo en la rutina que inicializa el gráfico. Debemos esperar a que esta rutina de inicialización termine y, tan pronto como se llame a la próxima rutina, entonces podremos indicar que el sistema se ha inicializado. Pero, ¿por qué usar esa variable? ¿El sistema no sabe si ya se inicializó o no? ¿Para qué esa variable? Sí, el sistema sabe que se ha inicializado. Pero necesitamos esta variable por otro motivo. Para aclararlo, veamos la rutina que cambia su estado.

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
        {
                u_Interprocess Info;
                int iPos, iTest;

                if (!m_Infos.bInit)
                {
                        ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
                        ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
                        ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
                        m_Infos.bInit = true;
                }
                iTest = 0;
                while ((iTest == 0) && (!_StopFlag))
                {
                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                        if (iTest == 0) Sleep(100);
                }
                if ((iTest < 0) || (_StopFlag)) return false;
                AdjustPositionToReplay(bViewBuider);
                m_MountBar.delay = 0;
                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                {
                        CreateBarInReplay(bViewMetrics, true);
                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                        if (m_MountBar.delay > 400)
                        {
                                if (ChartSymbol(m_IdReplay) == "") break;
                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                if (!Info.s_Infos.isPlay) return true;
                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                Sleep(m_MountBar.delay - 20);
                                m_MountBar.delay = 0;
                        }
                }                               
                return (m_ReplayCount == m_Ticks.nTicks);
        }

Esta rutina de arriba nos causa ciertos dolores de cabeza. Por eso necesitamos la variable que indica si el sistema ha sido inicializado o no. Fíjate en lo siguiente: en la primera ejecución de esta rutina, la variable aún indicará que el sistema NO se ha inicializado por completo. Se completará su inicialización en ese momento. Lo que estamos haciendo aquí es asegurarnos de que las líneas de precio correctas se muestren en pantalla. Si el sistema identifica el modo de representación como el estilo FOREX, se mostrarán las líneas de precio BID y ASK, y se ocultará la línea del último precio. Lo contrario ocurre si el modo de representación es del estilo BOLSA. En este caso, se ocultarán las líneas de precio BID y ASK, y se mostrará la línea del último precio.

Todo muy bonito y muy bien, si no fuera por el hecho de que algunos usuarios prefieren hacer una configuración diferente. Incluso si operan en el estilo de representación de BOLSA, les gusta que se muestren las líneas BID o ASK, y en algunos casos, ambas líneas. Entonces, si el usuario pausa el sistema después de configurar las cosas a su manera y luego vuelve a iniciar el sistema, este ignorará la configuración del usuario y volverá a la configuración interna. Sin embargo, al indicar que el sistema ya se ha inicializado (y usando una variable para esto), no volverá a la configuración interna, permaneciendo de la forma en que el usuario acaba de configurarlo.

Pero aquí surge una pregunta: ¿Por qué no hacer esta configuración en la rutina ViewReplay? La razón es que el gráfico en realidad no estaba recibiendo esta configuración de las líneas. Y no es solo eso, también tenemos otros problemas bastante molestos que resolver. Realmente necesitamos variables adicionales para ayudarnos. La programación pura y simple no resolverá todos los problemas.


Presentación de las barras

Finalmente hemos llegado al punto en el que podemos presentar las barras en el gráfico. Sin embargo, si intentas hacerlo en este momento, te encontrarás con errores de rango en las matrices. Por lo tanto, antes de presentar realmente las barras en el gráfico, necesitamos hacer algunas correcciones y ajustes en el sistema.

La primera corrección se encuentra en la rutina a continuación:

void AdjustPositionToReplay(const bool bViewBuider)
        {
                u_Interprocess Info;
                MqlRates       Rate[def_BarsDiary];
                int            iPos, nCount;

                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
                for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                if (iPos < m_ReplayCount)
                {
                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                        CustomTicksDelete(def_SymbolReplay, m_Ticks.Info[iPos].time_msc, LONG_MAX);
                        if ((m_dtPrevLoading == 0) && (iPos == 0)) FirstBarNULL(); else
                        {
                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                m_ReplayCount++;
                        }
                }else if (iPos > m_ReplayCount)
                {
                        if (bViewBuider)
                        {
                                Info.s_Infos.isWait = true;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }else
                        {
                                for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++);
                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                        }
                }
                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false, false);
                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                Info.s_Infos.isWait = false;
                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
        }

Esta prueba permite que el sistema determine si debe o no omitir los primeros ticks. El problema es que sin verificar si estamos trabajando o no con una representación similar a la de la bolsa, el bucle habilitado por la prueba fallará. Esto generará un error de rango. Sin embargo, al agregar una segunda prueba, superaremos esta etapa. Si el modo de representación es del tipo encontrado en el forex, el bucle no se ejecutará. Así que estaremos listos para la siguiente etapa.

En esta próxima etapa, realmente insertaremos los ticks en el gráfico. Aquí, lo único de lo que debemos preocuparnos es informar al sistema cuál será el precio de cierre de la barra; el resto lo maneja la rutina de modelado de la barra. En este caso, será la misma tanto para una representación de la bolsa como para una del forex. El código para hacerlo se puede ver 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 (bNew = (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;
        }
        def_Rate.close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close) :
                                                               (m_Ticks.Info[m_ReplayCount].bid > 0.0 ? m_Ticks.Info[m_ReplayCount].bid : 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
    }

Lo que hace esta línea es precisamente eso. Generará el precio de cierre de la barra basado en el tipo de representación que se está utilizando. El resto de la rutina permanece igual que antes. De esta manera, logramos cubrir el sistema de representación del forex y podemos realizar la repetición con los datos proporcionados en un archivo de ticks. Aun no tenemos la capacidad de hacer la simulación.

Puede que estés pensando que el sistema ya está terminado, pero no es así. Aún tenemos 2 problemas, y son bastante urgentes, antes de siquiera pensar en simular el forex.

El primer problema es que necesitamos que el archivo de configuración del repetidor/simulador tenga una lógica de creación. Esto obligará al usuario, en muchas ocasiones, a adaptarse al sistema de manera innecesaria. Además de esto, tenemos otro problema. El sistema de temporización. La razón de este segundo problema es que podríamos estar tratando con un activo o un momento de negociación en el que el activo pueda permanecer inactivo durante horas, ya sea porque está en subasta, suspendido o por cualquier otro motivo. No importa, también necesitamos corregir este problema del temporizador.

Dado que este segundo problema es más inmediato y urgente, vamos a empezar por él.


Corrección del temporizador

El gran y real problema en el sistema y el temporizador es que el sistema no puede manejar un tipo de condición que a veces ocurre en algunos activos. Esta condición puede ser una liquidez extremadamente baja, suspensión de operaciones, subastas u otras razones. Si por alguna razón el archivo de ticks indica al temporizador que el activo debe estar en modo de suspensión durante 15 minutos, por ejemplo, el sistema quedará completamente bloqueado durante ese tiempo.

En el mercado real, esto se maneja de una manera específica. Normalmente, la plataforma nos informará si el activo no se está negociando. Pero incluso si la plataforma no nos proporciona esta información, aún recibirá una notificación del mercado. Los operadores más experimentados al observar el activo notarán que algo está ocurriendo y que no hay nada que hacer durante ese período. Sin embargo, en el caso de estar utilizando la repetición del mercado, este tipo de situación puede ser problemática. Debemos permitir que el usuario la cierre o intente cambiar la posición en la que se está ejecutando la repetición o simulación.

Este tipo de solución ya se ha implementado antes, de hecho, puedes utilizar el indicador de control para hacerlo. Pero no había precedentes que nos obligaran a tomar una medida tan radical, a tal punto de permitir que una repetición salga de una situación en la que el activo estuviera en subasta, con una liquidez muy baja o incluso suspendido debido a un evento relevante. Todo esto genera lo que se conoce como riesgo de liquidez, pero en el sistema de repetición/simulación, podemos evitar fácilmente este riesgo y continuar con nuestros estudios. Sin embargo, para hacerlo efectivamente, necesitamos cambiar la forma en que funciona el temporizador.

A continuación, tienes el nuevo bucle del sistema de representación. Sé que el código puede parecer confuso a primera vista, pero observémoslo con calma.

bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
    {
        u_Interprocess Info;
        int iPos, iTest;

        iTest = 0;
        while ((iTest == 0) && (!_StopFlag))
        {
            iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
            iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
            iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
            if (iTest == 0) Sleep(100);
        }
        if ((iTest < 0) || (_StopFlag)) return false;
        AdjustPositionToReplay(bViewBuider);
        iPos = 0;
        while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
        {
            iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
            m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
            iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
            CreateBarInReplay(bViewMetrics, true);
            if (m_MountBar.delay > 400)
            while ((iPos > 200) && (!_StopFlag))
            {
                if (ChartSymbol(m_IdReplay) == "") break;
                if (ChartSymbol(m_IdReplay) == "") return false;
                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                if (!Info.s_Infos.isPlay) return true;
                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                Sleep(195);
                iPos -= 200;
                Sleep(m_MountBar.delay - 20);
                m_MountBar.delay = 0;
            }
        }                               
        return (m_ReplayCount == m_Ticks.nTicks);
    }

Todas las partes borradas han sido reemplazadas por otros códigos. De esta manera, podremos resolver este primer problema relacionado con la representación de las barras. Anteriormente, utilizábamos un cálculo hacia atrás. Ahora vamos a utilizar un cálculo hacia adelante. Esto evita algunas cosas extrañas en el gráfico mientras estamos en modo de espera. Presta atención al hecho de que cuando iniciamos el sistema, siempre esperará un tiempo antes de mostrar el tick. Anteriormente, se hacía justo lo contrario (MI ERROR). Ahora, debido a que el tiempo puede ser extremadamente largo, incluso de varias horas, tenemos otra forma de gestionar el temporizador. Para entender mejor, debes saber que antes el sistema permanecía en modo de espera hasta que todo el tiempo se completaba. Si intentabas hacer algún cambio, como cerrar el gráfico o intentar cambiar el punto de ejecución, el sistema simplemente no respondía como se esperaba. Esto se debía a que en esos archivos que se adjuntaban previamente, no había riesgo de utilizar un conjunto en el que el activo permaneciera sin negociación durante mucho tiempo. Pero cuando comencé a escribir este artículo, el sistema mostró esta falla. Por lo tanto, se realizaron correcciones.

Ahora, el temporizador se ejecutará hasta que el período sea mayor o igual a 200 milisegundos. Puedes cambiar este valor, pero asegúrate de cambiarlo también en estos otros puntos. Entonces, hemos comenzado a corregir las cosas, pero a pesar de todo, necesitamos hacer una cosa más antes de terminar. Si cierras el gráfico, el sistema saldrá del bucle. Bien. Ahora regresa al autor de llamada. Esto garantiza que las cosas funcionarán, al menos en teoría. Esto se debe a que el usuario puede interactuar nuevamente con el sistema durante el período de temporización. Las demás funciones prácticamente se mantuvieron intactas, por lo que todo sigue funcionando como antes. Sin embargo, si durante el período de temporización le pides al indicador de control que cambie la posición, ahora será posible hacerlo, antes no lo era. Esto es muy importante, ya que algunos activos pueden entrar en modo de suspensión y permanecer así durante un período bastante largo. Lo que es tolerable en una negociación real, pero NO en un sistema de repetición/simulación.


Conclusión

A pesar de todos los contratiempos, ya puedes comenzar a experimentar el uso de datos de FOREX en el sistema. A partir de esta versión de la Repetición/Simulador. Para probarlo, en el anexo tendrás acceso a algunos datos de FOREX. El sistema aún necesita correcciones en algunos puntos. Pero como no quiero entrar en detalles, ya que esto podría requerir cambios radicales en algunos puntos mostrados en este artículo, voy a finalizar las modificaciones aquí.

En el próximo artículo, resolveré algunos problemas adicionales que quedaron pendientes. No es que estos problemas te impidan utilizar el sistema. Sin embargo, si lo vas a utilizar con datos del mercado de divisas, notarás que algunas cosas no se están presentando correctamente. Necesitamos corregirlas mínimamente, pero eso se verá en el próximo artículo.

Todavía queda por resolver el problema del archivo de configuración, además de otros problemas relacionados con él. Dado que el sistema está funcionando adecuadamente, permitiéndote hacer una repetición de datos obtenidos en el mercado de divisas, la solución de estos problemas pendientes se pospondrá para el próximo artículo.


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

Archivos adjuntos |
Market_Replay_yvg20.zip (14386.04 KB)
Desarrollo de un sistema de repetición — Simulación de mercado (Parte 21):  FOREX (II) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 21): FOREX (II)
Vamos a continuar el armado del sistema para cubrir el mercado FOREX. Entonces, para resolver este problema, primero necesitaríamos declarar la carga de los ticks antes de cargar las barras previas. Esto soluciona el problema, pero al mismo tiempo obliga al usuario a seguir un tipo de estructura en el archivo de configuración que, en mi opinión, no tiene mucho sentido. La razón es que, al desarrollar la programación responsable de analizar y ejecutar lo que está en el archivo de configuración, podemos permitir que el usuario declare las cosas en cualquier orden.
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 sistema de repetición — Simulación de mercado (Parte 22): FOREX (III) Desarrollo de un sistema de repetición — Simulación de mercado (Parte 22): FOREX (III)
Para aquellos que aún no han comprendido la diferencia entre el mercado de acciones y el mercado de divisas (forex), a pesar de que este ya es el tercer artículo en el que abordo esto, debo dejar claro que la gran diferencia es el hecho de que en forex no existe, o mejor dicho, no se nos informa acerca de algunas cosas que realmente ocurrieron en la negociación.
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
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 adecuadamente, tendremos la posibilidad de tener un sistema realmente muy versátil. Capaz de adaptarse muy fácilmente a cualquier situación que deba ser cubierta.