English 中文 Español Deutsch 日本語 Português
Свопы (Часть I) : Локирование и синтетические позиции

Свопы (Часть I) : Локирование и синтетические позиции

MetaTrader 5Трейдинг | 14 апреля 2021, 11:22
6 821 6
Evgeniy Ilin
Evgeniy Ilin

Оглавление

    Введение

    Тема данной статьи уже давно вертится у меня на языке, все не было времени плотно ей заняться. Тема свопов довольно широко распространена в интернете, в основном среди уже профессионалов торговли, которые привыкли считать каждый пункт, что на самом деле является лучшим подходом к торговле. В этой статье вы не только убедитесь в том, что свопы нужно учитывать обязательно, но и поймете как их использовать во благо, а самые пытливые получат очень сложную, но интересную идею для модернизации своповых методов торговли, которые при должной проработке можно использовать в как рамках одного торгового счета, так и в качестве усиления прибыли для классического локирования с использованием двух счетов.


    О свопах

    Я здесь не буду рассказывать, что такое своп и углубляться в теорию. Меня же интересует чисто прикладная плоскость. Самый главный вопрос — можно ли заработать на свопах? С точки зрения трейдера своп — это прибыль либо убыток, а многие вообще не принимают его в расчет, потому что торгуют внутри дня. Остальные просто стараются не обращать на него внимание, напрасно думая, что его влияние настолько незначительно, что практически не повлияет на торговлю. На самом деле в свопе может быть сокрыта чуть ли не половина спреда, только снимается этот спред не в момент покупки или продажи, а в момент смены суток по серверу.

    Своп начисляется пропорционально объему открытой позиции и происходит это в такие моменты:

    1. C понедельника на вторник
    2. Со вторника на среду
    3. Со среды на четверг (практически у всех брокеров в эту ночь начисляется тройная величина свопа)
    4. С четверга на пятницу

    Обычно своп указан в спецификации торгового инструмента в пунктах или в процентах. Конечно, есть и другие методы расчета, но я пока разобрался только с двумя, но и этого хватает. Структурированной информации по этому вопросу очень мало, но если разобраться с ней, то окажется, что есть даже рабочие стратегии на свопах. Они приносят минимальные проценты, но плюсом данных стратегий является абсолютная гарантия прибыли. Главной трудностью данного подхода является тот факт, что у наиболее популярных брокеров очень мало инструментов с положительным свопом, чтобы можно было на этом заработать. Если и можно что-то заработать, то это сущие копейки. Тем не менее это лучше, чем вовсе слить депозит, а уж если вы используете какую-либо другую торговую систему, то вы скорее всего его сольете. 

    Итогом моих выводов касаемо торговли на форексе явилось то, что кроме положительных свопов ничего не может вам гарантировать прибыль. Есть конечно системы, которые способны приносить прибыль, но, используя их, мы заведомо соглашаемся с тем, что за любую торговую операцию мы потихонечку отдаем брокеру свои деньги, питая иллюзии, что цена пойдет в нашу сторону. Положительный своп же — это обратный процесс. Маркерами торговли в сторону положительного свопа, на мой взгляд, являются следующие утверждения:

    • Положительный своп эквивалентен частичному движению цены в сторону нашей открытой позиции (прибыль каждый день)
    • Своп через какое-то время способен перекрыть убытки по спредам и комиссиям, а через еще какое-то время вовсе только прибавлять нам деньги
    • Для того чтобы своп работал, нужно как можно дольше держать позицию, тогда профит-фактор наших позиций примет максимальные значения
    • При должной проработке прибыль абсолютно прогнозируема и гарантирована

    Конечно же самым большим минусом данного подхода является зависимость от величины депозита, но в то же время ни одна другая концепция не способна настолько уверенно гарантировать прибыль на форексе. Данную зависимость можно уменьшить путем уменьшения объема открываемых позиций или риска, что то же самое. Риск — это отношение объема нашей позиции к нашему депозиту, поскольку при его увеличении мы все больше рискуем, что цена может уйти в убыточную сторону и депозита может не хватить для того, чтобы дождаться, пока прибыль от свопов скомпенсирует наши убытки от спредов и комиссий. Для минимизации влияния всех возможных негативных эффектов придумали механизм локирования, о котором речь пойдет ниже.


    Локирование с использованием двух торговых счетов

    Данный метод торговли по свопам является самым популярным среди трейдеров. Для осуществления данной стратегии понадобятся два счета с разными свопами по одинаковым валютным парам либо иным торговым инструментам. Такой шаг необходим, поскольку в рамках одного счета открытие двух встречных позиций бессмысленно, так как это эквивалентно простому сливу депозита за счет того, что если у инструмента даже и есть положительный своп, то при торговле в противоположную сторону он всегда отрицателен. Я создал схему, в которой наглядно отражена концепция данного метода:

    Classic Swap Trading

    Как видно из диаграммы, для конкретно выбранного инструмента, по которому мы хотим торговать свопы, существует всего 10 торговых сценариев, 6 из которых активно используются. Последние четыре варианта можно выбирать в крайнем случае, если невозможно найти валютную пару, которая попадает под условия "1-6", поскольку один из свопов в таком случае получается отрицательным, и мы просто имеем ситуацию, когда положительный своп больше отрицательного. Все из этих ситуаций присутствуют, если рассмотреть большое количество разных брокеров и проанализировать их своповые таблицы. Но при использовании данной стратегии лучшими для нас будут варианты "2" и "5". Все дело в том, что в данных вариантах на обоих концах присутствуют положительные свопы, таким образом, при торговле мы получим большую прибыль с обоих брокеров, кроме того нам не так часто придется перекидывать средства со счета на счет.

    Главным минусом данной стратегии является необходимость постоянно перекидывать деньги со счета на счет, потому что при открытии встречных позиций на одном брокере будет убыток, а на другом прибыль, но если правильно рассчитать объемы совершаемых сделок относительно имеющегося депозита, то это не нужно будет делать слишком часто. Но есть один неоспоримый плюс — прибыль будет в любом случае и можно спрогнозировать ее точную величину. Я думаю, многим бы хотелось избавиться от этой рутины и как-то производить эти манипуляции внутри одного счета (что по определению невозможно), но есть один способ, как можно, если не полностью перейти на работу внутри одного счета, то, по крайней мере, увеличить прибыль классического метода торговли свопами. Об основных положениях этого метода будет идти речь ниже.


    О курсах валют

    Начнем с самого важного, с основы, относительно которой выстраивается вся логика, опираясь на которую можно выстраивать математические уравнения. Для примера возьму такие валютные пары как EURUSD, USDJPY, EURJPY. Все эти 3 пары связаны. Для того чтобы понять связь, нужно представить эти инструменты немного в ином виде:

    • 1/P = EUR/USD
    • 1/P = USD/JPY
    • 1/P = EUR/JPY
    • P - курс выбранной валюты

    В реальности в любом инструменте всегда есть та валюта или что-то ее заменяющее, что мы приобретаем, и есть та валюта, которую мы отдаем. Например, если взять первое соотношение (пара EURUSD), то при открытии позиции в 1 лот на "Buy" мы тем самым приобретаем 100000 единиц базовой валюты. Таковы правила торговли на рынке Форекс, один лот всегда равен 100000 единицам базовой валюты. На данной паре базовой валютой является "EUR", поэтому мы покупаем "EUR" за "USD". Курс валюты "P" в данном случае означает сколько единиц "USD" содержится в 1 "EUR". Для остальных инструментов аналогично, базовая валюта содержится в числителе дроби, а знаменатель, я даже не знаю, как назвать эту валюту, с вашего позволения назову ее "основная валюта", если кому-то принципиально, поправьте в комментариях под статьей. Количество основной валюты считается просто умножением цены на величину "EUR":

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

    В случае, если мы открываем позицию на продажу, валюты как бы меняются местами. Базовая валюта начинает выполнять роль основной, а основная играет роль базовой. Иначе говоря, приобретаем мы уже "USD" за "EUR", но количество денег обеих валют вычисляется точно так же относительно "EUR". Это правильно, потому что иначе возникала бы довольно большая путаница. Точно также все считается для других валют, просто условимся, что если валюта выполняет роль базовой, то мы будем считать ее со знаком "+", если это валюта, которая выполняет роль основной, то будем считать ее со знаком "-". В итоге любому трейду соответствует набор двух чисел, которые символизируют что купили и за что купили. Можно это еще интерпретировать так что всегда есть валюта выполняющая роль товара, а вторая валюта выполняет свою же роль валюты, за которую приобретается товар. 

    Если же мы открываем несколько позиций по нескольким инструментам, то таких основных и дополнительных валют становится больше, и мы получаем некую синтетическую позицию. С точки зрения использования свопов такая синтетическая позиция абсолютно бесполезна, но можно создать такую синтетическую позицию, которая будет максимально полезна. Как это сделать я покажу чуть дальше, а пока пойдем дальше. Мы определились, как считается объем, выраженный через обе валюты, исходя из этого можно прийти к выводу, что можно так составить сложную синтетическую позицию, и она будет эквивалентна какой-то более простой:

    • EUR/JPY = EUR/USD * USD/JPY — курс валюты составленный из двух производных

    В реальности таких соотношений бесконечное множество, они составлены из нескольких валют, таких как:

    • EUR - Евро
    • USD - Доллар США
    • JPY - Японская йена
    • GBP - Английский фунт
    • CHF - Швейцарский франк
    • CAD - Канадский доллар
    • NZD - Новозеландский доллар
    • AUD - Австралийский доллар
    • CNY - Китайский юань
    • SGD - Сингапурский доллар
    • NOK - Норвежская крона
    • SEK - Шведская крона

    Это не весь список валют, но нам нужно знать только то, что из любых 2 валют из данного списка можно составить произвольный торговый инструмент. Некоторые из этих торговых инструментов представлены у брокеров, некоторые можно получить как комбинацию из позиций других инструментов. Типичный пример — наша пара EURJPY. Это лишь простейший пример построения производных валютных курсов, но на основе данных размышлений можно прийти к тому, что любую позицию можно представить в виде совокупности позиций по другим инструментам. Исходя из соображений выше получается что:

    • Value1 - базовая валюта инструмента выраженная в абсолютной величине
    • Value2 - дополнительная валюта инструмента выраженная в абсолютной величине
    • A - объем базовой валюты позиции выраженный в лотах
    • B - объем основной валюты позиции выраженный в лотах
    • Contract - размер купленной или проданной валюты выраженный в абсолютной величине (соответствует 1 лоту)
    • A = 1/P = Value1/Value2 - уравнение любого инструмента (в том числе непредставленных инструментов в окне обзора рынка)
    • Value1 = Contract*A
    • Value2 = Contract*B

    Данные соотношения понадобятся нам в дальнейшем для расчета лотов. Пока просто запомним их. Эти соотношения описывают соотношение количества покупаемых либо продаваемых валют, на их основе можно будет выстраивать более серьезную логику для кода.


    Локирование с использованием синтетических позиций

    Синтетической позицией я называю позицию, которую можно набрать из нескольких других позиций, при этом эти другие позиции должны быть обязательно составлены из других инструментов. Данная позиция должна быть эквивалентна одной открытой позиции по какому-либо инструменту. Зачем такие сложности, спросите вы? Все достаточно просто. Такая позиция может понадобиться для того чтобы:

    1. Залокировать оригинальную позицию по имитируемому инструменту
    2. Попытаться составить эквивалент позиции с совсем иными показателями свопов
    3. Иные цели
    Лично мне эта идея пришла в связи с пунктом №2. Дело в том, что свопы выставляются брокерами в большей степени из своих каких-то соображений и в первую очередь из соображения дополнительной прибыли. Я думаю, что в этих соображениях есть и учет свопов других конкурирующих контор, чтобы трейдеры не могли беспрепятственно торговать свопы. Ниже будут диаграммы, которые смогут вас познакомить с этой концепцией. Возможно, вы даже сможете ее как-то дополнить.

    Ниже я изобразил общую схему данного метода:

    Method diagram

    Самой по себе даже этой диаграммы мало, чтобы полностью собрать данные о том, как открыть синтетическую позицию. Данная диаграмма лишь говорит, как определять направление торговли конкретной составляющей синтетической позиции, которая обязательно должна быть представлена одним из доступных инструментов выбранного брокера. 

    Теперь необходимо определить, как считать объемы этих позиций. Я думаю логично высчитывать данные объемы исходя из того соображения, что позиция должна быть эквивалентна позиции, открытой  лотом "1.0" по результирующему инструменту, к которому приводится выбранный вариант уравнения. Нам понадобятся для расчета следующие величины:

    • ContractB - размер контракта пары к которой приводится уравнение (в большинстве случаев он равен 100000 единиц базовой валюты инструмента)
    • Contract[1] - размер контракта пары лот которой нужно определить
    • A[1] - количество базовой валюты выраженное в лотах предыдущей сбалансированной пары (либо первой в цепочке
    • B[1] - количество основной валюты выраженное в лотах предыдущей сбалансированной пары (либо первой в цепочке)
    • A[2] - количество базовой валюты выраженное в лотах текущей балансируемой пары
    • B[2] - количество основной валюты выраженное в лотах текущей балансируемой пары
    • C[1] - размер контракта предыдущей сбалансированной пары (либо первой в цепочке)
    • C[2] - размер контракта текущей балансируемой пары

    Единственное хочу сказать, что "ContractB" не всегда можно определить, так как в результате комбинирования может получиться инструмент, который не представлен у брокера, в этом случае можно его задать произвольно, например, равным базовой константе "100000".

    Сначала определяется первая пара в цепочке, которая содержит базовую валюту результирующего инструмента в нужном положении, после чего производится поиск остальных пар, которые компенсируют лишние валюты, которые не входят в результирующий эквивалент. Балансировка заканчивается, когда находится основная валюта, в нужном положении, в рассматриваемой паре. Я специально создал схему для того, чтобы было понятно, как это делается:

    Normalization

    Остается только реализовать данные методики в коде и проанализировать результаты. Первый прототип будет максимально простым, просто чтобы оценить корректность всех соображений. Я надеюсь, что тем, кто хотел разобраться получше, это удалось с помощью моих диаграмм.


    Пишем утилиту для исследования своповых многоугольников

    Сортировка окна Market Watch и подготовка данных:

    Для того чтобы использовать данную технику, мы сначала должны отобрать только те пары, имена которых имеют в длину ровно 6 знаков и состоят только из букв верхнего регистра. По моим наблюдениям все брокеры придерживаются этого правила. В некоторых случаях появляются либо префиксы, либо постфиксы, которые тоже нужно учитывать при написании алгоритмов для работы со строковыми данными. Для хранения информации об инструменте в удобном для меня формате я создал две структуры, вторая будет использована позже:

    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;//лот
       };

    Некоторые поля здесь не будут использоваться при сортировке пар. Для того чтобы не плодить контейнеры, я просто немного его расширил, чтобы можно было использовать данную структуру не только для этой цели. У меня был прототип похожего алгоритма, но возможности его были сильно ограничены, и он мог рассматривать только те пары, которые были в основном окне терминала, здесь же все проще и, что самое главное, автоматизировано и более вариативно. Для того чтобы задать размер массива с инструментами, нам понадобится вот эта функция:

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

    Первая строка задает сколько максимум пар мы можем вместить из окна Market Watch, остальные две также задают размер массивов, которые мы будем использовать. Оставшиеся 2 массива выполняют вспомогательную роль, для того чтобы разрезать валютную пару на 2 части (2 составные валюты). Переменные, которые подсвечены желтым, являются входными параметрами советника.

    • MaxSymbols - максимальный размер хранилища с парами (решил сделать задаваемым вручную)
    • MaxPairs - максимальное количество пар в обеих частях формулы, которую мы генерируем (формулы длиннее этого числа не будут искаться советником)

    Для того чтобы проверить торговый инструмент на соответствие критериям (признаки двух разных валют, которые потенциально могут присутствовать в других инструментах), я сделал вот такую функцию-предикат:

    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;
       }

    Эта функция не единственное условие будущих проверок инструментов, просто данное условие не напишешь внутри логического выражения, поэтому проще вынести его как предикат. А теперь перехожу к главной функции, которая и заполняет наш массив необходимыми данными:

    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++;
             }
          }
       }

    В этой функции простой перебор всех символов и фильтрация по сложному составному условию, которое проверяет как соответствие длине строкового наименования символа, так и возможность торговли по данному символу и прочие параметры, которые относятся к тем символам, у которых метод расчета свопа отличается от наиболее часто используемого "в пунктах". В блоке "switch" как раз выбирается один из методов расчета свопа. Пока что реализовано только 2 метода — в пунктах и в процентах. Правильная сортировка в первую очередь важна для того, чтобы не заниматься вычислениями вхолостую. Единственное скажу еще про функцию, выделенную красным. Дело в том, что в тех случаях, когда основной (не базовой валютой) валютой пары является та валюта, которая не соответствует валюте нашего депозита, то приходится еще вводить некий корректировочный коэффициент, который должен перевести наш своп в валюту нашего депозита. Данная функция и занимается вычислением данного показателя. Таков ее код:

    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;
       }

    Данная функция является как предикатом, так и возвращает значение корректировочного коэффициента в ту переменную, которая была передана извне по ссылке. Корректировочный коэффициент вычисляется, используя курс нужной валюты, в которой присутствует валюта нашего депозита.

    Генерация случайных формул

    Предположим, что мы заполнили наш массив необходимыми данными, теперь каким-то образом нужно перебирать эти инструменты и пытаться создавать на лету все возможные комбинации формул, которые можно составить из данных пар. Для начала нужно определиться, в каком виде будет храниться наша формула. Структура, хранящая все показатели данной формулы, должна быть максимально простой и в то же время читаемой для пользователя, в случае если мы захотим посмотреть логи, а мы обязательно захотим это сделать, иначе ошибки просто невозможно будет выявить.

    Хорошо, что наша формула — это набор множителей как слева, так и справа от знака "=". Множителем может являться как курс валюты в первой степени, так и в минус первой (что эквивалентно перевернутой дроби, либо единице, отнесенной к курсу текущего инструмента). Я решил принять структуру в таком виде:

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

    Все данные будут храниться в строковом формате. Для исследования формулы мы будем парсить эти строки и вынимать всю необходимую нам информацию, кроме того, их всегда можно будет распечатать. Так будет выглядеть распринтовка генерируемых формул:

    Random Equations

    Лично для меня такая запись абсолютно понятна и читаема. В качестве разделителя между парами используется символ "^". В структуре формулы разделители не нужны, так как она состоит из единичных символов "u" и "d", которые говорят о том, в какой степени находится множитель:

    1. "u" - просто курс валюты
    2. "d" - 1/курс валюты

    Как видно, формулы, которые получаются на выходе, имеют как плавающую длину, так и плавающий размер обеих сторон уравнения, но этот размер имеет свои ограничения. С помощью данного подхода достигается максимальная вариативность генерируемых формул и, как следствие, максимальное качество найденных вариантов в рамках торговых условий выбранного брокера. У всех брокеров эти условия абсолютно разные. Для того чтобы успешно генерировать данные формулы, необходимы дополнительные функции рандома, способные генерировать числа в нужном нам диапазоне, для этого придется написать свой функционал, использующий возможности встроенной функции MathRand:

    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;
       }

    Все три функции пригодятся нам на определенном этапе, а вот теперь можно и написать саму функцию, которая будет заниматься генерацией данных формул. Начиная с данного момента, код будет становиться все сложнее и сложнее, но я не буду использовать объектно-ориентированный подход, поскольку задача нестандартна. Я решил использовать процедурный подход. Процедуры получились очень большими и громоздкими, но зато нет никакого лишнего функционала, и каждая функция занимается своей конкретной задачей без использования каких-то промежуточных функций — для того чтобы избегать дублирования кода. В данном случае это лишь усложнит понимание и без того очень сложной и нестандартной задачи. Функция будет выглядеть вот так:

    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;
       }

    Как видно, все три предыдущие функции используются здесь для генерации рандомной формулы. Больше нигде в коде эти функции не используются. После того как наша формула готова, мы можем приступить к поэтапному анализу данной формулы. Те формулы, которые некорректны, будут отброшены следующей более сложным, но крайне важным фильтром. Первое, что необходимо сделать — это проверить равенство. Если равенство не соблюдается, то данная формула некорректна, а те формулы, что пройдут проверку, будут подвергнуты следующей ступени анализа.

    Балансировка формулы

    Данная ступень осуществляет сразу несколько критериев анализа:

    1. Подсчет всех лишних множителей в числителе и знаменателе и их уничтожение
    2. Проверка на наличие по 1 валюте в числителе и по 1 валюте в знаменателе
    3. Проверка соответствия результирующих дробей в левой части и правой части
    4. В случае, если правая часть является обратной величиной левой части, то мы просто переворачиваем правую структуру формулы (что эквивалентно возведению в "-1" степень)
    5. Если все этапы завершены успешно, то результат записывается в новую переменную

    Так все это будет выглядеть в коде:

    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;
       }

    Функцию, выделенную зеленым, пожалуй, тоже покажу, она нужна для того, чтобы определить, есть ли в списке инструментов тот, к которому была сведена формула. Может так оказаться, что формула свелась, например, не к "USDJPY", а к "JPYUSD". В данном случае видно, что такого инструмента не существует, хотя и можно было бы его создать, конечно же, но наша задача просто поправить формулу так, чтобы она сводилась к корректному инструменту, для этого нужно просто возвести обе части формулы в степень "-1", что эквивалентно перевороту структуры формулы ("d" меняем на "u" и наоборот). Ну а если такого инструмента и вовсе нет в окне Market Watch, то мы можем оставить все как есть:

    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;
       }

    Для хранения формул, которые прошли фильтрацию, я сделал вот такую структуру, кое-какие поля перекочевали с предыдущей, но появились и новые:

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

    Нормализация формул

    Данная процедура является следующим этапом фильтрации результатов и содержит следующие последовательные операции, которые следуют друг за другом:

    1. Исходя из результирующего инструмента, к которому приводятся обе части уравнения, выбираем стартовую пару из списка для обеих частей равенства.
    2. Обе пары, исходя из степени, в которой они находятся в уравнении, должны обеспечивать наличие базовой валюты в числитель дроби
    3. После нахождения такой пары, если нижняя валюта дроби не содержит основную валюту результирующего инструмента, то идем дальше
    4. Идем дальше таким образом, чтобы верхняя валюта следующей пары компенсировала нижнюю валюту предыдущей
    5. Производим эти шаги до тех пор, пока не получится искомая результирующая
    6. После нахождения результирующей остальные неиспользуемые составляющие формулы отбрасываются (так как их произведение дает единицу)
    7. Попутно с данным процессом последовательно от пары к паре вычисляются "лотовые коэффициенты" (показывают каким лотом нужно открыть позиции по конкретным парам чтобы обеспечить наш результирующий инструмент)
    8. Результат записывается в новую переменную, которая будет использоваться в следующей ступени анализа

    Код данной функции таков:

    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;
       }

    Я понимаю, что это очень здоровая и сложная процедура, но мне кажется, в таких случаях лучше не плодить промежуточные состояния, потому что это приведет к существенному дублированию кода, что и так уже присутствует в достаточном количестве. Кроме того, все этапы сделаны максимально компактно и разделены на блоки, насколько это позволяет логика. Главное, что набор данных функций дают нам абсолютно идентичные результаты с теми, если бы все эти преобразования мы совершали у себя в тетради. В данном случае за нас все эти математические манипуляции сделает набор из нескольких сложных, но необходимых методов.

    Для того чтобы понять, насколько прибыльна формула, которую мы нашли, необходимо проанализировать ее особым образом. Не стоит забывать про то, что по каждой паре можно открыть позицию как вверх, так и вниз, и, соответственно, это нам дает, что из каждой формулы можно получить два промежуточных варианта контуров — прямой и обратный. Тот контур, чей показатель прибыльности окажется выше, тот и принимается в качестве результата.

    В качестве показателя прибыльности я сделал показатель, эквивалентный профит-фактору, только составленный из прибыли и убытков, которые приходят в результате начисления свопов. Если начисленный положительный своп имеющегося контура больше отрицательного по модулю, то такой контур считается прибыльным. В остальных случаях такие контура убыточны — иначе говоря, своп-фактор нашего контура будет положительным только тогда, когда он будет больше единицы.

    Возвращаемый результат уже записывается в совсем иной контейнер, который я создал для того, чтобы использовать его в дальнейшем, как самодостаточный командный пакет для торговли и развития будущей торговой логики. Он содержит все необходимое для того, чтобы быстро и просто открыть весь контур:

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

    Дополнительно добавил туда 2 метода для удобного вывода информации о содержимом, в рамках данной статьи, но считаю, что здесь они неуместны и бесполезны, не буду их приводить. Можно будет посмотреть в исходниках. Теперь информация о каждой составляющей уравнения содержится отдельно как элементы массивов, просто проще будет потом с этим работать и не надо будет постоянно парсить их из строк. Возможно стоило сделать так сразу, но тогда бы пострадала читаемость.

    Вычисление своп факора и финальная корректировка структуры уравнения

    Это последний этап, в котором считается самый важный показатель данной системы, по которому мы и будем сравнивать наши варианты. У кого больше данный показатель, тот и лучше.

    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;
             }
          }
       }

    Для последовательного вывода результатов я специально предусмотрел лог, который пишется только при условии нахождения варианта формулы, который прошел проверку фильтров. Лог будет выглядеть так:

    Log

    Красным здесь отмечен результирующий инструмент, к которому приводятся обе стороны уравнения. Следующей строкой идет уже нормализованный вариант с лотовыми коэффициентами. Третьей строкой — вариант с просчитанным своп-фактором. Ну а четвертой строкой — лучший из найденных вариантов за время сеанса брутфорса, который также вывел на график c помощью функции Comment. Данный прототип будет приложен к статье и все желающие смогут с ним поиграться. В данном виде, фактически, это уже прототип торгового ассистента для своповой торговли. Функционала минимум, возможностей тоже, но в следующей статье постараюсь его расширить. Прототип представлен в двух версиях: для обоих терминалов — MetaTrader 4 и MetaTrader 5.


    Выводы из первых тестов

    В одиночку довольно сложно делать какие-то выводы касаемо такой сложной темы, но все же кое-что полезное мне удалось понять, хоть и пока что не получилось найти своп-фактор первышающий единицу. Так выглядят первые выводы, к которым я пришел анализируя работу данного прототипа:

    • На некоторых валютных парах можно увеличить положительные свопы либо уменьшить отрицательные (благодаря представлению позиции в виде синтетического эквивалента)
    • Даже если не удалось найти прибыльный контур, то одну из его частей всегда можно использовать как альтернативную позицию для локирования на двух разных торговых счетах
    • Локирование такой синтетической позицией может избавить нас от необходимости использования Swap Free счетов, поскольку можно добиться встречного положительного свопа на обоих концах
    • Нужно производить более качественный и глубокий анализ по как можно большему числу самых популярных брокеров, а для этого потребуется расширение функционала.
    • Есть надежда доказать, что можно достичь прибыльного своп-фактора (но это пока только предположение)
    • Свопы могут дать небольшую, но стабильную прибыль при грамотном использовании

    Заключение

    Надеюсь, что данный подход кого-то заинтересовал или, что тоже было бы неплохо, дал пищу для мозга. Метод очень сложен для восприятия, но на самом деле реализует в себе простой принцип открытия двух встречных позиций одинаковым объемом. Напрямую открытие таких двух позиций навстречу означает убыток абсолютно всегда. Ни у одного брокера вы не найдете, чтобы положительный своп в одну сторону был бы больше по модулю, чем отрицательный своп в противоположную сторону. И уж тем более никогда не найдете ситуаций, когда своп положителен в обе стороны, потому что это физически невозможно в силу математики расчета.

    Я не буду вдаваться в подробности этой математики, поскольку ей можно посвятить целую статью. Сейчас и в дальнейшем лучше работать с проявлениями этой математики. Применяя данный метод, мы можем как уменьшить убыток от свопа при локировании позиций, так и попытаться найти брешь в своповых таблицах брокеров и вовсе обеспечить себе локирование с положительным профит-фактором, (прибыльным суммарным свопом всех позиций) обеспечив безрисковую торговлю и независимость от колебаний цены.

    На мой взгляд, своповые методы торговли сильно недооценены, поскольку положительный своп — это наша потенциальная прибыль. Данный метод является лишь одной из возможных вариаций своповых методов торговли, но мне данная задача по душе и в следующих статьях я постараюсь развить идею, модернизировать код и, конечно же, написать новый дополнительный функционал, а также уделить внимание вопросам прогнозирования прибыли и написанию торгового функционала.

    Прикрепленные файлы |
    Prototype.zip (21.27 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
    Evgeniy Ilin
    Evgeniy Ilin | 14 апр. 2021 в 15:42
    AlexxR84:
    Сейчас редко встретишь брокера, у которого по основным валютным парам и их кроссам есть положительные свопы. Только на экзотике такое встречается. И не факт, что найдётся синтетический инструмент, у которого отрицательный своп будет по модулю меньше, чем положительный на исходной паре. Кроме того, чем больше валют в синтетическом инструменте, тем больше будет суммарный спред при его открытии, а если ещё учесть проскальзывание при открытии.. можно год только ждать окупаемости всех этих вещей. 
    Ну и свопы, они же не постоянны и периодически меняются, и соответственно вся эта схема из синтетики порушится. Нужно будет искать новые варианты. А это новые спреды, проскальзывания и т.д.   
    Идея конечно интересная, я поверхностно рассматривал такую схему торговли, но к сожалению не нашёл приемлемых вариантов, и забросил)

    Вы абсолютно правы, насчет локирования синтетикой внутри одного счета это вряд ли сейчас. Но допустим вот я частенько лазаю по их своповым таблицам и у них там данные я даже не знаю скольки годичной давности и такое ощущение что им лет 5 )) Взять даже тех же самых Robof..... Иначе говоря я начинаю спецификацию смотреть на счете у себя там вообще ничего общего с этими таблицами )), и там как раз уних все зелено. Я думаю что существуют еще брокеры такие кто не сдвинул это все в красную зону. Они конечно скорее всего кухонные, но это не важно, копеечку другую отбить можно. Но примерно год назад анализировал 5-6 разных брокеров как раз когда прототип данного метода делал, и встречались такие пары которые у двух брокеров навстречу давали плюс... Это сто процентов. Тут просто руками это лень делать а вот совом можно. Даже если не выходит внутри одного счета всегда можно усилить классическую схему используя эти соображения. По итогам хочу как раз получить такой сов, но это конечно дело не одной статьи и не одной недели ). Одно точно доказано - синтетикой можно уменьшить отрицательный своп либо усилить положительный а этого достаточно. Получим если не усиление прибыли то дополнительные варианты для локирования

    YuriyKo
    YuriyKo | 5 мая 2021 в 09:44
       Евгений, как всегда, фундаментальность ваших разработок поражает и вызывает восхищение. Но давайте посмотрим на реальность. Для того, чтобы заработать на положительном свопе, открытую позицию надо держать месяц и более. За это время котировка валютной пары может уйти в такой в такой минус, что может наступить маржин-колл. И чем выше используемое плечо тем быстрее наступит маржин колл. Если использовать плечо в пределах от 1:1 до 1:10, то депозит выдержит глубокую просадку. И через несколько месяцев или даже лет открытой позиции цена может выйти из просадки в небольшой плюс. Я не буду точно утверждать, но по моим наблюдениям, чем меньше плечо, тем меньше величина свопа.  
    Evgeniy Ilin
    Evgeniy Ilin | 5 мая 2021 в 14:46
    YuriyKo:
       Евгений, как всегда, фундаментальность ваших разработок поражает и вызывает восхищение. Но давайте посмотрим на реальность. Для того, чтобы заработать на положительном свопе, открытую позицию надо держать месяц и более. За это время котировка валютной пары может уйти в такой в такой минус, что может наступить маржин-колл. И чем выше используемое плечо тем быстрее наступит маржин колл. Если использовать плечо в пределах от 1:1 до 1:10, то депозит выдержит глубокую просадку. И через несколько месяцев или даже лет открытой позиции цена может выйти из просадки в небольшой плюс. Я не буду точно утверждать, но по моим наблюдениям, чем меньше плечо, тем меньше величина свопа.  

    Большое спасибо за поддержку ). А насчет остального да вы правы, но тут есть способы как бороться с марждин коллами, можно так рассчитать объемы что марджин коллы будут происходить настолько редко что это не сможет сдвинуть прибыльность в сторону слива, все это считается, да это сложная математика но без нее никак, придется подключать теорию вероятностей по полной, но все равно задачка как минимум интересная. Я задумал еще как минимум 2 статьи в этой ветке и я попытаюсь это осветить. Просто сейчас вынужден заниматься другими делами, надеюсь в скором времени вернуться к этой теме. Не ждите скоро, материал очень сложный.

    CHEPtrade
    CHEPtrade | 17 авг. 2021 в 09:33
    Очень интересно, но сложно) все равно продолжайте)
    Maxim Kuznetsov
    Maxim Kuznetsov | 17 авг. 2021 в 10:48

    А зачем так сложно ?

    это-же всё представимо в виде полносвязного ор.графа (или матрицы, кому как привычно) и сводится к поиску циклов с минимальной(максимальной) суммой весов. 

    Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов Прочие классы в библиотеке DoEasy (Часть 70): Расширение функционала и автообновление коллекции объектов-чартов
    В статье расширим функционал объектов-чартов, организуем навигацию по графикам, создание скриншотов, сохранение и применение шаблонов к графикам. Также сделаем автоматическое обновление коллекции объектов-чартов, их окон и индикаторов в них.
    Советы профессионального программиста (Часть I): Хранение, отладка и компиляция кодов. Работа с проектами и логами Советы профессионального программиста (Часть I): Хранение, отладка и компиляция кодов. Работа с проектами и логами
    Советы профессионального программиста о методах, приемах и вспомогательных инструментах, облегчающих программирование.
    Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов Прочие классы в библиотеке DoEasy (Часть 71): События коллекции объектов-чартов
    В статье создадим функционал отслеживания некоторых событий объектов-чартов — добавление и удаление графиков символов, добавление и удаление подокон на график, а также добавление/удаление/изменение индикаторов в окнах чартов.
    Прочие классы в библиотеке DoEasy (Часть 69): Класс-коллекция объектов-чартов Прочие классы в библиотеке DoEasy (Часть 69): Класс-коллекция объектов-чартов
    С этой статьи начнём разработку класса-коллекции объектов-чартов, который будет хранить в себе список-коллекцию объектов-чартов с их подокнами и индикаторами в них, и даст возможность работы с любыми выбранными чартами и их подокнами, или сразу со списком из нескольких чартов одновременно.