Swaps (Parte I): bloqueio e posições sintéticas

Evgeniy Ilin | 6 julho, 2021

Sumário

Introdução

Há muito tempo que tinha na ponta minha língua o tópico deste artigo, e também não tinha tempo para abordá-lo devidamente. O tema dos swaps é bastante difundido na Internet, principalmente entre os profissionais de trading que já estão acostumados a contar cada ponto, o que na verdade é a melhor abordagem de negociação. Neste artigo, você não apenas aprenderá que os swaps devem ser considerados sempre, mas também entenderá como usá-los para o bem, e os mais curiosos receberão uma ideia muito complexa, mas interessante para modernizar os métodos de negociação de swap, que, se bem trabalhados, podem ser usados a nível de conta de negociação e de aumento de lucro para o bloqueio clássico usando duas contas.


Falemos sobre swaps

Não vou falar o que é uma swap nem me aprofundar na teoria. Eu só estou interessado na sua aplicação. A questão mais importante é se é possível ganhar dinheiro com swaps. Do ponto de vista do trader, um swap é um lucro ou uma perda, e muitos nem o levam em consideração só porque negociam dentro dos limites do dia. Outros simplesmente tentam ignorá-lo pensando em vão que sua influência é tão insignificante que praticamente não afetará os trades. Na verdade, quase metade do spread pode ficar oculto no swap, e tal spread só é retirado não no momento da compra ou venda, mas, sim, no momento da mudança do dia de acordo com o servidor.

O swap é cobrado proporcionalmente ao volume da posição aberta e ocorre:

  1. De segunda a terça
  2. De terça a quarta
  3. De quarta a quinta-feira (quase todas as corretoras cobram um swap triplo nesta noite)
  4. De quinta a sexta

Normalmente, o swap é especificado para o instrumento de negociação em pontos ou como uma porcentagem. Claro, existem outros métodos de cálculo, mas usei apenas dois até agora, e isso chega. Há muito pouca informação estruturada sobre este assunto, mas se mergulharmos, descobriremos que até existem estratégias funcionais baseadas em swaps. Embora tenham juros mínimos, a garantia de lucro é absoluta. A principal dificuldade dessa abordagem é que as corretoras mais populares têm muito poucos instrumentos com um swap positivo para lucrar. Se for possível ganhar algo, então serão só alguns centavos. No entanto, isso é melhor do que estourar completamente o depósito, e se você usar outro sistema de negociação, provavelmente o perderá. 

O resultado das minhas conclusões sobre a negociação Forex é que, além de swaps positivos, nada pode garantir lucro. É claro que existem sistemas capazes de gerar lucro, mas, ao usá-los, concordamos deliberadamente que, para qualquer operação de negociação, entregamos gradualmente nosso dinheiro à corretora, alimentando a ilusão de que o preço irá em nossa direção. Um swap positivo é o processo reverso. Os marcadores de negociação na direção de um swap positivo, na minha opinião, são os seguintes:

Claro, a maior desvantagem dessa abordagem é a dependência do volume do depósito, mas, ao mesmo tempo, nenhum outro conceito é capaz de garantir com tanta segurança o lucro no Forex. Essa dependência pode ser reduzida diminuindo o volume de posições abertas ou o risco, que é o mesmo. O risco é a relação entre o volume da nossa posição e o nosso depósito, porque à medida que aumenta, arriscamos cada vez mais que o preço pode ir para o lado negativo e que o depósito pode não ser suficiente para esperar o lucro vindo dos swaps e compensar nossas perdas graças aos spreads e comissões. Para minimizar a influência de todos os possíveis efeitos negativos, foi inventado um mecanismo de bloqueio, que discutiremos a seguir.


Bloqueio usando duas contas de negociação

Este método de negociação de swap é o mais popular entre os traders. Para implementar essa estratégia, são necessárias duas contas com swaps diferentes para os mesmos pares de moedas ou outros instrumentos de negociação. Tal passo é necessário, uma vez que abrir duas posições opostas dentro da mesma conta não faz sentido, pois equivale a simplesmente estourar o depósito porque mesmo que o instrumento tenha um swap positivo, ao negociar na direção oposta, ele é sempre negativo. Criei um diagrama que reflete claramente o conceito deste método:

Classic Swap Trading

Como se pode ver no diagrama, para o instrumento selecionado para negociarmos swaps, existem apenas 10 cenários de negociação, 6 dos quais são usados ativamente. As últimas quatro opções podem ser escolhidas como último recurso, se for impossível encontrar um par de moedas que se enquadre nas condições "1-6", uma vez que um dos swaps, neste caso, acaba sendo negativo, e simplesmente temos uma situação em que o swap positivo é maior do que o negativo. Todas essas situações estão presentes se considerarmos o grande número de corretoras e analisarmos suas tabelas de swap. Mas ao usar essa estratégia, as melhores opções para nós serão "2" e "5". Acontece que nelas existem swaps positivos em ambas as pontas, assim, na hora de operar, teremos um grande lucro com ambas as corretoras, além disso, não é frequente termos que transferir fundos de uma conta para outra.

