English Русский 中文 Deutsch 日本語 Português
Swaps (parte I) : Bloqueo de posiciones y posiciones sintéticas

Swaps (parte I) : Bloqueo de posiciones y posiciones sintéticas

MetaTrader 5Trading | 24 junio 2021, 08:39
1 125 0
Evgeniy Ilin
Evgeniy Ilin

Índice

    Introducción

    Llevamos mucho dándole vueltas al tema de este artículo, pero, hasta ahora, la falta de tiempo había hecho imposible la profundización en el mismo. El tema de los swaps está bastante extendido en Internet, sobre todo entre los profesionales del trading acostumbrados a contar cada punto, enfoque que, ciertamente, es el mejor para el trading. En este artículo, el lector no solo podrá comprobar la necesidad de considerar los swaps, sino que también comprenderá cómo usarlos. Los más curiosos encontrarán una idea muy compleja, pero interesante, relacionada con la modernización de los métodos comerciales basados en swaps, que, si se resuelve correctamente, podremos usar tanto en el marco de una cuenta comercial, como para mejorar los beneficios en el bloqueo de posiciones clásico con dos cuentas.


    Sobre los swaps

    No vamos a hablar aquí de qué es un swap ni profundizar en la teoría. Nos interesa el plano puramente aplicado. La pregunta más importante es la siguiente: ¿podemos ganar dinero con los swaps? Desde el punto de vista de los tráders, un swap supone o bien un beneficio o bien una pérdida, y muchos no lo tienen en cuenta en absoluto, porque comercian dentro del día. Otros simplemente intentan no prestarle atención, pensando (en vano) que su influencia resulta tan insignificante que prácticamente no afectará al trading. De hecho, casi la mitad del spread se puede ocultar en el swap, solo que este spread se elimina no en el momento de una compra o una venta, sino en el momento en que cambia el día en el servidor.

    El swap se cobra en proporción al volumen de la posición abierta y tiene lugar en momentos como el paso de un día a otro:

    1. Del lunes al martes
    2. Del martes al miércoles
    3. Del miércoles al jueves (casi todos los brókeres cobran un swap triple esa noche)
    4. Del jueves al viernes

    Por lo general, el swap se indica en las especificaciones del instrumento comercial en puntos o en porcentaje. Obviamente, existen otros métodos de cálculo, pero hasta ahora solo hemos tratado con dos; esto parece suficiente por el momento. Existe muy poca información estructurada sobre este tema, pero si nos aclaramos un poco con ella, veremos que incluso hay estrategias de trabajo para los swaps. Estas ofrecen un rendimiento mínimo, pero tienen la ventaja de proporcionar beneficios de forma absolutamente garantizada. La principal dificultad de este enfoque reside en que los brókeres más populares tienen muy pocos instrumentos con un swap positivo para ganar dinero con ellos. Si resulta posible ganar algo, serán apenas unos centavos. Sin embargo, esto resulta mejor que agotar completamente el depósito, y si usamos cualquier otro sistema comercial, lo más probable es que lo perdamos. 

    Nuestra conclusión con respecto al comercio en Fórex es que, aparte de los swaps positivos, nada puede garantizarnos beneficios. Claro está que existen sistemas capaces de generar beneficios, pero al usarlos, aceptamos deliberadamente que para cualquier operación comercial estamos dándole gradualmente nuestro dinero al bróker, alimentando la ilusión de que el precio irá en nuestra dirección. Un swap positivo representa el proceso inverso. En opinión del autor, los marcadores comerciales en la dirección de un swap positivo son las siguientes afirmaciones:

    • Un swap positivo equivale a un movimiento de precio parcial en la dirección de nuestra posición abierta (beneficio diario)
    • Después de un tiempo, un swap puede cubrir las pérdidas derivadas de los spreads y comisiones, y después solo nos proporcionará dinero
    • Para que un swap funcione, deberemos mantener la posición el mayor tiempo posible, luego el factor de beneficio de nuestras posiciones tomará los valores máximos
    • Con la debida persistencia y aplicación, el beneficio se convierte en algo absolutamente predecible y garantizado

    Obviamente, la mayor desventaja de este enfoque es la dependencia respecto al tamaño del depósito, pero, al mismo tiempo, ningún otro concepto es capaz de garantizar beneficios en Fórex con tanta confianza. Esta dependencia se puede reducir disminuyendo el volumen de posiciones abiertas o, lo que es lo mismo, el riesgo. El riesgo es la relación entre el volumen de nuestra posición y nuestro depósito, ya que, a medida que aumenta, resulta más y más probable que el precio se incline del lado de las pérdidas y que el depósito no resulte suficiente para esperar a que los beneficios de los swaps compensen las pérdidas derivadas de los swaps y las comisiones. Para minimizar la influencia de todos los posibles efectos negativos, hemos inventado el mecanismo de bloqueo de posiciones que discutiremos a continuación.


    Bloqueo con uso de dos cuentas comerciales

    Este método de comercio con swaps es el más popular entre los tráders. Para implementar esta estrategia, necesitaremos dos cuentas con diferentes swaps para las mismas parejas de divisas u otros instrumentos comerciales. Este paso resulta imprescindible, ya que abrir dos posiciones opuestas dentro de la misma cuenta carece de sentido: equivale simplemente a extraviar el depósito, porque incluso si el instrumento tiene un swap positivo, cuando operamos en la dirección opuesta, siempre es negativo. Hemos creado un diagrama que muestra claramente el concepto de este método:

    Classic Swap Trading

    Como podemos ver en el diagrama, para el instrumento específico para el que queremos comerciar con swaps, solo existen 10 escenarios comerciales, 6 de los cuales se usan activamente. Las últimas cuatro opciones se pueden elegir como último recurso, si resulta imposible encontrar una pareja de divisas que se encuentre en las condiciones "1-6", ya que, en este caso, uno de los swaps resulta ser negativo, y simplemente tenemos una situación en la que el swap positivo es mayor que el negativo. Nos encontraremos todas estas situaciones si analizamos un gran número de brókeres diferentes y analizamos sus tablas de swaps. Pero al usar esta estrategia, las mejores opciones para nosotros serán la 2 y la 5. Lo que ocurre es que en estas opciones hay swaps positivos en ambos extremos, por lo que, a la hora de comerciar, obtendremos mucho beneficio de ambos brókeres, además, no deberemos transferir fondos de una cuenta a otra con tanta frecuencia.

    La principal desventaja de esta estrategia es la necesidad de transferir dinero constantemente de una cuenta a otra, porque al abrir posiciones opuestas, en un bróker obtendremos pérdidas, mientras que en el otro tendremos beneficios. No obstante, si calculamos correctamente los volúmenes de las transacciones realizadas en relación con el depósito existente, esto no tendría que repetirse con demasiada frecuencia. Sin embargo, hay una ventaja indiscutible: conseguiremos beneficios en cualquier caso, y también podremos predecir su valor exacto. A muchos tráders les gustaría eliminar esta rutina y realizar de alguna forma estas manipulaciones dentro de una sola cuenta (lo cual, resulta imposible por definición). A pesar de que no podemos pasar por completo al trabajo con una sola cuenta, si que existe una forma de, como mínimo, aumentar los beneficios del método clásico de comercio con swaps. A continuación, discutiremos las principales características de este método.


    Sobre los tipos de cambio

    Empezaremos por lo más importante: la base sobre la que se construye toda la lógica, y a partir de la cual podemos construir las ecuaciones matemáticas. Tomaremos, por ejemplo, las parejas de divisas EURUSD, USDJPY, EURJPY. Estas 3 parejas están relacionadas. Para comprender su relación, deberemos representar estas herramientas en una forma ligeramente distinta:

    • 1/P = EUR/USD
    • 1/P = USD/JPY
    • 1/P = EUR/JPY
    • P - tipo de cambio de la divisa seleccionada

    En realidad, en cualquier instrumento siempre existe una divisa o algo que la reemplaza, que adquirimos, y también existe una divisa que damos. Por ejemplo, si tomamos la primera relación (pareja EURUSD), cuando abrimos una posición de 1 lote en "Buy", adquirimos 100.000 unidades de la divisa básica. Estas son las reglas para comerciar en el mercado Fórex, un lote siempre es igual a 100.000 unidades de la divisa básica. En esta pareja, la divisa básica es "EUR", por lo que compramos "EUR" por "USD". En este caso, el tipo de cambio "P" indica cuántas unidades de "USD" hay en 1 "EUR". Para el resto de instrumentos resulta similar: la divisa básica se encontrará en el numerador de la fracción, mientras que el denominador contendrá (la llamaremos así, con el permiso del lector) la "divisa principal". Si a alguien le disgusta la terminología, puede escribir al respecto en los comentarios al artículo. La cantidad de la divisa principal se calcula simplemente multiplicando el precio por el valor "EUR":

    • 1/P = EUR/USD --->  USD/P = EUR ---> USD = P*EUR
    • EUR = Lots*100000

    Si abrimos una posición de venta, las divisas parecerán cambiar de lugar. La divisa básica comenzará a desempeñar el papel de la divisa principal, mientras que la divisa principal desempeñará el papel de la básica. En otras palabras, estaremos ya comprando "USD" por "EUR", pero la cantidad de dinero de ambas divisas se calculará de la misma forma en relación con "EUR". Esto es lo correcto, porque de lo contrario habría mucha confusión. Exactamente de la misma manera se calculará todo para las otras divisas, solo que acordaremos que si una divisa juega el papel de básica, se considerará con el signo "+", y si una divisa juega el papel de principal, la consideraremos con el signo "-". Como resultado, cualquier transacción se corresponderá con un conjunto de dos números que simbolizarán lo que se ha comprado y por cuánto se ha comprado. Otra interpretación de esto es que siempre habrá una divisa que actuará como mercancía y otra divisa que actuará propiamente como divisa, y que pagaremos para comprar la mercancía. 

    Si abrimos varias posiciones para varios instrumentos, entonces habrá más divisas principales y adicionales, y obtendremos una especie de posición sintética. Desde el punto de vista del uso de los swaps, una posición sintética de este tipo resultará absolutamente inútil, pero podremos crear una posición sintética que resulte de gran utilidad. Veremos cómo hacer esto un poco más adelante: por ahora, continuaremos un poco más. Ya hemos determinado cómo se calcula el volumen expresado a través de ambas divisas; en base a ello, podemos llegar a la conclusión de que es posible crear una posición sintética compleja de esta forma, y que resultará equivalente a alguna más simple:

    • EUR/JPY = EUR/USD * USD/JPY - tipo de cambio compuesto por dos derivados

    En realidad, existe un número infinito de relaciones de ese clase, compuestas por varias divisas, como por ejemplo:

    • EUR - Euro
    • USD - Dólar USA
    • JPY - Yen japonés
    • GBP - Libra esterlina
    • CHF - Franco suizo
    • CAD - Dólar canadiense
    • NZD - Dólar neozelandés
    • AUD - Dólar australiano
    • CNY - Yuan chino
    • SGD - Dólar de Singapur
    • NOK - Corona noruega
    • SEK - Corona sueca

    Esta no es la lista completa de divisas, pero solo necesitamos saber que podemos usar 2 divisas cualesquiera de esta lista para crear un instrumento comercial aleatorio. Algunos de estos instrumentos comerciales son ofrecidos por brókeres, algunos pueden obtenerse como una combinación de posiciones de otros instrumentos. Un ejemplo típico es nuestra pareja EURJPY. Este es solo el ejemplo más simple de construcción de tipos de cambio derivados, pero, usando como base estas reflexiones, podemos llegar a la conclusión de que cualquier posición puede representarse como un conjunto de posiciones de otros instrumentos. Partiendo de las consideraciones anteriores, tenemos que:

    • Value1 - divisa básica del instrumento, expresada en valor absoluto
    • Value2 - divisa adicional del instrumento, expresada en valor absoluto
    • A - volumen de la divisa básica de la posición expresada en lotes
    • B - volumen de la divisa principal de la posición expresado en lotes
    • Contract - cantidad de divisa comprada o vendida, expresada en valor absoluto (corresponde a 1 lote)
    • A = 1/P = Value1/Value2 - ecuación de cualquier instrumento (incluidos los instrumentos que no se presentan en la ventana de observación del mercado)
    • Value1 = Contract*A
    • Value2 = Contract*B

    Necesitaremos estas relaciones en el futuro para calcular los lotes. Por el momento, solo tenemos que recordarlas. Estas relaciones describen la proporción en la cantidad de divisas compradas o vendidas; partiendo de esta, podremos construir una lógica más seria para el código.


    Bloqueo con uso de posiciones sintéticas

    Llamamos posición sintética a la posición que puede construirse a partir de otras posiciones; en este caso, además, estas otras posiciones deberán estar compuestas necesariamente por otros instrumentos. Esta posición deberá equivaler a una posición abierta para cualquier instrumento. ¿Para qué tantas dificultades?, se preguntará el lector. Todo es bastante simple. Una posición así puede ser necesaria para:

    1. Bloquear la posición original en un instrumento simulado
    2. Intentar compensar el equivalente a una posición con otros indicadores de swap completamente distintos
    3. Otros objetivos
    Esta idea surgió en relación con el punto №2. El hecho es que los brókeres establecen los swaps en mayor medida por motivos propios y, en primer lugar, para obtener beneficios adicionales. Estas consideraciones también incluyen tener en cuenta los swaps de otras empresas competidoras para que los tráders no puedan comerciar con swaps libremente. A continuación, mostraremos varios diagramas para que el lector se familiarice con este concepto. Incluso podrá complementarlo de alguna forma.

    Más abajo, se muestra el esquema general de este método:

    Method diagram

    Por sí solo, incluso este gráfico resulta insuficiente para recopilar datos completos sobre cómo abrir una posición sintética. Este diagrama solo indica cómo determinar la dirección del comercio para un componente específico de una posición sintética, que necesariamente deberá estar representada por uno de los instrumentos disponibles del bróker elegido. 

    Ahora necesitamos determinar cómo calcular los volúmenes de estas posiciones. Lo lógico sería calcular estos volúmenes partiendo de la base de que la posición debe equivaler a la posición abierta por el lote "1.0" en el instrumento resultante al que se reduce la variante seleccionada de la ecuación. Para el cálculo, necesitaremos los siguientes valores:

    • ContractB - tamaño del contrato de la pareja al que se reduce la ecuación (en la mayoría de los casos, es igual a 100.000 unidades de la divisa básica del instrumento)
    • Contrato [1] - tamaño del contrato de la pareja para la que deseamos determinar el lote
    • A [1] - cantidad de divisa básica expresada en lotes de la pareja equilibrada anterior (o la primera en la cadena
    • B[1] - cantidad de divisa principal expresada en lotes de la pareja equilibrada anterior (o la primera en la cadena
    • A[2] - cantidad de la divisa básica expresada en lotes de la pareja equilibrada actual
    • B[2] - cantidad de la divisa principal expresada en lotes de la pareja equilibrada actual
    • C [1] - tamaño del contrato de la pareja equilibrada anterior (o la primera en la cadena)
    • C [2] - tamaño del contrato de la pareja equilibrada actual

    Querríamos decir que "ContractB" no siempre se puede determinar, ya que, como resultado de la combinación, podemos obtener un instrumento que no sea representado por el bróker; en este caso, podemos configurarlo aleatoriamente, por ejemplo, igual a una constante básica "100000".

    Primero, determinamos la primera pareja de la cadena que contiene la divisa básica del instrumento resultante en la posición deseada, después de lo cual, realizamos la búsqueda de las parejas restantes que compensan las divisas que sobran y que no estén incluidas en el equivalente resultante. El equilibrio finaliza cuando la divisa principal está en la posición correcta en la pareja analizada. Hemos creado especialmente un diagrama para dejar en claro cómo se hace esto:

    Normalization

    Todo lo que queda es implementar estas técnicas en el código y analizar los resultados. El primer prototipo será lo más simple posible: servirá solo para valorar la exactitud de todas las consideraciones. Esperamos que los diagramas hayan servido de ayuda a quienes querían comprender mejor el material.


    Escribiendo una utilidad para investigar los polígonos de swaps

    Clasificando la ventana de Market Watch y preparando los datos:

    Para usar esta técnica, primero debemos seleccionar solo aquellas parejas cuyos nombres tengan exactamente 6 caracteres de longitud y consten solo de letras mayúsculas. Según parece, todos los brókeres se adhieren a esta práctica. En algunos casos, aparecen prefijos o sufijos que también debemos tener en cuenta a la hora de escribir los algoritmos para trabajar con datos de línea. Para guardar la información sobre el instrumento en un formato adecuado, hemos creado dos estructuras; la segunda se usará más tarde:

    struct Pair// required symbol information
       {
       string Name;// currency pair
       double SwapBuy;// buy swap
       double SwapSell;// sell swap
       double TickValue;// profit from 1 movement tick of a 1-lot position
       double TickSize;// tick size in the price
       double PointX;// point size in the price
       double ContractSize;// contract size in the base deposit currency
       double Margin;// margin for opening 1 lot
       };
    
    struct PairAdvanced : Pair// extended container
       {
       string Side;// in numerator or denominator
       double LotK;// lot coefficient
       double Lot;// lot
       };
    

    Algunos campos no se usarán aquí al clasificar las parejas. Para no generar demasiados contenedores, lo hemos expandido un poco, de forma que esta estructura pueda usarse también para otros propósitos. Teníamos un prototipo de un algoritmo similar, pero sus posibilidades eran muy limitadas: solo podíamos analizar las parejas que se encontraban en la ventana principal del terminal; aquí todo resulta más simple y, lo más importante, está automatizado y es más variable. Para establecer el tamaño de la matriz con los instrumentos, necesitamos esta función:

    Pair Pairs[];// data of currency pairs
    void SetSizePairsArray()// set size of the array of pairs
       {
       ArrayResize(Pairs,MaxSymbols);
       ArrayResize(BasicPairsLeft,MaxPairs*2); // since each pair has 2 currencies, there can be a maximum of 2 times more base currencies
       ArrayResize(BasicPairsRight,MaxPairs*2);// since each pair has 2 currencies, there can be a maximum of 2 times more base currencies
       }
    

    La primera línea establece el número máximo de parejas que podemos configurar desde la ventana de Market Watch; las otras dos también establecen el tamaño de las matrices que utilizaremos. Las 2 matrices restantes cumplen un papel auxiliar, para cortar las parejas de divisas en 2 partes (2 divisas compuestas). Las variables resaltadas en amarillo son los parámetros de entrada del asesor.

    • MaxSymbols - tamaño máximo de almacenamiento con parejas (hemos decidido configurarlo manualmente)
    • MaxPairs - número máximo de parejas en ambas partes de la fórmula que generamos (el asesor no buscará fórmulas más largas que este número)

    Para comprobar si un instrumento comercial cumple con los criterios (signos de dos divisas diferentes que potencialmente pueden encontrarse en otros instrumentos), hemos creado la siguiente función de predicado:

    bool IsValid(string s)//проверка инструмента на валидность (должен быть составлен из букв верхнего регистра)
       {
       string Mask="abcdefghijklmnopqrstuvwxyz1234567890";//маска неподдерживаемых символов (буквы нижнего регистра и цифры)
       for ( int i=0; i<StringLen(s); i++ )// reset symbols
          {
          for ( int j=0; j<StringLen(Mask); j++ )
             {
             if ( s[i] == Mask[j] ) return false;
             }
          }   
       return true;
       }
    

    Esta función no es la única condición para realizar futuras comprobaciones con los instrumentos: simplemente no podemos escribir esta condición dentro de una expresión lógica, por lo que resulta más fácil representarla como un predicado. Ahora vamos a pasar a la función principal, encargada de rellenar nuestra matriz con los datos necesarios:

    void FillPairsArray()// fill the array with required information about the instruments
       {
       int iterator=0;
       double correction;
       int TempSwapMode;
       
       for ( int i=0; i<ArraySize(Pairs); i++ )// reset symbols
          {
          Pairs[iterator].Name="";
          }   
       
       for ( int i=0; i<SymbolsTotal(false); i++ )// check symbols from the MarketWatch window
          {
          TempSwapMode=int(SymbolInfoInteger(Pairs[iterator].Name,SYMBOL_SWAP_MODE));
          if ( StringLen(SymbolName(i,false)) == 6+PrefixE+PostfixE && IsValid(SymbolName(i,false)) && SymbolInfoInteger(SymbolName(i,false),SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_FULL  
          && ( ( TempSwapMode  == 1 )  ||  ( ( TempSwapMode == 5 || TempSwapMode == 6 ) && CorrectedValue(Pairs[iterator].Name,correction) )) )
             {
             if ( iterator >= ArraySize(Pairs) ) break;
             Pairs[iterator].Name=SymbolName(i,false);
             Pairs[iterator].TickSize=SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_TRADE_TICK_SIZE);
             Pairs[iterator].PointX=SymbolInfoDouble(Pairs[iterator].Name, SYMBOL_POINT);
             Pairs[iterator].ContractSize=SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_TRADE_CONTRACT_SIZE);
             switch(TempSwapMode)
               {
                case  1:// in points
                  Pairs[iterator].SwapBuy=SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_SWAP_LONG)*Pairs[iterator].TickValue*(Pairs[iterator].PointX/Pairs[iterator].TickSize);
                  Pairs[iterator].SwapSell=SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_SWAP_SHORT)*Pairs[iterator].TickValue*(Pairs[iterator].PointX/Pairs[iterator].TickSize);              
                  break;
                case  5:// in percent
                  Pairs[iterator].SwapBuy=correction*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_SWAP_LONG)*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_BID)*Pairs[iterator].ContractSize/(360.0*100.0);
                  Pairs[iterator].SwapSell=correction*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_SWAP_SHORT)*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_BID)*Pairs[iterator].ContractSize/(360.0*100.0);              
                  break;
                case  6:// in percent
                  Pairs[iterator].SwapBuy=correction*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_SWAP_LONG)*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_BID)*Pairs[iterator].ContractSize/(360.0*100.0);
                  Pairs[iterator].SwapSell=correction*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_SWAP_SHORT)*SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_BID)*Pairs[iterator].ContractSize/(360.0*100.0);              
                  break;              
               }     
             Pairs[iterator].Margin=SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_MARGIN_INITIAL);
             Pairs[iterator].TickValue=SymbolInfoDouble(Pairs[iterator].Name,SYMBOL_TRADE_TICK_VALUE);         
             iterator++;
             }
          }
       }
    

    En esta función, se encuentran la iteración simple de todos los símbolos y el filtrado según una condición compuesta y compleja que verifica tanto el cumplimiento de la longitud del nombre de la línea del símbolo, como la posibilidad de comerciar en este símbolo, además de otros parámetros relacionados con aquellos símbolos para los cuales el método de cálculo del swap se distingue del más utilizado "en puntos". Uno de los métodos de cálculo del swap se selecciona precisamente en el bloque "switch". Hasta ahora, solo hemos implementado 2 métodos: en puntos y en porcentaje. Principalmente, la correcta clasificación resulta importante para no hacer cálculos en vano. Lo único de lo que hablaremos es de la función resaltada en rojo. El hecho es que, en los casos en que la divisa principal (no la divisa básica) de una pareja es una divisa que no se corresponde con la divisa de nuestro depósito, todavía tenemos que introducir un cierto coeficiente de corrección que debería convertir nuestro swap a la divisa de nuestro depósito. Precisamente esta función se encarga de calcular este indicador. Este es su código:

    bool CorrectedValue(string Pair0,double &rez)// adjustment factor to convert to deposit currency for the percentage swap calculation method
       {
       string OurValue=AccountInfoString(ACCOUNT_CURRENCY);// deposit currency
       string Half2Source=StringSubstr(Pair0,PrefixE+3,3);// lower currency of the pair to be adjusted
       if ( Half2Source == OurValue )
          {
          rez=1.0;
          return true;
          }
       
       for ( int i=0; i<SymbolsTotal(false); i++ )// check symbols from the MarketWatch window
          {
          if ( StringLen(SymbolName(i,false)) == 6+PrefixE+PostfixE && IsValid(SymbolName(i,false)) )//find the currency rate to convert to the account currency
             {
             string Half1=StringSubstr(SymbolName(i,false),PrefixE,3);
             string Half2=StringSubstr(SymbolName(i,false),PrefixE+3,3);
         
             if ( Half2 == OurValue && Half1 == Half2Source )
                {
                rez=SymbolInfoDouble(SymbolName(i,false),SYMBOL_BID);
                return true;
                }
             if ( Half1 == OurValue && Half2 == Half2Source )
                {
                rez=1.0/SymbolInfoDouble(SymbolName(i,false),SYMBOL_BID);
                return true;
                }            
             }
          } 
       return false;
       }
    

    Esta función es un predicado, y retorna el valor del factor de corrección a la variable que ha sido transmitida desde fuera mediante un enlace. El factor de corrección se calcula usando el tipo de cambio de la divisa deseada en la que se encuentra la divisa de nuestro depósito.

    Generando fórmulas aleatorias

    Vamos a suponer que hemos rellenado nuestra matriz con los datos necesarios; ahora, de alguna manera, necesitamos iterar sobre estos instrumentos e intentar crear sobre la marcha todas las combinaciones posibles de fórmulas que se puedan crear a partir de estas parejas. Primero debemos decidir en qué forma se almacenará nuestra fórmula. La estructura que guarda todos los indicadores de esta fórmula deberá ser lo más simple posible, al tiempo que legible para los usuarios, porque, si queremos mirar los logs (y definitivamente querremos hacer esto) y la estructura es complicada, simplemente resultará imposible identificar los posibles errores.

    Estaría bien que nuestra fórmula fuera un conjunto de factores, tanto a la izquierda como a la derecha del signo "=". El factor puede ser el tipo de cambio en grado 1 o -1 (que equivale a una fracción invertida, o una unidad referida al tipo de cambio actual del instrumento). Hemos decidido tomar la estructura así:

    struct EquationBasic // structure containing the basic formula
       {
       string LeftSide;// currency pairs participating in the formula on the left side of the "=" sign
       string LeftSideStructure;// structure of the left side of the formula
       string RightSide;// currency pairs participating in the right side of the formula
       string RightSideStructure;// structure of the right side of the formula
       };
    

    Todos los datos se almacenarán en formato de línea. Para estudiar la fórmula, analizaremos estas líneas y extraeremos toda la información que necesitemos, además, siempre se pueden imprimir. Así es como se verá la impresión de las fórmulas generadas:

    Random Equations

    En nuestro parecer, un registro así resulta absolutamente claro y legible. El carácter "^" se usa como separador entre parejas. En la estructura de la fórmula no se necesitan separadores, ya que consta de los caracteres únicos "u" y "d", que indican el grado en que se encuentra el factor:

    1. "u" - es simplemente el tipo de cambio de la divisa
    2. "d" - 1/tipo de cambio de la divisa

    Como podemos ver, las fórmulas obtenidas a la salida tienen una longitud fluctuante y un tamaño flotante a ambos lados de la ecuación, pero este tamaño posee sus propias limitaciones. Este enfoque logra la máxima variabilidad de las fórmulas generadas y, como consecuencia, la máxima calidad de las opciones encontradas dentro de las condiciones comerciales del bróker seleccionado. Estas condiciones resultan completamente distintas para todos los brókeres. Para generar con éxito estas fórmulas, necesitamos funciones aleatorias adicionales que puedan generar números en el intervalo necesario; para ello, deberemos escribir nuestra propia funcionalidad utilizando las capacidades de la función incorporada MathRand:

    int GenerateRandomQuantityLeftSide()// generate a random number of pairs on the left side of the formula
       {
       int RandomQuantityLeftSide=1+int(MathFloor((double(MathRand())/32767.0)*(MaxPairs-1)));
       if ( RandomQuantityLeftSide >= MaxPairs ) return MaxPairs-1;
       return RandomQuantityLeftSide;
       }
    
    int GenerateRandomQuantityRightSide(int LeftLenght)//сгенерируем рандомное количество пар в правой стороне формулы (принимая во внимание сколько уже есть в левой стороне)
       {
       int RandomQuantityRightSide=1+int(MathFloor((double(MathRand())/32767.0)*(MaxPairs-LeftLenght)));
       if ( RandomQuantityRightSide < 2 && LeftLenght == 1 ) return 2;// there must be at least 2 pairs in one of the sides, otherwise it will be equivalent to opening two opposite positions
       if ( RandomQuantityRightSide > (MaxPairs-LeftLenght) ) return (MaxPairs-LeftLenght);
       return RandomQuantityRightSide;
       }
       
    int GenerateRandomIndex()// generate a random index of a symbol from the MarketWatch window
       {
       int RandomIndex=0;
         
       while(true)
          {
          RandomIndex=int(MathFloor((double(MathRand())/32767.0) * double(MaxSymbols)) );
          if ( RandomIndex >= MaxSymbols ) RandomIndex=MaxSymbols-1;
          if ( StringLen(Pairs[RandomIndex].Name) > 0 ) return RandomIndex;
          }
    
       return RandomIndex;
       }
    

    Las tres funciones nos resultarán útiles en una determinada etapa; por ahora, podemos escribir la función en sí, que se ocupará de la generación de estas fórmulas. A partir de este momento, el código se volverá cada vez más complejo, pero no usaremos un enfoque orientado a objetos, ya que la tarea no resulta estándar. Hemos decidido usar un enfoque procedimental. Los procedimientos han resultado muy grandes y engorrosos, pero no existe ninguna funcionalidad adicional, y cada función se encarga de su tarea específica sin usar funciones intermedias, evitando así la duplicación de código. En este caso, esto solo complicaría la comprensión de una tarea ya de por sí compleja y no estándar. La función tendrá el aspecto que sigue:

    EquationBasic GenerateBasicEquation()// generate both parts of the random equation
       {
       int RandomQuantityLeft=GenerateRandomQuantityLeftSide();
       int RandomQuantityRight=GenerateRandomQuantityRightSide(RandomQuantityLeft);
       string TempLeft="";
       string TempRight="";
       string TempLeftStructure="";
       string TempRightStructure="";   
       
       for ( int i=0; i<RandomQuantityLeft; i++ )
          {
          int RandomIndex=GenerateRandomIndex();
          if ( i == 0 && RandomQuantityLeft > 1 ) TempLeft+=Pairs[RandomIndex].Name+"^";
          if ( i != 0 && (RandomQuantityLeft-i) > 1 ) TempLeft+=Pairs[RandomIndex].Name+"^";
          if ( i == RandomQuantityLeft-1 ) TempLeft+=Pairs[RandomIndex].Name;
          
          if ( double(MathRand())/32767.0 > 0.5 ) TempLeftStructure+="u";
          else TempLeftStructure+="d";
          }
          
       for ( int i=RandomQuantityLeft; i<RandomQuantityLeft+RandomQuantityRight; i++ )
          {
          int RandomIndex=GenerateRandomIndex();
          
          if ( i == RandomQuantityLeft && RandomQuantityRight > 1 ) TempRight+=Pairs[RandomIndex].Name+"^";
          if ( i != RandomQuantityLeft && (RandomQuantityLeft+RandomQuantityRight-i) > 1 ) TempRight+=Pairs[RandomIndex].Name+"^";
          if ( i == RandomQuantityLeft+RandomQuantityRight-1 ) TempRight+=Pairs[RandomIndex].Name;
          
          if ( double(MathRand())/32767.0 > 0.5 ) TempRightStructure+="u";
          else TempRightStructure+="d";
          }
          
       EquationBasic result;
       result.LeftSide=TempLeft;
       result.LeftSideStructure=TempLeftStructure;
       result.RightSide=TempRight;
       result.RightSideStructure=TempRightStructure;
       
       return result;
       }
    

    Como podemos ver, las tres funciones anteriores se usan aquí para generar una fórmula aleatoria. Estas funciones no se usan en ninguna otra parte del código. Cuando nuestra fórmula esté lista, podremos proceder a analizar esta fórmula paso a paso. Aquellas fórmulas que sean incorrectas, serán descartadas por el siguiente filtro, más complejo, pero extremadamente importante. Lo primero que debemos hacer es comprobar la igualdad. Si no se detecta igualdad, entonces esta fórmula será incorrecta. Aquellas fórmulas que superen la prueba, serán sometidas a la siguiente etapa de análisis.

    Equilibrio de la fórmula

    Esta etapa implementa varios criterios de análisis al mismo tiempo:

    1. Cálculo de todos los factores sobrantes en el numerador y el denominador, y posterior eliminación de los mismos
    2. Comprobación de la presencia de 1 divisa en el numerador y 1 divisa en el denominador
    3. Comprobación de la correspondencia de las fracciones resultantes en los lados izquierdo y derecho
    4. Si el lado derecho supone la magnitud inversa del lado izquierdo, simplemente invertimos la estructura derecha de la fórmula (que es equivalente a elevar a la potencia "-1")
    5. Si todas las etapas se completan correctamente, el resultado se escribirá en una nueva variable.

    Este será el aspecto de todo ello en el código:

    BasicValue BasicPairsLeft[];// array of base pairs to the left
    BasicValue BasicPairsRight[];// array of base pairs to the right
    bool bBalanced(EquationBasic &CheckedPair,EquationCorrected &r)//сбалансирована ли текущая формула (если да, то вернем скорректированную версию в переменную "r")
       {
       bool bEnd=false;
       string SubPair;// the full name of the currency pair
       string Half1;// the first currency of the pair
       string Half2;// the second currency of the pair
       string SubSide;// the currency pair in the numerator or denominator
       string Divider;// separator
       int ReadStartIterator=0;// reading start index
       int quantityiterator=0;// quantity
       bool bNew;
       BasicValue b0;
       
       for ( int i=0; i<ArraySize(BasicPairsLeft); i++ )//reset the array of base pairs 
          {
          BasicPairsLeft[i].Value = "";
          BasicPairsLeft[i].Quantity = 0;
          }
       for ( int i=0; i<ArraySize(BasicPairsRight); i++ )// resetting the array of base pairs
          {
          BasicPairsRight[i].Value = "";
          BasicPairsRight[i].Quantity = 0;
          }
       ////Calculate balance values for the left side
       quantityiterator=0;
       ReadStartIterator=0;
       for ( int i=ReadStartIterator; i<StringLen(CheckedPair.LeftSide); i++ )// extract base currencies from the left side of the equation
          {
          Divider=StringSubstr(CheckedPair.LeftSide,i,1);
          if ( Divider == "^" || i == StringLen(CheckedPair.LeftSide) - 1 )
             {
             SubPair=StringSubstr(CheckedPair.LeftSide,ReadStartIterator+PrefixE,6);
             SubSide=StringSubstr(CheckedPair.LeftSideStructure,quantityiterator,1);
             Half1=StringSubstr(CheckedPair.LeftSide,ReadStartIterator+PrefixE,3);
             Half2=StringSubstr(CheckedPair.LeftSide,ReadStartIterator+PrefixE+3,3);         
    
             bNew=true;
             for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )//если такой валюты в списке нет, то добавляем ее туда
                {
                if ( BasicPairsLeft[j].Value == Half1 )
                   {
                   if ( SubSide == "u" ) BasicPairsLeft[j].Quantity++;
                   if ( SubSide == "d" ) BasicPairsLeft[j].Quantity--;
                   bNew = false;
                   break;
                   }
                }
             if ( bNew )
                {
                for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )//если такой валюты в списке нет, то добавляем ее туда
                   {
                   if ( StringLen(BasicPairsLeft[j].Value) == 0 )
                      {
                      if ( SubSide == "u" ) BasicPairsLeft[j].Quantity++;
                      if ( SubSide == "d" ) BasicPairsLeft[j].Quantity--;                  
                      BasicPairsLeft[j].Value=Half1;
                      break;
                      }
                   }            
                }
                
             bNew=true;
             for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )//если такой валюты в списке нет, то добавляем ее туда
                {
                if ( BasicPairsLeft[j].Value == Half2 )
                   {
                   if ( SubSide == "u" ) BasicPairsLeft[j].Quantity--;
                   if ( SubSide == "d" ) BasicPairsLeft[j].Quantity++;
                   bNew = false;
                   break;
                   }
                }
             if ( bNew )
                {
                for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )//если такой валюты в списке нет, то добавляем ее туда
                   {
                   if (  StringLen(BasicPairsLeft[j].Value) == 0 )
                      {
                      if ( SubSide == "u" ) BasicPairsLeft[j].Quantity--;
                      if ( SubSide == "d" ) BasicPairsLeft[j].Quantity++;                  
                      BasicPairsLeft[j].Value=Half2;
                      break;
                      }
                   }            
                }            
                      
             ReadStartIterator=i+1;
             quantityiterator++;
             }
          }
       ///end of left-side balance calculation   
          
       ////Calculate balance values for the right side
       quantityiterator=0;
       ReadStartIterator=0;
       for ( int i=ReadStartIterator; i<StringLen(CheckedPair.RightSide); i++ )//вынем базовые валюты из правой части уравнения
          {
          Divider=StringSubstr(CheckedPair.RightSide,i,1);
    
          if ( Divider == "^"|| i == StringLen(CheckedPair.RightSide) - 1 )
             {
             SubPair=StringSubstr(CheckedPair.RightSide,ReadStartIterator+PrefixE,6);
             SubSide=StringSubstr(CheckedPair.RightSideStructure,quantityiterator,1);
             Half1=StringSubstr(CheckedPair.RightSide,ReadStartIterator+PrefixE,3);
             Half2=StringSubstr(CheckedPair.RightSide,ReadStartIterator+PrefixE+3,3);         
    
             bNew=true;
             for ( int j=0; j<ArraySize(BasicPairsRight); j++ )//если такой валюты в списке нет, то добавляем ее туда
                {
                if ( BasicPairsRight[j].Value == Half1 )
                   {
                   if ( SubSide == "u" ) BasicPairsRight[j].Quantity++;
                   if ( SubSide == "d" ) BasicPairsRight[j].Quantity--;
                   bNew = false;
                   break;
                   }
                }
             if ( bNew )
                {
                for ( int j=0; j<ArraySize(BasicPairsRight); j++ )//если такой валюты в списке нет, то добавляем ее туда
                   {
                   if (  StringLen(BasicPairsRight[j].Value) == 0 )
                      {
                      if ( SubSide == "u" ) BasicPairsRight[j].Quantity++;
                      if ( SubSide == "d" ) BasicPairsRight[j].Quantity--;                  
                      BasicPairsRight[j].Value=Half1;
                      break;
                      }
                   }            
                }
                
             bNew=true;
             for ( int j=0; j<ArraySize(BasicPairsRight); j++ )//если такой валюты в списке нет, то добавляем ее туда
                {
                if ( BasicPairsRight[j].Value == Half2 )
                   {
                   if ( SubSide == "u" ) BasicPairsRight[j].Quantity--;
                   if ( SubSide == "d" ) BasicPairsRight[j].Quantity++;
                   bNew = false;
                   break;
                   }
                }
             if ( bNew )
                {
                for ( int j=0; j<ArraySize(BasicPairsRight); j++ )//если такой валюты в списке нет, то добавляем ее туда
                   {
                   if (  StringLen(BasicPairsRight[j].Value) == 0 )
                      {
                      if ( SubSide == "u" ) BasicPairsRight[j].Quantity--;
                      if ( SubSide == "d" ) BasicPairsRight[j].Quantity++;                  
                      BasicPairsRight[j].Value=Half2;
                      break;
                      }
                   }            
                }            
                      
             ReadStartIterator=i+1;
             quantityiterator++;
             }
          }
       ///end of right-side balance calculation      
     
       ///calculate the number of lower and upper currencies based on the received data from the previous block
       int LeftUpTotal=0;// the number of upper elements in the left part
       int LeftDownTotal=0;// the number of lower elements in the left part
       int RightUpTotal=0;// the number of upper elements in the right part
       int RightDownTotal=0;// the number of lower elements in the right part
       
       
       string LastUpLeft;
       string LastDownLeft;
       string LastUpRight;
       string LastDownRight;   
       for ( int i=0; i<ArraySize(BasicPairsLeft); i++ )
          {
          if ( BasicPairsLeft[i].Quantity > 0 && StringLen(BasicPairsLeft[i].Value) > 0 ) LeftUpTotal+=BasicPairsLeft[i].Quantity;
          if ( BasicPairsLeft[i].Quantity < 0 && StringLen(BasicPairsLeft[i].Value) > 0 ) LeftDownTotal-=BasicPairsLeft[i].Quantity;
          }
       for ( int i=0; i<ArraySize(BasicPairsRight); i++ )
          {
          if ( BasicPairsRight[i].Quantity > 0 && StringLen(BasicPairsRight[i].Value) > 0 ) RightUpTotal+=BasicPairsRight[i].Quantity;
          if ( BasicPairsRight[i].Quantity < 0 && StringLen(BasicPairsRight[i].Value) > 0 ) RightDownTotal-=BasicPairsRight[i].Quantity;
          }      
       ///
       ///check if both sides are equal
       if ( LeftUpTotal == 1 && LeftDownTotal == 1 && RightUpTotal == 1 && RightDownTotal == 1 )// there must be one pair in the upper and in the lower part of both sides of the equality, otherwise the formula is invalid
          {
          for ( int i=0; i<ArraySize(BasicPairsLeft); i++ )
             {
             if ( BasicPairsLeft[i].Quantity == 1 && StringLen(BasicPairsLeft[i].Value) > 0 ) LastUpLeft=BasicPairsLeft[i].Value;
             if ( BasicPairsLeft[i].Quantity == -1 && StringLen(BasicPairsLeft[i].Value) > 0 ) LastDownLeft=BasicPairsLeft[i].Value;
             }
          for ( int i=0; i<ArraySize(BasicPairsRight); i++ )
             {
             if ( BasicPairsRight[i].Quantity == 1 && StringLen(BasicPairsRight[i].Value) > 0 ) LastUpRight=BasicPairsRight[i].Value;
             if ( BasicPairsRight[i].Quantity == -1 && StringLen(BasicPairsRight[i].Value) > 0 ) LastDownRight=BasicPairsRight[i].Value;
             }      
          }
       else return false;
       if ( (LastUpLeft == LastUpRight && LastDownLeft == LastDownRight) || (LastUpLeft == LastDownRight && LastDownLeft == LastUpRight) )
          {
          if ( LastUpLeft == LastDownRight && LastDownLeft == LastUpRight )//если формула эквивалентна крест-накрест то перевернем структуру правой части уравнения (эквивалентно возведению в -1 степень)
             {
             string NewStructure;//новая структура, которую будем строить из предыдущей
             for ( int i=0; i<StringLen(CheckedPair.RightSideStructure); i++ )
                {
                if ( CheckedPair.RightSideStructure[i] == 'u' ) NewStructure+="d";
                if ( CheckedPair.RightSideStructure[i] == 'd' ) NewStructure+="u";
                }
             CheckedPair.RightSideStructure=NewStructure;
             }      
          }
       else return false;// if the resulting fractions on both sides are not equivalent, then the formula is invalid
       if ( LastUpLeft == LastDownLeft ) return false;// if result in one, then the formula is invalid
    
      /// Now it is necessary to write all the above into a corrected and more convenient structure
       string TempResult=CorrectedResultInstrument(LastUpLeft+LastDownLeft,r.IsResultInstrument);
       if ( r.IsResultInstrument && LastUpLeft+LastDownLeft != TempResult ) 
          {
          string NewStructure="";//новая структура, которую будем строить из предыдущей
          for ( int i=0; i<StringLen(CheckedPair.RightSideStructure); i++ )
             {
             if ( CheckedPair.RightSideStructure[i] == 'u' ) NewStructure+="d";
             if ( CheckedPair.RightSideStructure[i] == 'd' ) NewStructure+="u";
             }
          CheckedPair.RightSideStructure=NewStructure;
          NewStructure="";//новая структура, которую будем строить из предыдущей
          for ( int i=0; i<StringLen(CheckedPair.LeftSideStructure); i++ )
             {
             if ( CheckedPair.LeftSideStructure[i] == 'u' ) NewStructure+="d";
             if ( CheckedPair.LeftSideStructure[i] == 'd' ) NewStructure+="u";
             }
          CheckedPair.LeftSideStructure=NewStructure;      
            
          r.ResultInstrument=LastDownLeft+LastUpLeft;
          r.UpPair=LastDownLeft;
          r.DownPair=LastUpLeft;      
          }
       else
          {
          r.ResultInstrument=LastUpLeft+LastDownLeft;
          r.UpPair=LastUpLeft;
          r.DownPair=LastDownLeft;
          }
    
       r.LeftSide=CheckedPair.LeftSide;
       r.RightSide=CheckedPair.RightSide;
       r.LeftSideStructure=CheckedPair.LeftSideStructure;
       r.RightSideStructure=CheckedPair.RightSideStructure;
       ///   
        
       ///if code has reached this point, it is considered that we have found the formula meeting the criteria, and the next step is normalization
          
       return true;
       }
    

    Quizá mostraremos también la función resaltada en verde, pues es necesaria para determinar si la lista de instrumentos contiene aquel instrumento al que se ha reducido la fórmula. Puede resultar que la fórmula se haya reducido, por ejemplo, no a "USDJPY", sino a "JPYUSD". En este caso, estará claro que dicho instrumento no existe, aunque podría haber sido creado, claro, pero nuestra tarea consiste simplemente en corregir la fórmula para que se reduzca al instrumento correcto; para ello, solo necesitamos elevar ambas partes de la fórmula a "-1", lo cual equivale a invertir la estructura de la fórmula (cambiar "d" por "u", y viceversa). Bueno, si no existe tal instrumento en la ventana de Market Watch, podremos dejar todo como está:

    string CorrectedResultInstrument(string instrument, bool &bResult)//если сгенерированной формуле соответствует какой либо эквивалентный инструмент то вернем его (либо оставим без изменения)
       {   
       string Half1="";
       string Half2="";   
       string Half1input=StringSubstr(instrument,0,3);//input upper currency
       string Half2input=StringSubstr(instrument,3,3);//input lower currency
       bResult=false;
       for ( int j=0; j<ArraySize(Pairs); j++ )
          {
          Half1=StringSubstr(Pairs[j].Name,PrefixE,3);
          Half2=StringSubstr(Pairs[j].Name,PrefixE+3,3);
          if ( (Half1==Half1input && Half2==Half2input) || (Half1==Half2input && Half2==Half1input) )// direct match or crossed match
             {
             bResult=true;
             return Pairs[j].Name;
             }
          }
       
       return instrument;
       }
    

    Para guardar las fórmulas que han pasado la filtración, hemos creado la estructura que veremos a continuación. Algunos campos han migrado desde la anterior, pero también han aparecido otros nuevos:

    struct EquationCorrected // corrected structure of the basic formula
       {
       string LeftSide;//валютные пары, участвующие в формуле с левой стороны от знака "="
       string LeftSideStructure;// structure of the left side of the formula
       string RightSide;//валютные пары, участвующие в правой части формулы
       string RightSideStructure;// structure of the right side of the formula
       
       string ResultInstrument;// the resulting instrument to which both parts of the formula come after transformation
       bool IsResultInstrument;// has the suitable equivalent symbol been found
       string UpPair;// the upper currency of the resulting instrument
       string DownPair;// the lower currency of the resulting instrument
       };
    

    Normalización de fórmulas

    Este procedimiento supone el siguiente paso para filtrar los resultados, y contiene las siguientes operaciones secuenciales, que se suceden una tras otra:

    1. Usando como base el instrumento resultante, al que se reducen ambos lados de la ecuación, seleccionamos una pareja inicial de la lista para ambos lados de la igualdad.
    2. Ambas parejas, según el grado al que se encuentren en la ecuación, deberán posibilitar la presencia de la divisa básica en el numerador de la fracción.
    3. Después de encontrar dicha pareja, si la divisa más baja de la fracción no contiene la divisa principal del instrumento resultante, entonces continuaremos.
    4. Vamos a continuar de forma que la divisa superior de la siguiente pareja compense la divisa inferior de la anterior.
    5. Ejecutamos estos pasos hasta obtener el resultado deseado
    6. Después de encontrar el resultado, descartaremos los componentes restantes no usados en la fórmula (ya que su producto da como resultado la unidad)
    7. De paso, en este proceso se calculan secuencialmente los "coeficientes de lote" de una pareja a otra (muestran qué lote necesitamos para abrir posiciones para parejas específicas y conseguir nuestro instrumento resultante)
    8. El resultado se escribe en una nueva variable que se usará en la siguiente etapa del análisis.

    El código de esta función es el siguiente:

    bool bNormalized(EquationCorrected &d,EquationNormalized &v)//попытка нормализации формулы (нормализованную формулу вернем в "v")
       {
       double PreviousContract;// previous contract
       bool bWasPairs;// if any pairs have been found
       double BaseContract;// contract of the pair to which the equation is reduced
       double PreviousLotK=0.0;// previous LotK
       double LotK;// current LotK
       string PreviousSubSide;// in numerator or denominator (previous factor)
       string PreviousPair;// previous pair
       string PreviousHalf1;// upper currency of the previous pair
       string PreviousHalf2;// lower currency of the previous pair
       string SubPair;// the full name of the currency pair
       string Half1;// the first currency of the pair
       string Half2;// the second currency of the pair
       string SubSide;// the currency pair in the numerator or denominator
       string Divider;// separator
       int ReadStartIterator=0;// reading start index
       int quantityiterator=0;// quantity
       int tryiterator=0;// the number of balancing attempts
       int quantityleft=0;// the number of pairs on the left after normalization
       int quantityright=0;//the number of pairs on the right after normalization
       bool bNew;
       BasicValue b0;
       
       for ( int i=0; i<ArraySize(BasicPairsLeft); i++ )//reset the array of base pairs 
          {
          BasicPairsLeft[i].Value = "";
          BasicPairsLeft[i].Quantity = 0;
          }
       for ( int i=0; i<ArraySize(BasicPairsRight); i++ )// resetting the array of base pairs
          {
          BasicPairsRight[i].Value = "";
          BasicPairsRight[i].Quantity = 0;
          }
          
       if ( d.IsResultInstrument ) BaseContract=SymbolInfoDouble(d.ResultInstrument, SYMBOL_TRADE_CONTRACT_SIZE);// define the contract of the equivalent pair based on the instrument
       else BaseContract=100000.0;
          
       ////Calculate the number of pairs for the left side
       tryiterator=0;
       ReadStartIterator=0;
       for ( int i=ReadStartIterator; i<StringLen(d.LeftSide); i++ )// extract base currencies from the left side of the equation
          {
          Divider=StringSubstr(d.LeftSide,i,1);
          if ( Divider == "^" )
             {
             ReadStartIterator=i+1;
             tryiterator++;
             }
             
          if ( i == StringLen(d.LeftSide) - 1 )
             {
             ReadStartIterator=i+1;
             tryiterator++;
             }         
          }
       ///end of quantity calculation for the left part   
          
       ArrayResize(v.PairLeft,tryiterator);
       ///calculate the lot coefficients for the left side
       
       bool bBalanced=false;// is the formula balanced
       bool bUsed[];
       ArrayResize(bUsed,tryiterator);
       ArrayFill(bUsed,0,tryiterator,false);
       int balancediterator=0;
       PreviousHalf1="";
       PreviousHalf2="";
       PreviousLotK=0.0;
       PreviousSubSide="";
       PreviousPair="";
       PreviousContract=0.0;
       bWasPairs=false;// have there been pairs
       for ( int k=0; k<tryiterator; k++ )// try to normalize the left side
          {
          if( !bBalanced )
             {
             quantityiterator=0;
             ReadStartIterator=0;
             for ( int i=ReadStartIterator; i<StringLen(d.LeftSide); i++ )// extract base currencies from the left side of the equation
                {
                Divider=StringSubstr(d.LeftSide,i,1);
                if ( Divider == "^" || i == StringLen(d.LeftSide) - 1 )
                   {
                   SubPair=StringSubstr(d.LeftSide,ReadStartIterator+PrefixE,6);
                   SubSide=StringSubstr(d.LeftSideStructure,quantityiterator,1);
                   Half1=StringSubstr(d.LeftSide,ReadStartIterator+PrefixE,3);
                   Half2=StringSubstr(d.LeftSide,ReadStartIterator+PrefixE+3,3);         
                
                   if ( ! bUsed[quantityiterator] && (( PreviousHalf1 == "" && ((Half1 == d.UpPair && SubSide == "u") || (Half2 == d.UpPair && SubSide == "d")) ) // if it is the first pair in the list
                   || ( (( PreviousHalf2 == Half1 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half1 && PreviousSubSide == "d" )) && SubSide == "u" ) // if the current pair is in the numerator
                   || ( (( PreviousHalf2 == Half2 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half2 && PreviousSubSide == "d" )) && SubSide == "d" )) )// if the current pair is in the denominator
                      {// find the entry point(pair) of the chain
                      if( PreviousHalf1 == "" )// define the lot coefficient of the first pair
                         {
                         if ( SubSide == "u" )
                            {
                            LotK=BaseContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);// (1 start)
                            v.PairLeft[balancediterator].LotK=LotK;
                            PreviousLotK=LotK;
                            bWasPairs=true;
                            PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                            }
                         if ( SubSide == "d" )
                            {
                            double Pt=SymbolInfoDouble(SubPair,SYMBOL_BID);
                            if ( Pt == 0.0 ) return false; 
                            LotK=(BaseContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE))/Pt;// (2 start)
                            v.PairLeft[balancediterator].LotK=LotK;
                            PreviousLotK=LotK;
                            bWasPairs=true;
                            PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                            }                     
                         }
                      else
                         {
                         if( PreviousSubSide == "u" )// define the lot coefficient of further pairs
                            {
                            if ( SubSide == "u" )
                               {
                               double Pp=SymbolInfoDouble(PreviousPair,SYMBOL_BID);
                               if ( Pp == 0.0 ) return false;
                               if ( PreviousContract <= 0.0 ) return false; 
                               LotK=PreviousLotK*Pp*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// ( 1 )
                               v.PairLeft[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }
                            if ( SubSide == "d" )
                               {
                               double Pt=SymbolInfoDouble(SubPair,SYMBOL_BID);
                               double Pp=SymbolInfoDouble(PreviousPair,SYMBOL_BID);
                               if ( Pt == 0.0 ) return false; 
                               if ( Pp == 0.0 ) return false;
                               if ( PreviousContract <= 0.0 ) return false;                           
                               LotK=PreviousLotK*(Pp/Pt)*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// ( 2 )
                               v.PairLeft[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }                     
                            }
                         if( PreviousSubSide == "d" )// define the lot coefficient of further pairs
                            {
                            if ( SubSide == "u" )
                               {
                               if ( PreviousContract <= 0.0 ) return false;
                               LotK=PreviousLotK*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// ( 3 )
                               v.PairLeft[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }
                            if ( SubSide == "d" )
                               {
                               double Pt=SymbolInfoDouble(SubPair,SYMBOL_BID);
                               if ( Pt == 0.0 ) return false;
                               if ( PreviousContract <= 0.0 ) return false;
                               LotK=(PreviousLotK/Pt)*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// ( 4 )
                               v.PairLeft[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }                     
                            }                  
                         }
                                       
                      bNew=true;
                      for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )// if the currency is not found in the list, add it
                         {
                         if ( BasicPairsLeft[j].Value == Half1 )
                            {
                            if ( SubSide == "u" ) BasicPairsLeft[j].Quantity++;
                            if ( SubSide == "d" ) BasicPairsLeft[j].Quantity--;
                            bNew = false;
                            break;
                            }
                         }
                      if ( bNew )
                         {
                         for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )// if the currency is not found in the list, add it
                            {
                            if ( StringLen(BasicPairsLeft[j].Value) == 0 )
                               {
                               if ( SubSide == "u" ) BasicPairsLeft[j].Quantity++;
                               if ( SubSide == "d" ) BasicPairsLeft[j].Quantity--;                  
                               BasicPairsLeft[j].Value=Half1;
                               break;
                               }
                            }            
                         }
                
                      bNew=true;
                      for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )// if the currency is not found in the list, add it
                         {
                         if ( BasicPairsLeft[j].Value == Half2 )
                            {
                            if ( SubSide == "u" ) BasicPairsLeft[j].Quantity--;
                            if ( SubSide == "d" ) BasicPairsLeft[j].Quantity++;
                            bNew = false;
                            break;
                            }
                         }
                      if ( bNew )
                         {
                         for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )// if the currency is not found in the list, add it
                            {
                            if (  StringLen(BasicPairsLeft[j].Value) == 0 )
                               {
                               if ( SubSide == "u" ) BasicPairsLeft[j].Quantity--;
                               if ( SubSide == "d" ) BasicPairsLeft[j].Quantity++;                  
                               BasicPairsLeft[j].Value=Half2;
                               break;
                               }
                            }            
                         }
                      
                      v.PairLeft[balancediterator].Name=SubPair;
                      v.PairLeft[balancediterator].Side=SubSide;
                      v.PairLeft[balancediterator].ContractSize=SymbolInfoDouble(v.PairLeft[balancediterator].Name, SYMBOL_TRADE_CONTRACT_SIZE);
                   
    
                      balancediterator++;                  
                      PreviousHalf1=Half1;
                      PreviousHalf2=Half2;
                      PreviousSubSide=SubSide;
                      PreviousPair=SubPair;
                      
                      quantityleft++;
                      if ( SubSide == "u" && Half2 == d.DownPair )// if the fraction is not inverted
                         {
                         bBalanced=true;// if the missing part is in the denominator, then we have balanced the formula
                         break;// since the formula is balanced, we don't need the rest
                         }
                      if ( SubSide == "d" && Half1 == d.DownPair )// if the fraction is inverted
                         {
                         bBalanced=true;// if the missing part is in the numerator, then we have balanced the formula
                         break;// since the formula is balanced, we don't need the rest
                         }
                      
                      int LeftUpTotal=0;// the number of upper elements in the left part
                      int LeftDownTotal=0;// the number of lower elements in the left part
                      string LastUpLeft;
                      string LastDownLeft;
                      for ( int z=0; z<ArraySize(BasicPairsLeft); z++ )
                         {
                         if ( BasicPairsLeft[z].Quantity > 0 && StringLen(BasicPairsLeft[z].Value) > 0 ) LeftUpTotal+=BasicPairsLeft[z].Quantity;
                         if ( BasicPairsLeft[z].Quantity < 0 && StringLen(BasicPairsLeft[z].Value) > 0 ) LeftDownTotal-=BasicPairsLeft[z].Quantity;
                         }
                      if ( bWasPairs && LeftUpTotal == 0 && LeftDownTotal == 0 ) return false;                                           
                      }
    
                   ReadStartIterator=i+1;
                   bUsed[quantityiterator]=true;
                   quantityiterator++;
                   }
                }
             }
             else break;
          }
       ///end of coefficient calculation for the left part       
         
       if ( !bBalanced ) return false;// if the left side is not balanced, then there is no point in balancing the right side
         
       ////Calculate the number of pairs for the right side
       tryiterator=0;
       ReadStartIterator=0;
       for ( int i=ReadStartIterator; i<StringLen(d.RightSide); i++ )// extract base currencies from the right side of the equation
          {
          Divider=StringSubstr(d.RightSide,i,1);
          if ( Divider == "^" )
             {
             ReadStartIterator=i+1;
             tryiterator++;
             }
             
          if ( i == StringLen(d.RightSide) - 1 )
             {
             ReadStartIterator=i+1;
             tryiterator++;
             }         
          }   
       ArrayResize(v.PairRight,tryiterator);
       /// end of calculation of the number of pairs for the right side  
         
       bBalanced=false;// is the formula balanced
       ArrayResize(bUsed,tryiterator);
       ArrayFill(bUsed,0,tryiterator,false);
       balancediterator=0;
       PreviousHalf1="";
       PreviousHalf2="";
       PreviousLotK=0.0;
       PreviousSubSide="";
       PreviousPair="";
       PreviousContract=0.0;
       bWasPairs=false;
       for ( int k=0; k<tryiterator; k++ )// try to normalize the right side
          {
          if ( !bBalanced )
             {
             quantityiterator=0;
             ReadStartIterator=0;
             for ( int i=ReadStartIterator; i<StringLen(d.RightSide); i++ )// extract base currencies from the right side of the equation
                {
                Divider=StringSubstr(d.RightSide,i,1);
                if ( Divider == "^" || i == StringLen(d.RightSide) - 1 )
                   {
                   SubPair=StringSubstr(d.RightSide,ReadStartIterator+PrefixE,6);
                   SubSide=StringSubstr(d.RightSideStructure,quantityiterator,1);
                   Half1=StringSubstr(d.RightSide,ReadStartIterator+PrefixE,3);
                   Half2=StringSubstr(d.RightSide,ReadStartIterator+PrefixE+3,3);         
                
                   if ( ! bUsed[quantityiterator] && (( PreviousHalf1 == "" && ((Half1 == d.UpPair && SubSide == "u") || (Half2 == d.UpPair && SubSide == "d")) ) // if it is the first pair in the list
                   || ( (( PreviousHalf2 == Half1 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half1 && PreviousSubSide == "d" )) && SubSide == "u" ) // if the current pair is in the numerator
                   || ( (( PreviousHalf2 == Half2 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half2 && PreviousSubSide == "d" )) && SubSide == "d" )) )// if the current pair is in the denominator
                      {// find the entry point(pair) of the chain
                      if( PreviousHalf1 == "" )// define the lot coefficient of the first pair
                         {
                         if ( SubSide == "u" )
                            {
                            LotK=BaseContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);// (1 start)
                            v.PairRight[balancediterator].LotK=LotK;
                            PreviousLotK=LotK;
                            bWasPairs=true;
                            PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                            }
                         if ( SubSide == "d" )
                            {
                            double Pt=SymbolInfoDouble(SubPair,SYMBOL_BID);
                            if ( Pt == 0.0 ) return false; 
                            LotK=(BaseContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE))/Pt;// (2 start)
                            v.PairRight[balancediterator].LotK=LotK;
                            PreviousLotK=LotK;
                            bWasPairs=true;
                            PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                            }                     
                         }
                      else
                         {
                         if( PreviousSubSide == "u" )// define the lot coefficient of further pairs
                            {
                            if ( SubSide == "u" )
                               {
                               double Pp=SymbolInfoDouble(PreviousPair,SYMBOL_BID);
                               if ( Pp == 0.0 ) return false;
                               if ( PreviousContract <= 0.0 ) return false;                            
                               LotK=PreviousLotK*Pp*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// (1)
                               v.PairRight[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }
                            if ( SubSide == "d" )
                               {
                               double Pt=SymbolInfoDouble(SubPair,SYMBOL_BID);
                               double Pp=SymbolInfoDouble(PreviousPair,SYMBOL_BID);
                               if ( Pt == 0.0 ) return false; 
                               if ( Pp == 0.0 ) return false;
                               if ( PreviousContract <= 0.0 ) return false;                            
                               LotK=PreviousLotK*(Pp/Pt)*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// (2)
                               v.PairRight[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }                     
                            }
                         if( PreviousSubSide == "d" )// define the lot coefficient of further pairs
                            {
                            if ( SubSide == "u" )
                               {
                               if ( PreviousContract <= 0.0 ) return false;
                               LotK=PreviousLotK*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// (3)
                               v.PairRight[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }
                            if ( SubSide == "d" )
                               {
                               double Pt=SymbolInfoDouble(SubPair,SYMBOL_BID);
                               if ( Pt == 0.0 ) return false;
                               if ( PreviousContract <= 0.0 ) return false; 
                               LotK=(PreviousLotK/Pt)*(PreviousContract/SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE));// (4)
                               v.PairRight[balancediterator].LotK=LotK;
                               PreviousLotK=LotK;
                               PreviousContract=SymbolInfoDouble(SubPair, SYMBOL_TRADE_CONTRACT_SIZE);
                               }                     
                            }                  
                         }                
                      bNew=true;
                      for ( int j=0; j<ArraySize(BasicPairsRight); j++ )// if the currency is not found in the list, add it
                         {
                         if ( BasicPairsRight[j].Value == Half1 )
                            {
                            if ( SubSide == "u" ) BasicPairsRight[j].Quantity++;
                            if ( SubSide == "d" ) BasicPairsRight[j].Quantity--;
                            bNew = false;
                            break;
                            }
                         }
                      if ( bNew )
                         {
                         for ( int j=0; j<ArraySize(BasicPairsLeft); j++ )// if the currency is not found in the list, add it
                            {
                            if ( StringLen(BasicPairsLeft[j].Value) == 0 )
                               {
                               if ( SubSide == "u" ) BasicPairsRight[j].Quantity++;
                               if ( SubSide == "d" ) BasicPairsRight[j].Quantity--;                  
                               BasicPairsRight[j].Value=Half1;
                               break;
                               }
                            }            
                         }
                
                      bNew=true;
                      for ( int j=0; j<ArraySize(BasicPairsRight); j++ )// if the currency is not found in the list, add it
                         {
                         if ( BasicPairsRight[j].Value == Half2 )
                            {
                            if ( SubSide == "u" ) BasicPairsRight[j].Quantity--;
                            if ( SubSide == "d" ) BasicPairsRight[j].Quantity++;
                            bNew = false;
                            break;
                            }
                         }
                      if ( bNew )
                         {
                         for ( int j=0; j<ArraySize(BasicPairsRight); j++ )// if the currency is not found in the list, add it
                            {
                            if (  StringLen(BasicPairsRight[j].Value) == 0 )
                               {
                               if ( SubSide == "u" ) BasicPairsRight[j].Quantity--;
                               if ( SubSide == "d" ) BasicPairsRight[j].Quantity++;                  
                               BasicPairsRight[j].Value=Half2;
                               break;
                               }
                            }            
                         }
                      
                      v.PairRight[balancediterator].Name=SubPair;
                      v.PairRight[balancediterator].Side=SubSide;
                      v.PairRight[balancediterator].ContractSize=SymbolInfoDouble(v.PairRight[balancediterator].Name, SYMBOL_TRADE_CONTRACT_SIZE);
    
    
                      balancediterator++;                  
                      PreviousHalf1=Half1;
                      PreviousHalf2=Half2;
                      PreviousSubSide=SubSide;
                      PreviousPair=SubPair;
                   
                      quantityright++;
                      if ( SubSide == "u" && Half2 == d.DownPair )// if the fraction is not inverted
                         {
                         bBalanced=true;// if the missing part is in the denominator, then we have balanced the formula
                         break;// since the formula is balanced, we don't need the rest
                         }
                      if ( SubSide == "d" && Half1 == d.DownPair )// if the fraction is inverted
                         {
                         bBalanced=true;// if the missing part is in the numerator, then we have balanced the formula
                         break;// since the formula is balanced, we don't need the rest
                         }
                         
                      int RightUpTotal=0;// the number of upper elements in the right part
                      int RightDownTotal=0;// the number of lower elements in the right part
                      string LastUpRight;
                      string LastDownRight; 
                        
                      for ( int z=0; z<ArraySize(BasicPairsRight); z++ )
                         {
                         if ( BasicPairsRight[z].Quantity > 0 && StringLen(BasicPairsRight[z].Value) > 0 ) RightUpTotal+=BasicPairsRight[z].Quantity;
                         if ( BasicPairsRight[z].Quantity < 0 && StringLen(BasicPairsRight[z].Value) > 0 ) RightDownTotal-=BasicPairsRight[z].Quantity;
                         }
                      if ( bWasPairs && RightUpTotal == 0 && RightDownTotal == 0 ) return false;                                       
                      }
    
                   ReadStartIterator=i+1;
                   bUsed[quantityiterator]=true;
                   quantityiterator++;
                   }
                }
             }
             else break;
          }     
       
       if ( quantityleft == 1 && quantityright == 1 ) return false;// if the equation has been normalized to only 2 pairs, it is not valid (at least 3 pairs are required)
          
       return bBalanced;
       }
    

    Podemos entender que este es un procedimiento muy complejo y voluminoso, pero parece que en tales casos resulta mejor no producir estados intermedios, porque esto provocará una duplicación significativa del código, que ya de por sí es más que suficiente. Además, todas las etapas se han hecho lo más compactas posible y se han dividido en bloques en la medida en que la lógica lo permite. Lo principal es que el conjunto de estas funciones nos dé resultados absolutamente idénticos a los que tendríamos si realizáramos todas estas transformaciones en nuestros cuadernos. En este caso, efectuaremos todas estas manipulaciones matemáticas usando un conjunto de varios métodos complejos, pero necesarios.

    Para entender cuán rentable es la fórmula que hemos encontrado, será necesario analizarla de forma especial. No olvidemos que podemos abrir para cada pareja una posición tanto hacia arriba como hacia abajo, y que, por consiguiente, esto posibilitará que de cada fórmula pueda obtener dos variantes intermedias de los circuitos: una directa y otra inversa. Tomaremos como resultado el circuito cuyo indicador de rentabilidad sea mayor.

    Como indicador de rentabilidad, hemos creado un indicador equivalente al factor de beneficio, compuesto únicamente por los beneficios y las pérdidas surgidos como resultado de los swaps. Si el swap positivo cobrado del circuito existente es superior al negativo según el módulo, este circuito se considerará rentable. En los demás casos, dichos circuitos no resultarán rentables; en otras palabras, el factor de swap de nuestro circuito será positivo solo cuando sea mayor que uno.

    El resultado retornado ya está escrito en un contenedor completamente distinto, que hemos creado para su uso futuro como un paquete de comandos autosuficiente para comerciar y desarrollar la lógica comercial futura. Contiene todo lo necesario para abrir la ruta completa de forma rápida y sencilla:

    struct EquationNormalized // the final structure with the formula in normalized form
       {
       Pair PairLeft[];// currency pairs on the left side
       Pair PairRight[];// currency pairs on the right side
       double SwapPlusRelative;// relative equivalent of the positive swap
       double SwapMinusRelative;// relative equivalent of the negative swap
       double SwapFactor;// resulting swap factor
       };
    

    Además, hemos añadido allí 2 métodos para mostrar de la forma adecuada la información sobre el contenido en el marco del presente artículo; no obstante, parece que aquí resultan inapropiados e inútiles, y no los mostraremos. El lector podrá verlo todo en los archivos fuente. Ahora, la información sobre cada componente de la ecuación se contiene por separado como elementos de matrices. Así será más fácil trabajar con la información más adelante y no tendremos que parsear dichos elementos a partir de líneas. Podría haber merecido la pena hacerlo directamente, pero luego la legibilidad se vería afectada.

    Calculando el factor de swap y realizando el ajuste final de la estructura de la ecuación

    Esta es la última etapa. En ella, se analizará el indicador más importante de este sistema, que usaremos para comparar nuestras opciones. La opción que tenga este indicador más alto, será la mejor.

    void CalculateBestVariation(EquationNormalized &ii)// calculation of the best swap factor of the formula and final structure adjustment if needed
       {
       double SwapMinus=0.0;// total negative swap
       double SwapPlus=0.0;// total positive swap
       double SwapMinusReverse=0.0;// total negative swap
       double SwapPlusReverse=0.0;// total positive swap
       
       double SwapFactor=0.0;// swap factor of the direct pass
       double SwapFactorReverse=0.0;// swap factor of the reverse pass
       
       for ( int i=0; i<ArraySize(ii.PairLeft); i++ )// define the missing parameters for calculating the left side
          {
          for ( int j=0; j<ArraySize(Pairs); j++ )
             {
             if ( Pairs[j].Name == ii.PairLeft[i].Name )
                {
                ii.PairLeft[i].Margin=Pairs[j].Margin;
                ii.PairLeft[i].TickValue=Pairs[j].TickValue;
                ii.PairLeft[i].SwapBuy=Pairs[j].SwapBuy;
                ii.PairLeft[i].SwapSell=Pairs[j].SwapSell;
                break;
                }
             }
          }
          
       for ( int i=0; i<ArraySize(ii.PairRight); i++ )// define the missing parameters for calculating the right side
          {
          for ( int j=0; j<ArraySize(Pairs); j++ )
             {
             if ( Pairs[j].Name == ii.PairRight[i].Name )
                {
                ii.PairRight[i].Margin=Pairs[j].Margin;
                ii.PairRight[i].TickValue=Pairs[j].TickValue;
                ii.PairRight[i].SwapBuy=Pairs[j].SwapBuy;
                ii.PairRight[i].SwapSell=Pairs[j].SwapSell;
                break;
                }
             }
          }      
       
       double TempSwap;
       //calculate all components taking into account a change in the structure
       for ( int i=0; i<ArraySize(ii.PairLeft); i++ )// for left parts
          {
          if ( ii.PairLeft[i].Side == "u" )
             {// for direct trading
             TempSwap=ii.PairLeft[i].SwapBuy*ii.LotKLeft[i];
             if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
             else SwapMinus-=TempSwap;
             //for reverse trading
             TempSwap=ii.PairLeft[i].SwapSell*ii.LotKLeft[i];
             if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
             else SwapMinusReverse-=TempSwap;         
             }
          if ( ii.PairLeft[i].Side == "d" )
             {// for direct trading
             TempSwap=ii.PairLeft[i].SwapSell*ii.LotKLeft[i];
             if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
             else SwapMinus-=TempSwap;
             //for reverse trading
             TempSwap=ii.PairLeft[i].SwapBuy*ii.LotKLeft[i];
             if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
             else SwapMinusReverse-=TempSwap;         
             }
          }
          
       for ( int i=0; i<ArraySize(ii.PairRight); i++ )// for right parts
          {
          if ( ii.PairRight[i].Side == "d" )
             {// for direct trading
             TempSwap=ii.PairRight[i].SwapBuy*ii.LotKRight[i];
             if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
             else SwapMinus-=TempSwap;
             //for reverse trading
             TempSwap=ii.PairRight[i].SwapSell*ii.LotKRight[i];
             if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
             else SwapMinusReverse-=TempSwap;         
             }
          if ( ii.PairRight[i].Side == "u" )
             {// for direct trading
             TempSwap=ii.PairRight[i].SwapSell*ii.LotKRight[i];
             if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
             else SwapMinus-=TempSwap;
             //for reverse trading
             TempSwap=ii.PairRight[i].SwapBuy*ii.LotKRight[i];
             if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
             else SwapMinusReverse-=TempSwap;         
             }
          }   
       //calculate the swap factor for the direct pass
       if ( SwapMinus > 0.0 && SwapPlus > 0.0 ) SwapFactor=SwapPlus/SwapMinus;
       if ( SwapMinus == 0.0 && SwapPlus == 0.0 ) SwapFactor=1.0;
       if ( SwapMinus == 0.0 && SwapPlus > 0.0 ) SwapFactor=1000001.0;
       if ( SwapMinus > 0.0 && SwapPlus == 0.0 ) SwapFactor=0.0;
       //calculate the swap factor for the reverse pass
       if ( SwapMinusReverse > 0.0 && SwapPlusReverse > 0.0 ) SwapFactorReverse=SwapPlusReverse/SwapMinusReverse;
       if ( SwapMinusReverse == 0.0 && SwapPlusReverse == 0.0 ) SwapFactorReverse=1.0;
       if ( SwapMinusReverse == 0.0 && SwapPlusReverse > 0.0 ) SwapFactorReverse=1000001.0;
       if ( SwapMinusReverse > 0.0 && SwapPlusReverse == 0.0 ) SwapFactorReverse=0.0;
       //select the best approach and calculate the missing values in the structure
       if ( SwapFactor > SwapFactorReverse )
          {
          ii.SwapPlusRelative=SwapPlus;
          ii.SwapMinusRelative=SwapMinus;
          ii.SwapFactor=SwapFactor;
          }
       else
          {
          ii.SwapPlusRelative=SwapPlusReverse;
          ii.SwapMinusRelative=SwapMinusReverse;
          ii.SwapFactor=SwapFactorReverse;
          bool bSigned;
          for ( int i=0; i<ArraySize(ii.PairRight); i++ )// if it is a reverse pass, then reverse the right structure of the formula
             {
             bSigned=false;
             if ( !bSigned && ii.PairRight[i].Side == "u" ) 
                {
                ii.PairRight[i].Side="d";
                bSigned=true;
                }
             if ( !bSigned && ii.PairRight[i].Side == "d" ) 
                {
                ii.PairRight[i].Side="u";
                bSigned=true;
                }
             }
          bSigned=false;    
          for ( int i=0; i<ArraySize(ii.PairLeft); i++ )// if it is a reverse pass, then reverse the left structure of the formula
             {
             bSigned=false;
             if ( !bSigned && ii.PairLeft[i].Side == "u" ) 
                {
                ii.PairLeft[i].Side="d";
                bSigned=true;
                }
             if ( !bSigned && ii.PairLeft[i].Side == "d" ) 
                {
                ii.PairLeft[i].Side="u";
                bSigned=true;
                }
             }              
          }
          
       bool bSigned;
       for ( int i=0; i<ArraySize(ii.PairRight); i++ )// reverse the right side anyway
          {
          bSigned=false;
          if ( !bSigned && ii.PairRight[i].Side == "u" ) 
             {
             ii.PairRight[i].Side="d";
             bSigned=true;
             }
          if ( !bSigned && ii.PairRight[i].Side == "d" ) 
             {
             ii.PairRight[i].Side="u";
             bSigned=true;
             }
          }
       }
    

    Para mostrar los resultados secuencialmente, hemos previsto especialmente un log que se escribe solo si se encuentra una variante de la fórmula que haya superado la comprobación del filtro. El registro tendrá el aspecto siguiente:

    Log

    Aquí, marcamos en rojo el instrumento resultante, al que se reducen ambos lados de la ecuación. La siguiente línea es la variante ya normalizada con los coeficientes de lote. La tercera línea es la variante con el factor de swap calculado. Bueno, y la cuarta línea es la mejor de las opciones encontradas durante la sesión de fuerza bruta, que también se ha dibujado utilizando la función Comment. Este prototipo se adjuntará al artículo, por lo que el lector podrá experimentar con él. De esta forma, en la práctica, es ya un prototipo de asistente comercial para el comercio con swaps. La funcionalidad es mínima, también sus capacidades, pero en el próximo artículo intentaremos ampliarlas. El prototipo se presenta en dos versiones, para ambos terminales: MetaTrader 4 y MetaTrader 5.


    Conclusiones de las primeras pruebas

    En solitorio, resulta bastante difícil sacar conclusiones sobre un tema tan complejo, pero sí que hemos logrado comprender algo útil, aunque hasta ahora no hayamos podido encontrar un factor de swap superior a la unidad. Estas son las primeras conclusiones extraídas al analizar el funcionamiento de este prototipo:

    • En algunas parejas de divisas, podemos aumentar los swaps positivos o reducir los swaps negativos (gracias a la representación de la posición como un equivalente sintético)
    • Incluso si no fuera posible encontrar un circuito rentable, una de sus partes siempre se podrá usar como una posición alternativa para bloquear dos cuentas comerciales diferentes.
    • El bloqueo con semejante posición sintética podría evitarnos la necesidad de usar cuentas Swap Free, ya que resulta posible conseguir un swap opuesto positivo en ambos extremos.
    • Debemos generar un análisis mejor y más profundo para la mayor cantidad posible de los brókeres populares, y esto requerirá expandir la funcionalidad.
    • Esperamos poder demostrar que es posible conseguir un factor de swap rentable (pero esto es solo una suposición hasta ahora)
    • Los swaps pueden ofrecer beneficios pequeños pero constantes si se usan con prudencia

    Conclusión

    Esperamos que este enfoque le resulte interesante a alguien o que al menos (esto también sería muy positivo) le dé material para pensar. El método es muy difícil de entender, pero, en realidad, implementa un sencillo principio para abrir dos posiciones opuestas con el mismo volumen. Abrir estas dos posiciones opuestas directamente implica casi siempre pérdidas garantizadas. No encontraremos ningún bróker en el que un swap unidireccional positivo sea mayor según el módulo que un swap unidireccional negativo. Y más aún, nunca encontraremos situaciones en las que el swap sea positivo en ambas direcciones, porque esto resulta físicamente imposible debido a las matemáticas del cálculo.

    No entraremos en detalles respecto a estas matemáticas, ya que requerirían un artículo completo. Ahora y en el futuro, resulta mejor trabajar con manifestaciones de estas matemáticas. Aplicando este método, podemos tanto reducir las pérdidas derivadas del swap al bloquear posiciones, como intentar encontrar un hueco en las tablas de swap de los brókeres y asegurarnos por completo el bloqueo con un factor de beneficio positivo (swap total rentable de todas las posiciones), posibilitando un trading sin riesgos, así como la independencia respecto a las fluctuaciones de precio.

    En opinión del autor, los métodos de comercio con swaps están muy infravalorados, ya que un swap positivo supone un beneficio potencial para nosotros. Este método es solo una de las posibles variaciones de los métodos de comercio con swaps. En los próximos artículos, intentaremos desarrollar la idea, modernizar el código y, por supuesto, escribir nuevas funcionalidades adicionales, así como prestar atención a las cuestiones relacionadas con el pronóstico de beneficios y la escritura de la funcionalidad comercial.

    Traducción del ruso hecha por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/ru/articles/9198

    Archivos adjuntos |
    Prototype.zip (20.52 KB)
    Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico Otras clases en la biblioteca DoEasy (Parte 70): Ampliación de la funcionalidad y actualización automática de la colección de objetos de gráfico
    En este artículo, ampliaremos la funcionalidad de los objetos de gráfico, organizaremos la navegación por los gráficos, crearemos capturas de pantalla, y también guardaremos plantillas y las aplicaremos a los gráficos. Asimismo, implementaremos la actualización automática de la colección de objetos de gráfico, sus ventanas y los indicadores en ellas.
    Consejos de un programador profesional (parte I): guardado, depuración y compilación de códigos. Trabajando con proyectos y logs Consejos de un programador profesional (parte I): guardado, depuración y compilación de códigos. Trabajando con proyectos y logs
    Consejos de un programador profesional sobre métodos, técnicas y herramientas auxiliares para facilitar la programación.
    Otras clases en la biblioteca DoEasy (Parte 71): Eventos de la colección de objetos de gráfico Otras clases en la biblioteca DoEasy (Parte 71): Eventos de la colección de objetos de gráfico
    En el presente artículo, crearemos la funcionalidad necesaria para monitorear algunos eventos de los objetos del gráfico: añadir y eliminar gráficos de símbolos, añadir y eliminar subventanas en el gráfico, y también añadir/eliminar/cambiar indicadores en las ventanas del gráfico.
    Otras clases en la biblioteca DoEasy (Parte 69): Clases de colección de objetos de gráfico Otras clases en la biblioteca DoEasy (Parte 69): Clases de colección de objetos de gráfico
    A partir de este artículo, comenzaremos el desarrollo de una colección de clases de objetos de gráfico que almacenará una colección de lista de objetos de gráfico con sus subventanas y los indicadores en ellas, y nos permitirá trabajar con cualquier gráfico seleccionado y sus subventanas, o bien directamente con una lista de varios gráficos al mismo tiempo.