A principal desvantagem é a necessidade de transferir dinheiro constantemente de uma conta para outra, pois ao abrir posições opostas em uma corretora haverá prejuízo e na outra lucro, mas se calcularmos corretamente os volumes de transações feito em relação ao depósito existente, isso não precisará ser feito com muita frequência. Mas a vantagem indiscutível é que em qualquer caso teremos lucro e poderemos prever seu valor exato. Acho que muitos gostariam de se livrar dessa rotina e de alguma forma realizar tudo isso dentro de uma conta (o que por definição é impossível), mas há uma maneira de você, se impossível trabalhar numa conta, pelo menos, aumentar o lucro do método clássico de negociação de swaps. As principais condições deste método serão discutidas a seguir.


Taxas de câmbio

Vamos começar com o mais importante, com a base de toda lógica para construir equações matemáticas. Por exemplo, vou pegar pares de moedas como EURUSD, USDJPY, EURJPY. Todos esses 3 pares estão conectados. Para entender a relação, precisamos apresentar essas ferramentas de uma forma ligeiramente diferente:

Na verdade, cada instrumento tem sempre aquela moeda (ou algo que a substitui) que adquirimos e tem uma que transferimos. Por exemplo, se você pegar a primeira relação (par EURUSD), quando abrimos uma posição de 1 lote em "Buy", adquirimos 100 000 unidades da moeda base. Essas são as regras no mercado Forex, um lote é sempre igual a 100 000 unidades da moeda base. Neste par, a moeda base é "EUR", então compramos "EUR" por "USD". A taxa de câmbio "P", neste caso, significa quantas unidades de "USD" estão contidas em 1 "EUR". Para o resto dos instrumentos é igual, a moeda base está contida no numerador da fração, e no denominador, não sei nem como chamar essa moeda, vou nomeá-la "moeda principal", se alguém achar importante, corrija nos comentários do artigo. A quantidade de moeda base é calculada simplesmente multiplicando o preço pelo valor "EUR":

Se abrirmos uma posição de venda, as moedas parecem mudar de lugar. A moeda base passa a desempenhar o papel da moeda principal e vice-versa. Em outras palavras, já adquirimos "USD" por "EUR", mas a quantidade de dinheiro em ambas as moedas é calculada da mesma forma em relação a "EUR". Isso é assim, porque, do contrário, haveria muita confusão. Para as outras moedas tudo é calculado da mesma maneira, só que se uma moeda desempenha o papel de base, nós a consideraremos com um sinal "+", se desempenha o papel principal, iremos considerá-la com um sinal "-". Como resultado, qualquer negociação corresponde a um conjunto de dois números que simbolizam o que se compra e em que moeda se compra. Também podemos interpretar isso como se sempre existisse uma moeda que desempenha o papel de uma mercadoria e outra, o papel da moeda em que tal mercadoria é comprada. 

Se abrirmos várias posições para vários instrumentos, haverá mais moedas principais e adicionais, e obteremos uma espécie de posição sintética. Do ponto de vista do uso de swaps, essa posição sintética é absolutamente inútil, mas podemos uma sintética que seja o oposto. Vou mostrar como fazer isso um pouco depois. Por enquanto, determinamos como é calculado o volume expresso em ambas as moedas, já com base nisso, podemos chegar à conclusão de que é possível criar uma posição sintética complexa equivalente a uma mais simples:

Na realidade, essas relações são um conjunto infinito, e são compostas por várias moedas, tais como:

Esta não é a lista completa de moedas, mas só precisamos saber que quaisquer 2 moedas desta lista podem ser usadas para criar um instrumento de negociação arbitrário. Alguns desses instrumentos de negociação são apresentados por corretoras, alguns podem ser obtidos como uma combinação de posições de outros instrumentos. Um exemplo típico é nosso par EURJPY. Este é apenas o exemplo mais simples de construção de pares de moedas derivados, mas com base nestas reflexões, pode-se chegar à conclusão de que qualquer posição pode ser representada como um conjunto de posições de outros instrumentos. Com base no dito acima, acontece que:

Precisaremos dessas relações no futuro para calcular os lotes. Vamos apenas nos lembrar delas por enquanto. Essas proporções descrevem a relação do número de moedas compradas ou vendidas; com base nelas, será possível construir uma lógica mais séria para o código.


Bloqueio usando posições sintéticas

Eu chamo de posição sintética uma posição que pode ser obtida de outras posições, enquanto essas outras posições devem necessariamente ser compostas por outros instrumentos. A posição em questão deve ser equivalente a uma posição aberta para qualquer instrumento. Por que tantas dificuldades, pergunta você? Tudo é bastante simples. Essa posição pode ser necessária para:

  1. Travar a posição original do instrumento simulado
  2. Tente fazer o equivalente a uma posição com indicadores de swaps completamente diferentes
  3. Outros objetivos
Pessoalmente, tive essa ideia por causa do ponto nº 2. Acontece que os swaps são definidos pelas corretoras em maior medida por seus próprios motivos e, em primeiro lugar, por motivos de lucro adicional. Acho que por isso devemos levar em consideração os swaps de outras empresas concorrentes, de modo que os traders não possam negociar swaps livremente. Abaixo estão os diagramas que podem apresentar esse conceito. Você pode até conseguir complementá-lo de alguma forma.

Abaixo, descrevo o esquema geral deste método:

Method diagram

Por si só, mesmo este gráfico não é suficiente para coletar dados completos sobre como abrir uma posição sintética. Este diagrama apenas fala sobre como determinar a direção da negociação para um componente específico de uma posição sintética, posição essa que deve necessariamente ser representada por um dos instrumentos disponíveis da corretora selecionada. 

Agora precisamos determinar como calcular os volumes dessas posições. Acho que é lógico calculá-los com base em que a posição deve ser equivalente à posição aberta com lote "1,0" para o instrumento resultante, ao qual é reduzida a versão selecionada da equação. Precisamos dos seguintes valores para o cálculo:

O que quero dizer é que nem sempre é possível determinar o "ContratoB", pois a combinação pode resultar num instrumento que não é apresentado pela corretora, neste caso pode ser definido de forma arbitrária, por exemplo, igual ao básico constante "100000".

Em primeiro lugar, é determinado o primeiro par da cadeia, que contém a moeda base do instrumento resultante na posição desejada, após isso, é realizada a busca pelos pares restantes, que compensam as moedas extras que não estão incluídas no equivalente resultante . O balanceamento termina quando a moeda principal está na posição certa no par em questão. Eu criei um diagrama especialmente para deixar claro como isso é feito:

Normalization

Resta implementar essas técnicas no código e analisar os resultados. O primeiro protótipo será o mais simples possível, apenas para avaliar o quão preciso são os apontamentos mencionados. Espero que aqueles que queriam entender melhor tenham conseguido isso com a ajuda dos meus diagramas.


Escrevemos um utilitário para examinar polígonos de swaps

Classificação da janela Market Watch e provisionamento de dados:

Para usar essa técnica, primeiro temos que selecionar só os pares cujos nomes têm exatamente 6 caracteres e apenas letras maiúsculas. De acordo com minhas observações, todas as corretoras aderem a esta regra. Em alguns casos, aparecem prefixos ou sufixos que também devem ser levados em consideração ao escrever algoritmos para trabalhar com dados de string. Para armazenar informações sobre o instrumento num formato conveniente, criei duas estruturas, a segunda será usada posteriormente:

struct Pair//необходимая информация об инструменте
   {
   string Name;//валютная пара
   double SwapBuy;//своп на покупку
   double SwapSell;//своп на продажу
   double TickValue;//заработок с 1 тика движения позиции открытой 1 лотом
   double TickSize;//размер тика в цене
   double PointX;//размер пункта в цене
   double ContractSize;//размер контракта в базовой валюте депозита
   double Margin;//маржа на открытие 1 лота
   };

struct PairAdvanced : Pair//расширенный контейнер
   {
   string Side;//в числителе или знаменателе   
   double LotK;//лотовый коэффициент
   double Lot;//лот
   };

Alguns campos não serão usados ao classificar os pares. Para não criar mais contêineres, apenas ampliei um pouco para que essa estrutura pudesse ser utilizada não só para esse fim. Eu tinha um protótipo de um algoritmo semelhante, mas suas capacidades eram muito limitadas, e ele só podia considerar os pares que estavam na janela principal do terminal, aqui tudo é mais simples e, o mais importante, automatizado e mais variável. Para definir o tamanho da matriz com instrumentos, precisamos desta função:

Pair Pairs[];//данные валютных пар
void SetSizePairsArray()//зададим размер массиву пар
   {
   ArrayResize(Pairs,MaxSymbols);
   ArrayResize(BasicPairsLeft,MaxPairs*2); //так как в паре по 2 валюты, то базовых валют может быть максимум в 2 раза больше
   ArrayResize(BasicPairsRight,MaxPairs*2);//так как в паре по 2 валюты, то базовых валют может быть максимум в 2 раза больше   
   }

A primeira linha define o máximo de pares que podemos encaixar na janela Market Watch, as outras duas também definem o tamanho das matrizes que usaremos. As 2 matrizes restantes desempenham um papel auxiliar para dividir o par de moedas em 2 partes (2 moedas compostas). As variáveis destacadas em amarelo são os parâmetros de entrada do EA.

Para verificar se um instrumento de negociação atende os critérios (sinais de duas moedas diferentes que podem estar potencialmente presentes em outros instrumentos), fiz a seguinte função predicado:

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

Esta função não é a única condição para verificar instrumentos no futuro, simplesmente não podemos escrever esta condição dentro de uma expressão lógica, portanto é mais fácil apresentá-la como um predicado. E agora passo para a função principal que preenche nossa matriz com dados:

void FillPairsArray()//заполним массив с необходимыми данными об инструментах
   {
   int iterator=0;
   double correction;
   int TempSwapMode;
   
   for ( int i=0; i<ArraySize(Pairs); i++ )//сбросим символы
      {
      Pairs[iterator].Name="";
      }   
   
   for ( int i=0; i<SymbolsTotal(false); i++ )//перебираем символы из окна (MarketWatch)
      {
      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://в пунктах
              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://в процентах
              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://в процентах
              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++;
         }
      }
   }

Nesta função, um simples percurso através de todos os símbolos e filtragem por uma condição composta complexa, que verifica se o comprimento do nome de string do símbolo é atendido, se negociar este símbolo é possível e outros parâmetros relacionados aos símbolos para os quais o método de cálculo de swap difere do mais comumente usado "em pontos". No bloco "switch", é selecionado um dos métodos para calcular o swap. Até agora, apenas 2 métodos foram implementados - em pontos e em porcentagem. A classificação correta é importante sobretudo para não fazer cálculos desnecessários. Só falarei sobre a função destacada em vermelho. Acontece que quando a moeda principal (não a moeda base) de um par é a moeda que não corresponde à moeda do nosso depósito, também temos que introduzir um certo coeficiente de ajuste que deve traduzir nosso swap para o moeda do nosso depósito. Esta função calcula este indicador. Este é o seu código:

bool CorrectedValue(string Pair0,double &rez)//корректировочный коэффициент для перевода в валюту счета для процентного метода расчета свопа
   {
   string OurValue=AccountInfoString(ACCOUNT_CURRENCY);//валюта нашего депозита
   string Half2Source=StringSubstr(Pair0,PrefixE+3,3);//нижняя валюта корректируемой пары
   if ( Half2Source == OurValue )
      {
      rez=1.0;
      return true;
      }
   
   for ( int i=0; i<SymbolsTotal(false); i++ )//перебираем символы из окна (MarketWatch)
      {
      if ( StringLen(SymbolName(i,false)) == 6+PrefixE+PostfixE && IsValid(SymbolName(i,false)) )//найдем курс валюты для перевода в валюту счета
         {
         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 função é um predicado e retorna o valor do fator de correção para a variável que foi passada de fora por referência. O fator de ajuste é calculado usando a cotação da moeda na qual está presente a moeda do nosso depósito.

Geração de fórmulas aleatórias

Suponhamos que tenhamos preenchido nossa matriz com os dados necessários, agora precisamos de alguma buscar variantes desses instrumentos e tentar criar instantaneamente todas as possíveis combinações de fórmulas que podem ser feitas a partir desses pares. Primeiro precisa decidir de que forma será armazenada nossa fórmula. A estrutura que armazena todos os indicadores desta fórmula deve ser o mais simples possível e legível para o usuário, caso desejemos ver os logs, e com certeza desejaremos fazer isso, caso contrário, será simplesmente impossível identificar erros.

É bom que nossa fórmula seja um conjunto de fatores à esquerda e à direita do sinal "=". O fator pode ser a cotação na potência de 1 ou -1 (que é equivalente a uma fração invertida, ou uma unidade referida à cotação do instrumento atual). Decidi deixar a estrutura assim:

struct EquationBasic //структура содержащая базовую формулу
   {
   string LeftSide;//валютные пары участвующие в формуле с левой стороны от знака "="
   string LeftSideStructure;//структура левой части формулы
   string RightSide;//валютные пары участвующие в правой части формулы
   string RightSideStructure;//структура правой части формулы
   };

Todos os dados serão armazenados em formato de string. Para estudar a fórmula, vamos analisar essas linhas e tirar todas as informações de que precisamos, além disso, elas sempre podem ser impressas. Esta é a aparência da impressão das fórmulas geradas:

Random Equations

Para mim, pessoalmente, esse registro é absolutamente claro e legível. O símbolo "^" é usado como separador entre pares. Na estrutura da fórmula, os separadores não são necessários, pois ela consiste em caracteres únicos "u" e "d", que indicam em que potência o fator está:

  1. "u" - cotação da moeda
  2. "d" - 1/cotação da moeda

Como se pode ver, as fórmulas obtidas na saída têm um comprimento flutuante e um tamanho flutuante de ambos os lados da equação, mas esse tamanho tem suas próprias limitações. Esta abordagem ajuda a atingir a variabilidade máxima das fórmulas geradas e, como consequência, a qualidade máxima das variantes encontradas dentro das condições de negociação da corretora selecionada. Essas condições são completamente diferentes para todas as corretoras. Para gerar essas fórmulas com sucesso, serão necessárias funções aleatórias adicionais que podem gerar números no intervalo de que precisamos. Para isso, teremos que escrever nossa própria funcionalidade usando os recursos da função MathRand embutida:

int GenerateRandomQuantityLeftSide()//сгенерируем рандомное количество пар в левой стороне формулы
   {
   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;//в одной из сторон должно быть не менее 2 пар, иначе это будет равносильно открытию двух встречных позиций
   if ( RandomQuantityRightSide > (MaxPairs-LeftLenght) ) return (MaxPairs-LeftLenght);
   return RandomQuantityRightSide;
   }
   
int GenerateRandomIndex()//сгенерируем рандомный индекс символа из окна MarketWatch
   {
   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;
   }

Todas as três funções serão úteis num determinado estágio, mas agora podemos escrever a função que tratará da geração dessas fórmulas. A partir de agora, o código ficará cada vez mais complexo, mas não usarei uma abordagem orientada a objetos, uma vez que a tarefa não é padrão. Decidi usar uma abordagem procedimental. Os procedimentos acabaram sendo muito grandes e complicados, mas não há nenhuma funcionalidade extra, e cada função lida com sua tarefa específica sem usar nenhuma função intermediária - a fim de evitar a duplicação de código. Nesse caso, isso apenas complicará uma tarefa já muito complexa e fora do padrão. A função ficará assim:

EquationBasic GenerateBasicEquation()//сгенерируем обе части рандомного уравнения
   {
   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 se pode ver, todas as três funções anteriores são usadas para gerar uma fórmula aleatória. Essas funções não são usadas em nenhuma outra parte do código. Depois que nossa fórmula estiver pronta, podemos proceder a uma análise passo a passo desta fórmula. As fórmulas incorretas serão descartadas pelo próximo filtro, embora mais complexo, extremamente importante. A primeira coisa a fazer é verificar a igualdade. Se a igualdade não for atendida, essa fórmula estará incorreta e as fórmulas que passarem no teste serão submetidas ao próximo estágio de análise.

Balanceamento da fórmula

Esta etapa implementa vários critérios de análise de uma vez:

  1. Cálculo de todos os fatores extras no numerador e denominador e sua eliminação
  2. Verificação de presença de 1 moeda no numerador e 1 moeda no denominador
  3. Verificação de correspondência das frações resultantes nos lados esquerdo e direito
  4. Se o lado direito é o recíproco do lado esquerdo, simplesmente invertemos a estrutura direita da fórmula (que é equivalente a elevar para a potência "-1")
  5. Se todas as etapas forem concluídas com sucesso, o resultado é escrito numa nova variável

É assim que o código ficará:

BasicValue BasicPairsLeft[];//массив базовых пар слева
BasicValue BasicPairsRight[];//массив базовых пар справа
bool bBalanced(EquationBasic &CheckedPair,EquationCorrected &r)//сбалансирована ли текущая формула (если да, то вернем скорректированную версию в переменную "r")
   {
   bool bEnd=false;
   string SubPair;//полное имя валютной пары
   string Half1;//первая валюта пары
   string Half2;//вторая валюта пары
   string SubSide;//валютная пара в числителе или знаменателе
   string Divider;//разделитель
   int ReadStartIterator=0;//индекс начала чтения
   int quantityiterator=0;//количество
   bool bNew;
   BasicValue b0;
   
   for ( int i=0; i<ArraySize(BasicPairsLeft); i++ )//обнуление массива базовых пар
      {
      BasicPairsLeft[i].Value = "";
      BasicPairsLeft[i].Quantity = 0;
      }
   for ( int i=0; i<ArraySize(BasicPairsRight); i++ )//обнуление массива базовых пар
      {
      BasicPairsRight[i].Value = "";
      BasicPairsRight[i].Quantity = 0;
      }
   ////вычисление значений баланса для левой части
   quantityiterator=0;
   ReadStartIterator=0;
   for ( int i=ReadStartIterator; i<StringLen(CheckedPair.LeftSide); i++ )//вынем базовые валюты из левой части уравнения
      {
      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++;
         }
      }
   ///конец вычисления баланса для левой части   
      
   ////вычисление значений баланса для правой части
   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++;
         }
      }
   ///конец вычисления баланса для правой части      
 
   ///вычислим количество нижних и верхних валют на основе полученных данных из предыдущего блока
   int LeftUpTotal=0;//количество верхних элементов в левой части
   int LeftDownTotal=0;//количество нижних элементов в левой части
   int RightUpTotal=0;//количество верхних элементов в правой части
   int RightDownTotal=0;//количество нижних элементов в правой части
   
   
   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;
      }      
   ///
   ///проверим обе части на эквивалентность
   if ( LeftUpTotal == 1 && LeftDownTotal == 1 && RightUpTotal == 1 && RightDownTotal == 1 )//вверху и внизу в обеих частях равенства должно быть строго по одной валюте, иначе формула невалидна
      {
      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 ( LastUpLeft == LastDownLeft ) return false;//если привели к единице то невалид

  /// осталось только записать все в новую скорректированную и более удобную структуру
   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;
   ///   
    
   ///если код дошел до этой точки то считаем что найдена формула удовлетворяющая критериям и следующим шагом необходимо будет провести нормализацию
      
   return true;
   }

A função destacada em verde, talvez, também apareça, ela é necessária para determinar se a lista de instrumentos contém a fórmula a que foi reduzida. Pode acontecer que a fórmula tenha sido reduzida, por exemplo, não para "USDJPY", mas para "JPYUSD". Nesse caso, o instrumento em questão não existe, embora pudesse ter sido criado, mas nossa tarefa é simplesmente corrigir a fórmula para que seja reduzida ao instrumento correto, para isso basta elevar ambas as partes da fórmula a "-1", o que equivale a inverter a estrutura da fórmula (alteramos "d" para "u" e vice-versa). Bem, se não houver tal instrumento na janela Market Watch, podemos deixar tudo como está:

string CorrectedResultInstrument(string instrument, bool &bResult)//если сгенерированной формуле соответствует какой либо эквивалентный инструмент то вернем его (либо оставим без изменения)
   {   
   string Half1="";
   string Half2="";   
   string Half1input=StringSubstr(instrument,0,3);//входная верхняя валюта
   string Half2input=StringSubstr(instrument,3,3);//входная нижняя валюта
   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) )//прямое совпадение либо крест накрест
         {
         bResult=true;
         return Pairs[j].Name;
         }
      }
   
   return instrument;
   }

Para armazenar as fórmulas que foram filtradas fiz a seguinte estrutura, alguns campos migraram do anterior, mas também surgiram novos:

struct EquationCorrected //скорректированная структура базовой формулы
   {
   string LeftSide;//валютные пары, участвующие в формуле с левой стороны от знака "="
   string LeftSideStructure;//структура левой части формулы
   string RightSide;//валютные пары, участвующие в правой части формулы
   string RightSideStructure;//структура правой части формулы
   
   string ResultInstrument;//результирующий инструмент к которому приводятся обе части формулы после преобразования
   bool IsResultInstrument;//найден ли подходящий эквивалентный инструмент
   string UpPair;//верхняя валюта результирующего инструмента
   string DownPair;//нижняя валюта результирующего инструмента
   };

Normalização de fórmulas

Este procedimento é a próxima etapa na filtragem dos resultados e contém as seguintes operações sequenciais:

  1. Com base no instrumento resultante, ao qual são reduzidos os dois lados da equação, selecionamos um par inicial da lista para os dois lados da igualdade.
  2. Ambos os pares, com base na sua potência, devem fornecer a moeda base no numerador da fração
  3. Depois de encontrar esse par, se a moeda mais baixa da fração não contiver a moeda base do instrumento resultante, vamos em frente
  4. Procedemos de forma que a moeda superior do próximo par compense a moeda inferior do anterior
  5. Realizamos essas etapas até a resultante desejada
  6. Depois de encontrar o resultante, os componentes não utilizados restantes da fórmula são descartados (desde que seu produto dê um)
  7. Juntamente com este processo, "coeficientes de lote" são calculados sequencialmente de par a par (eles mostram qual lote é preciso para abrir posições de pares específicos para garantir nosso instrumento resultante)
  8. O resultado é gravado numa nova variável que será usada na próxima etapa de análise.

O código para esta função é o seguinte:

bool bNormalized(EquationCorrected &d,EquationNormalized &v)//попытка нормализации формулы (нормализованную формулу вернем в "v")
   {
   double PreviousContract;//предыдущий контракт
   bool bWasPairs;//были ли найдены пары
   double BaseContract;//контракт пары к которой сводится уравнение
   double PreviousLotK=0.0;//предыдущий LotK
   double LotK;//Текущий LotK
   string PreviousSubSide;//в числителе или знаменателе (предыдущий множитель)
   string PreviousPair;//предыдущая пара
   string PreviousHalf1;//верхняя валюта предыдущей пары
   string PreviousHalf2;//нижняя валюта предыдущей пары
   string SubPair;//полное имя валютной пары
   string Half1;//первая валюта пары
   string Half2;//вторая валюта пары
   string SubSide;//валютная пара в числителе или знаменателе
   string Divider;//разделитель
   int ReadStartIterator=0;//индекс начала чтения
   int quantityiterator=0;//количество
   int tryiterator=0;//количество попыток баланса
   int quantityleft=0;//количество пар слева после нормализации
   int quantityright=0;//количество пар справа после нормализации
   bool bNew;
   BasicValue b0;
   
   for ( int i=0; i<ArraySize(BasicPairsLeft); i++ )//обнуление массива базовых пар
      {
      BasicPairsLeft[i].Value = "";
      BasicPairsLeft[i].Quantity = 0;
      }
   for ( int i=0; i<ArraySize(BasicPairsRight); i++ )//обнуление массива базовых пар
      {
      BasicPairsRight[i].Value = "";
      BasicPairsRight[i].Quantity = 0;
      }
      
   if ( d.IsResultInstrument ) BaseContract=SymbolInfoDouble(d.ResultInstrument, SYMBOL_TRADE_CONTRACT_SIZE);//определим контракт эквивалентной пары исходя из инструмента
   else BaseContract=100000.0;
      
   ////вычисление количества пар для левой части
   tryiterator=0;
   ReadStartIterator=0;
   for ( int i=ReadStartIterator; i<StringLen(d.LeftSide); i++ )//вынем базовые валюты из левой части уравнения
      {
      Divider=StringSubstr(d.LeftSide,i,1);
      if ( Divider == "^" )
         {
         ReadStartIterator=i+1;
         tryiterator++;
         }
         
      if ( i == StringLen(d.LeftSide) - 1 )
         {
         ReadStartIterator=i+1;
         tryiterator++;
         }         
      }
   ///конец вычисления количества для левой части   
      
   ArrayResize(v.PairLeft,tryiterator);
   ///посчитаем лотовые коэффициенты для левой части
   
   bool bBalanced=false;//сбалансирована ли формула
   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;//были ли уже пары
   for ( int k=0; k<tryiterator; k++ )//попытаемся нормализовать левую часть
      {
      if( !bBalanced )
         {
         quantityiterator=0;
         ReadStartIterator=0;
         for ( int i=ReadStartIterator; i<StringLen(d.LeftSide); i++ )//вынем базовые валюты из левой части уравнения
            {
            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")) ) //если это первая пара в списке
               || ( (( PreviousHalf2 == Half1 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half1 && PreviousSubSide == "d" )) && SubSide == "u" ) //если текущая пара в числителе
               || ( (( PreviousHalf2 == Half2 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half2 && PreviousSubSide == "d" )) && SubSide == "d" )) )//если текущая пара в знаменателе
                  {// найдем точку(пару) входа в цепочку
                  if( PreviousHalf1 == "" )//определим лотовый коэффициент стартовой пары
                     {
                     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" )//определим лотовый коэффициент последующих пар
                        {
                        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" )//определим лотовый коэффициент последующих пар
                        {
                        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 ( 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;
                           }
                        }            
                     }
                  
                  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 )//если дробь не перевернута
                     {
                     bBalanced=true;//если в знаменателе недостающая часть то мы сбалансировали формулу
                     break;//раз формула сбалансирована, то остальные нам не нужны
                     }
                  if ( SubSide == "d" && Half1 == d.DownPair )//если перевернута
                     {
                     bBalanced=true;//если в числителе недостающая часть то мы сбалансировали формулу
                     break;//раз формула сбалансирована, то остальные нам не нужны
                     }
                  
                  int LeftUpTotal=0;//количество верхних элементов в левой части
                  int LeftDownTotal=0;//количество нижних элементов в левой части
                  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;
      }
   ///конец вычисления коэффициентов для левой части       
     
   if ( !bBalanced ) return false;//если левая часть не сбалансирована то и правую балансировать смысла нет
     
   ////вычисление количества пар для правой части
   tryiterator=0;
   ReadStartIterator=0;
   for ( int i=ReadStartIterator; i<StringLen(d.RightSide); i++ )//вынем базовые валюты из левой части уравнения
      {
      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);
   ///конец вычисления количества пар для правой части  
     
   bBalanced=false;//сбалансирована ли формула
   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++ )//попытаемся нормализовать правую часть
      {
      if ( !bBalanced )
         {
         quantityiterator=0;
         ReadStartIterator=0;
         for ( int i=ReadStartIterator; i<StringLen(d.RightSide); i++ )//вынем базовые валюты из левой части уравнения
            {
            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")) ) //если это первая пара в списке
               || ( (( PreviousHalf2 == Half1 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half1 && PreviousSubSide == "d" )) && SubSide == "u" ) //если текущая пара в числителе
               || ( (( PreviousHalf2 == Half2 && PreviousSubSide == "u" ) || ( PreviousHalf1 == Half2 && PreviousSubSide == "d" )) && SubSide == "d" )) )//если текущая пара в знаменателе
                  {// найдем точку(пару) входа в цепочку
                  if( PreviousHalf1 == "" )//определим лотовый коэффициент стартовой пары
                     {
                     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" )//определим лотовый коэффициент последующих пар
                        {
                        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" )//определим лотовый коэффициент последующих пар
                        {
                        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 ( 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 ( 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 ( 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;
                           }
                        }            
                     }
                  
                  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 )//если дробь не перевернута
                     {
                     bBalanced=true;//если в знаменателе недостающая часть то мы сбалансировали формулу
                     break;//раз формула сбалансирована, то остальные нам не нужны
                     }
                  if ( SubSide == "d" && Half1 == d.DownPair )//если перевернута
                     {
                     bBalanced=true;//если в числителе недостающая часть то мы сбалансировали формулу
                     break;//раз формула сбалансирована, то остальные нам не нужны
                     }
                     
                  int RightUpTotal=0;//количество верхних элементов в правой части
                  int RightDownTotal=0;//количество нижних элементов в правой части
                  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;//если уравнение нормализовалось лишь до 2 пар то это не валид ( минимум 3 пары )
      
   return bBalanced;
   }

Eu entendo que este é um procedimento muito ecológico e complexo, mas me parece que nesses casos é melhor não produzir estados intermediários, pois isso levará a uma duplicação de código significativa, que já está presente em quantidade suficiente. Além disso, todos os estágios são feitos o mais compactos possível e divididos em blocos, tanto quanto a lógica permite. O principal é que o conjunto dessas funções nos dá resultados absolutamente idênticos àqueles se realizássemos todas essas transformações em nosso notebook. Nesse caso, todos esses tratamentos matemáticos serão feitos para nós por um conjunto de métodos complexos, mas necessários.

Para entender o quão lucrativa é a fórmula que encontramos, é necessário analisá-la de uma forma especial. Não se esqueça que para cada par podemos abrir uma posição tanto para cima quanto para baixo, e, portanto, cada fórmula dá duas variantes intermediárias dos contornos - direto e reverso. O contorno cujo indicador de rentabilidade é maior é considerado como resultado.

Como indicador de rentabilidade, fiz um indicador equivalente ao fator lucro, composto apenas pelos ganhos e perdas decorrentes dos swaps. Se o swap positivo acumulado do contorno existente for maior do que o negativo em valor absoluto, então tal contorno é considerado lucrativo. Em outros casos, esses contornos não são lucrativos, em outras palavras, o fator de swap do nosso contorno será positivo apenas quando for maior que um.

O resultado retornado já está escrito num contêiner completamente diferente que criei para usá-lo no futuro como um pacote de comandos autossuficiente para trading e desenvolvimento de lógica de negociação futura. Ele contém tudo de que precisamos para abrir de forma rápida e fácil todo o contorno:

struct EquationNormalized //финальная структура с формулой, содержащейся в нормализованном виде
   {
   Pair PairLeft[];//валютные пары c левой стороны
   Pair PairRight[];//валютные пары c правой стороны
   double SwapPlusRelative;//относительный эквивалент положительного свопа
   double SwapMinusRelative;//относительный эквивалент отрицательного свопа
   double SwapFactor;//результирующий своп-фактор
   };

Além disso, adicionei 2 métodos para exibir convenientemente informações sobre o conteúdo, no âmbito deste artigo, mas acho que eles são inadequados e inúteis aqui, não os mostrarei. Você pode ver isso nos códigos fonte. Agora, as informações sobre cada componente da equação estão contidas separadamente como elementos de matrizes; será simplesmente mais fácil trabalhar com isso posteriormente e não será necessário analisá-los constantemente a partir de strings. Talvez devêssemos ter feito isso imediatamente, mas a legibilidade já não teria sido boa.

Cálculo do fator de swap e ajuste final da estrutura da equação

Esta é a última etapa em que é considerado o indicador mais importante deste sistema que permitirá comparar nossas variantes. Quanto maior for esse indicador, melhor.

void CalculateBestVariation(EquationNormalized &ii)//вычисление лучшего своп-фактора формулы и корректировка структуры при необходимости
   {
   double SwapMinus=0.0;//отрицательный суммарный своп
   double SwapPlus=0.0;//положительный суммарный своп
   double SwapMinusReverse=0.0;//отрицательный суммарный своп
   double SwapPlusReverse=0.0;//положительный суммарный своп
   
   double SwapFactor=0.0;//своп-фактор прямого подхода
   double SwapFactorReverse=0.0;//своп-фактор реверсивного подхода
   
   for ( int i=0; i<ArraySize(ii.PairLeft); i++ )//определим недостающие параметры для расчета левой части
      {
      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++ )//определим недостающие параметры для расчета правой части
      {
      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;
   //посчитаем все составляющие сучетом что структура неизменна
   for ( int i=0; i<ArraySize(ii.PairLeft); i++ )//для левых частей
      {
      if ( ii.PairLeft[i].Side == "u" )
         {//для прямой торговли
         TempSwap=ii.PairLeft[i].SwapBuy*ii.LotKLeft[i];
         if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
         else SwapMinus-=TempSwap;
         //для реверсивной торговли
         TempSwap=ii.PairLeft[i].SwapSell*ii.LotKLeft[i];
         if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
         else SwapMinusReverse-=TempSwap;         
         }
      if ( ii.PairLeft[i].Side == "d" )
         {//для прямой торговли
         TempSwap=ii.PairLeft[i].SwapSell*ii.LotKLeft[i];
         if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
         else SwapMinus-=TempSwap;
         //для реверсивной торговли
         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++ )//для правых частей
      {
      if ( ii.PairRight[i].Side == "d" )
         {//для прямой торговли
         TempSwap=ii.PairRight[i].SwapBuy*ii.LotKRight[i];
         if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
         else SwapMinus-=TempSwap;
         //для реверсивной торговли
         TempSwap=ii.PairRight[i].SwapSell*ii.LotKRight[i];
         if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
         else SwapMinusReverse-=TempSwap;         
         }
      if ( ii.PairRight[i].Side == "u" )
         {//для прямой торговли
         TempSwap=ii.PairRight[i].SwapSell*ii.LotKRight[i];
         if ( TempSwap >= 0 ) SwapPlus+=TempSwap;
         else SwapMinus-=TempSwap;
         //для реверсивной торговли
         TempSwap=ii.PairRight[i].SwapBuy*ii.LotKRight[i];
         if ( TempSwap >= 0 ) SwapPlusReverse+=TempSwap;
         else SwapMinusReverse-=TempSwap;         
         }
      }   
   //вычисление своп-фактора для прямого подхода
   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;
   //вычисление своп-фактора для реверсивного подхода
   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;
   //выбор наилучшего подхода и вычисление недостающих величин в структуре
   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++ )//если реверсивный подход то реверсим правую структуру формулы
         {
         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++ )//если реверсивный подход то реверсим левую структуру формулы
         {
         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++ )//правую часть в любом случае переворачиваем
      {
      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 a saída sequencial dos resultados, dei especialmente um log escrito apenas se for encontrada uma variante da fórmula que passou na verificação do filtro. O log ficará assim:

Diário

O vermelho aqui é o instrumento resultante, para a qual são reduzidos ambos os lados da equação. A próxima linha é a variante já normalizada com coeficientes de lote. A terceira linha é a variante com o fator de swap calculado. Bem, a quarta linha é a melhor das variantes encontradas durante a sessão de força bruta, que também foi plotada no gráfico usando a função Comment. Este protótipo será anexado ao artigo e todos podem brincar com ele. Assim como está, na verdade, já é um protótipo de um assistente de negociação para negociação de swap. Há um mínimo de funcionalidade e de recursos também, mas no próximo artigo tentarei expandi-lo. O protótipo é apresentado em duas versões: para ambos os terminais - MetaTrader 4 e MetaTrader 5.


Conclusões a partir dos primeiros testes

Sozinho, é bastante difícil tirar conclusões sobre um tema tão complexo, mas mesmo assim consegui entender algo útil, embora até agora não tenha conseguido encontrar um fator de swap superior a um. Essas são as primeiras conclusões a que cheguei ao analisar o trabalho deste protótipo:

Fim do artigo

Espero que essa abordagem tenha interessado alguém ou, o que também seria legal, alimente a sede de conhecimento. O método é muito difícil de entender, mas na verdade ele implementa o princípio simples de abrir duas posições opostas com o mesmo volume. Abrir essas duas posições opostas diretamente significa uma perda sempre. Você não encontrará com nenhuma corretora que um swap unilateral positivo é maior em módulo do que um swap unilateral negativo. E mais ainda, você nunca verá situações em que o swap seja positivo nos dois sentidos, pois isso é fisicamente impossível devido à matemática do cálculo.

Não vou entrar em detalhes dessa matemática, uma vez que um artigo inteiro pode ser dedicado a isso. Agora e no futuro, é melhor trabalhar com aplicações dessa matemática. Aplicando este método, podemos reduzir a perda devida a swap ao bloquear posições e tentar encontrar uma lacuna nas tabelas de swap fornecidas por corretoras e nos proteger completamente com um fator de lucro positivo (swap lucrativo total de todas as posições), garantindo um risco nulo e independência das flutuações de preços.

Na minha opinião, os métodos de negociação de swap são muito subestimados, uma vez que um swap positivo é nosso lucro potencial. Este método é apenas uma das variações possíveis dos métodos de negociação de swap, mas gosto desta "missão" e nos próximos artigos tentarei desenvolver a ideia, modernizar o código e, claro, escrever funcionalidades adicionais, bem como prestar atenção às questões de previsão de lucro e escrita da funcionalidade de negociação.