English Español Português
preview
Разработка системы репликации (Часть 34): Система ордеров (III)

Разработка системы репликации (Часть 34): Система ордеров (III)

MetaTrader 5Примеры | 22 апреля 2024, 12:59
296 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 33): Система ордеров (II)", я объяснил, как мы будем строить нашу систему ордеров. В той статье мы рассмотрели большую часть кода и немного рассказали о сложностях и проблемах, которые нам предстоит решить. Хотя может показаться, что код прост и легок в применении, это далеко не так. Поэтому в этой статье мы немного углубимся в то, что у нас есть и что нужно сделать в плане реализации. Осталось обсудить и объяснить последнюю процедуру в классе C_Manager, а также прокомментировать код советника (EA). В случае с кодом советника мы в сосредоточимся на модифицированных частях. Таким образом, мы сможем получить представление о том, как он будет себя вести, не прибегая к тестированию на платформе MetaTrader 5. При этом вы можете тестировать и экспериментировать по своему усмотрению — весь код доступен в приложении к статье.

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

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


Основная функция класса C_Manager: DispatchMessage

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

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

Здесь есть две проблемы:

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

Учитывая эти два момента, мы можем посмотреть на код процедуры, чтобы узнать и понять, почему она выполняется, что можно увидеть ниже:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      static double price = 0;
      bool bBuy, bSell;
                                
      def_AcessTerminal.DispatchMessage(id, lparam, dparam, sparam);
      def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
      switch (id)
      {
         case CHARTEVENT_KEYDOWN:
            if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
            {
               if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  ToMarket(ORDER_TYPE_BUY);
               if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);
            }
            break;
         case CHARTEVENT_MOUSE_MOVE:
            bBuy = def_AcessMouse.CheckClick(C_Mouse::eSHIFT_Press);
            bSell = def_AcessMouse.CheckClick(C_Mouse::eCTRL_Press);
            if (bBuy != bSell)
            {
               if (!m_Objects.bCreate)
               {
                  def_AcessTerminal.CreateObjectGraphics(def_LINE_PRICE, OBJ_HLINE, m_Objects.corPrice, 0);
                  def_AcessTerminal.CreateObjectGraphics(def_LINE_STOP, OBJ_HLINE, m_Objects.corStop, 0);
                  def_AcessTerminal.CreateObjectGraphics(def_LINE_TAKE, OBJ_HLINE, m_Objects.corTake, 0);
                  EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
                  m_Objects.bCreate = true;
               }
               ObjectMove(def_InfoTerminal.ID, def_LINE_PRICE, 0, 0, def_InfoMouse.Position.Price);
               ObjectMove(def_InfoTerminal.ID, def_LINE_TAKE, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceTake, m_Infos.Leverage) * (bBuy ? 1 : -1)));
               ObjectMove(def_InfoTerminal.ID, def_LINE_STOP, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceStop, m_Infos.Leverage) * (bSell ? 1 : -1)));
               if ((def_AcessMouse.CheckClick(C_Mouse::eClickLeft)) && (price == 0)) CreateOrder((bBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), price = def_InfoMouse.Position.Price);
            }else if (m_Objects.bCreate)
            {
               EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
               ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
               m_Objects.bCreate = false;
               price = 0;
            }
            break;
         }
      }

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

    def_AcessTerminal.DispatchMessage(id, lparam, dparam, sparam);
    def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
    

    Помните, недавно мы говорили о том, что в какие-то моменты нам нужно, чтобы всё происходило в определенной последовательности? Вот эти две строчки выше как раз об этом. Они позволят не забыть или (что еще хуже) разместить обработку событий в неправильной последовательности в коде советника. Дело не в том,что это как-то влияет сейчас, на данном этапе написания кода, но чем меньше кода нам придется размещать в советнике, тем лучше он будет в будущем. Пропустив определенное событие, мы можем заставить весь проект работать совершенно неожиданным образом или не так, как нам хотелось бы.

    С этим всё понятно. Теперь мы можем начать с рассмотрения события CHARTEVENT_KEYDOWN. Он позаботится о срабатываниях, которые происходят при нажатии клавиши.

    case CHARTEVENT_KEYDOWN:
            if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
            {
                    if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  ToMarket(ORDER_TYPE_BUY);
                    if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))ToMarket(ORDER_TYPE_SELL);
            }
            break;
    

    Здесь у нас возникла ситуация, которая может показаться немного запутанной: согласно документации, переменная lparam будет содержать код нажатой клавиши. Такое действительно случается, но проблема в том, что нам нужно действовать немного иначе. Когда клавиша нажата, операционная система генерирует определенное событие. Если MetaTrader 5 получает фокус от операционной системы, он передаст сгенерированное событие. Поскольку у нашего кода есть специальный обработчик для действия нажатия клавиш, он изолирует обработку нажатия клавиш от других видов событий.

    Примечание: Код, показанный выше, на самом деле не обязательно помещать в событие CHARTEVENT_KEYDOWN; его можно разместить и вне данного события. Однако, поместив его в событие CHARTEVENT_KEYDOWN, мы избежим неловкой ситуации. Она возникает, когда мы запускаем анализ условия клавиш, то есть события CHARTEVENT_KEYDOWN, когда платформа оповещает нас о каком-то другом типе события, которое было вызвано по какой-то причине.

    Подумайте о странной проблеме обработки состояния клавиатуры, когда сработало событие, например CHARTEVENT_CHART_CHANGE; оно срабатывает, когда на графике происходят какие-то изменения. А наша программа там проверяет состояние клавиатуры. У подобных вещей нет практического смысла, а также требуют больших затрат времени на реализацию. Именно поэтому мы изолируем разбор состояния клавиатуры в событии CHARTEVENT_KEYDOWN.

    Но давайте вернемся к коду: можно заметить, что я использую функцию TerminalInfoInteger, чтобы узнать и изолировать определенный код клавиатуры. Если этого не сделать, то нам придется прилагать дополнительные усилия, чтобы проверить, была ли клавиша CTRL нажата одновременно с другой клавишей, в данном случае UP ARROW или DOWN ARROW. Именно этим мы и занимаемся. Нам нужно сочетание клавиш, чтобы наша программа, в данном случае советник, знала, что делать в плане программирования. В случае нажатия сочетания CTRL + UP ARROW советник должен понимать это как наше желание совершить покупку по рыночной цене. Если сочетание CTRL + DOWN ARROW, советник перейдет к продаже по рыночной цене. Обратите внимание, что, несмотря на то, что в переменной lparam значения клавиш указываются по отдельности, это не помогает нам работать с сочетаниями клавиш. Но если делать это так, как сейчас, нажимая только одну из сочетаний клавиш, советник никак не получит указания торговать по рыночной цене.

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

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

    case CHARTEVENT_MOUSE_MOVE:
            bBuy = def_AcessMouse.CheckClick(C_Mouse::eSHIFT_Press);
            bSell = def_AcessMouse.CheckClick(C_Mouse::eCTRL_Press);
    

    Обратите внимание на один момент. Здесь мы используем класс C_Study, чтобы получить доступ к классу C_Mouse. Не забудьте об этом, и обратите внимание, что в отличие от обработчика события CHARTEVENT_KEYDOWN, рассмотренного выше, здесь мы фиксируем состояние клавиши. Однако, они относятся к мышке. Вас это не смутило? На самом деле данные клавиши относятся к мыши, а не к алфавитно-цифровой клавиатуре. Но почему? Вы пытаетесь меня запутать? Ничего подобного, мой дорогой читатель. Тот факт, что мы можем нажимать клавиши SHIFT и CTRL на алфавитно-цифровой клавиатуре и при этом умудряться работать с этим внутри класса C_Mouse, объясняется тем, что всё происходит не совсем так. Данные клавиши SHIFT и CTRL на самом деле относятся к мыши. Но не к любой мыши. Я говорю об очень специфическом виде мыши, более или менее похожей на ту, которая показана на изображении 01, чуть ниже:

    Рисунок 01

    Рисунок 01:

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

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

            if (bBuy != bSell)
            {
                    if (!m_Objects.bCreate)
                    {
                            def_AcessTerminal.CreateObjectGraphics(def_LINE_PRICE, OBJ_HLINE, m_Objects.corPrice, 0);
                            def_AcessTerminal.CreateObjectGraphics(def_LINE_STOP, OBJ_HLINE, m_Objects.corStop, 0);
                            def_AcessTerminal.CreateObjectGraphics(def_LINE_TAKE, OBJ_HLINE, m_Objects.corTake, 0);
                            EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
                            m_Objects.bCreate = true;
                    }
                    ObjectMove(def_InfoTerminal.ID, def_LINE_PRICE, 0, 0, def_InfoMouse.Position.Price);
                    ObjectMove(def_InfoTerminal.ID, def_LINE_TAKE, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceTake, m_Infos.Leverage) * (bBuy ? 1 : -1)));
                    ObjectMove(def_InfoTerminal.ID, def_LINE_STOP, 0, 0, def_InfoMouse.Position.Price + (Terminal.FinanceToPoints(m_Infos.FinanceStop, m_Infos.Leverage) * (bSell ? 1 : -1)));
                    if ((def_AcessMouse.CheckClick(C_Mouse::eClickLeft)) && (price == 0)) CreateOrder((bBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), price = def_InfoMouse.Position.Price);
            }else if (m_Objects.bCreate)
            {
                    EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
                    ObjectsDeleteAll(def_InfoTerminal.ID, def_Prefix);
                    m_Objects.bCreate = false;
                    price = 0;
            }
    

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

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

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

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

    Чтобы понять, как происходит данная рекурсия, давайте посмотрим на рисунок 02, расположенный чуть ниже:

    Рисунок 02

    Рисунок 02: Поток внутренних сообщений.

    Зеленая стрелка на рисунке 02 - это как раз то место, где имеет место рекурсия. Но как это происходит в коде? Чтобы увидеть это, просто нужно посмотреть на фрагмент ниже, где мы показываем процедуру DispatchMessage, присутствующую в классе C_Manager.

    void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
       {
    
    // ...                          
    
          def_AcessMouse.DispatchMessage(id, lparam, dparam, sparam);
          switch (id)
          {
    
    // ...
    
             case CHARTEVENT_MOUSE_MOVE:
    // ...
                if (bBuy != bSell)
                {
                   if (!m_Objects.bCreate)
                   {
    // ...
                      EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_HideMouse, 0, 0, "");
                   }
    
    // ...
    
                }else if (m_Objects.bCreate)
                {
                   EventChartCustom(def_InfoTerminal.ID, C_Mouse::ev_ShowMouse, 0, 0, "");
    
    // ...
                }
                break;
             }
          }
    

    При просмотре рисунка 02, рекурсия может быть не очень понятна, а если просто посмотреть на приведенный выше код, то он также может быть недостаточно понятен. Но если соединить рисунок 02 с приведенным выше кодом, то можно увидеть, как осуществляется рекурсия. Если это спланировать неправильно, у нас возникнут серьезные проблемы. Давайте перейдем к объяснению, чтобы вы, как начинающий программист, смогли понять как силу рекурсии, так и её опасность. Когда нам нужно сделать что-то очень сложное, что бы это ни было, нужно разбить процесс на более мелкие или простые задачи. Эти задачи можно повторять определенное количество раз, чтобы в итоге получилось нечто гораздо более сложное, но следующее простой концепции. В этом и заключается сила рекурсии. Однако она используется не только в этом сценарии. Хотя чаще всего её связывают именно с этим, мы можем использовать рекурсию и для решения других вопросов.

    Приведенный выше отрывок - как раз такой случай. Чтобы понять объяснения, я прошу вас также взглянуть на рисунок 02. Всё начинается с того, что пользователь генерирует событие, которое заставляет платформу MetaTrader 5 вызывать функцию OnChartEvent. При этом данная функция вызывает функцию DispatchMessage в классе C_Manager. На этом этапе у нас есть вызов процедуры обработки событий, присутствующей по наследству в классе C_Mouse, в частности, в процедуре DispatchMessage, присутствующей в этом классе. Когда процедура возвращается, программа продолжает работу с того места, где она остановилась, и мы вводим обработку событий, чтобы проверить, хочет ли пользователь создать или удалить дополнительные линии. Именно программа решает данный вопрос, но в любом случае в какой-то момент мы получим выполнение вызова EventChartCustom. В этот момент активируется рекурсия.

    На самом деле произойдет следующее: платформа MetaTrader 5 выполнит новый вызов функции OnChartEvent, что приведет к ее повторному выполнению и к вызову процедуры DispatchMessage класса C_Manager. Это, в свою очередь, снова вызовет по наследованию класс C_Mouse, чтобы выполнить пользовательскую процедуру, которая, в зависимости от ситуации, заставит индикатор мыши появиться или исчезнуть. Однако, из-за рекурсии код не вернется, как можно было бы подумать. На самом деле, он вернется, но это снова приведет к выполнению всего кода, который находится в процедуре DispatchMessage класса C_Manager. И здесь кроется опасность: если разместить вызов так, чтобы пользовательское событие, которое обрабатывалось в классе C_Mouse, появилось и в классе C_Manager, оно также будет обработано в классе C_Manager. И если случайно снова обработать данное событие в классе C_Mouse с помощью функции EventChartCustom, то мы окажемся в бесконечном цикле.

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

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

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

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


    Анализ обновлений в советнике

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

    input group "Mouse";
    input color     user00 = clrBlack;      //Price Line
    input color     user01 = clrPaleGreen;  //Positive Study
    input color     user02 = clrLightCoral; //Negative Study
    input group "Trade";
    input uint      user10 = 1;             //Leverage
    input double    user11 = 100;           //Take Profit ( Finance )
    input double    user12 = 75;            //Stop Loss ( Finance )
    input bool      user13 = true;          //Is Day Trade
    //+------------------------------------------------------------------+
    input group "Control of Time"
    input string    user20  = "00:00 - 00:00";      //Sunday
    input string    user21  = "09:05 - 17:35";      //Monday
    input string    user22  = "10:05 - 16:50";      //Tuesday
    input string    user23  = "09:45 - 13:38";      //Wednesday
    input string    user24  = "11:07 - 15:00";      //Thursday
    input string    user25  = "12:55 - 18:25";      //Friday
    input string    user26  = "00:00 - 00:00";      //Saturday
    

    Он отвечает за взаимодействие с пользователем. Здесь у нас есть две новые группы информации, которые могут быть доступны и настроены пользователем. В первой группе мы можем сообщить, как будет осуществляться сделка - по рыночной цене или отложенной. Установленные здесь значения, в целом, просты для понимания. Значение, обозначающее плечо (user 10), должно быть заполнено таким образом, чтобы оно представляло собой количество раз, когда мы будем использовать минимальный объем, необходимый для торговли активом. В случае с Форексом вы, скорее всего, будете использовать значение 100 или что-то подобное, чтобы найти хорошую маржу для работы. В противном случае вы будете работать в порядке центов, что сделает линии лимитов далеко от той точки, где мы ожидали их увидеть. Если вы торгуете на бирже и в дробной форме, вы должны сообщить количество используемых акций. Иначе вам придется сообщать о количестве лотов. Для фьючерсных сделок вы должны указать количество контрактов. Короче говоря, что-то тривиальное. В части о тейк-профите (user 11) и стоп-лоссе (user 12) следует указывать не количество пунктов, а финансовую стоимость, которая будет использоваться. Данное значение должно быть соответствующим образом скорректировано кодом, чтобы отразить правильную позицию в цене актива. Последняя переменная (user 13) служит только для того, чтобы сообщить, выставляем ли мы длинную позицию или короткую, которая закончится в конце торговой сессии.

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

    Теперь, во второй группе, у нас есть кое-что, что следует протестировать перед правильной настройкой. И это не потому что это сложно или трудно для понимания, а потому что вы должны понимать, что данные значения будут определять, когда советник позволит нам отправлять ордеры или размещать отложенные ордеры. Вопрос об управлении, прекращении или даже изменении ордеров больше не будет зависеть от советника. За это будет отвечать платформа MetaTrader 5, по крайней мере, пока.

    Затем можно установить 1-часовое окно, в котором советник позволит нам работать, используя имеющиеся в нем ресурсы. Это следует делать с учетом того, что конфигурация настраивается с упором на неделю, а не на конкретный день или особую дату.

    Чтобы понять это, необходимо посмотреть на код OnInit, который можно увидеть чуть ниже:

    int OnInit()
    {
            string szInfo;
            
            terminal = new C_Terminal();
            study    = new C_Study(terminal, user00, user01, user02);
            manager  = new C_Manager(terminal, study, user00, user02, user01, def_MagicNumber, user12, user11, user10, user13);
            
            if (_LastError != ERR_SUCCESS) return INIT_FAILED;
            
            for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
            {
                    switch (c0)
                    {
                            case SUNDAY     : szInfo = user20; break;
                            case MONDAY     : szInfo = user21; break;
                            case TUESDAY    : szInfo = user22; break;
                            case WEDNESDAY  : szInfo = user23; break;
                            case THURSDAY   : szInfo = user24; break;
                            case FRIDAY     : szInfo = user25; break;
                            case SATURDAY   : szInfo = user26; break;
                    }
                    (*manager).SetInfoCtrl(c0, szInfo);
            }
    
            MarketBookAdd(def_InfoTerminal.szSymbol);
            OnBookEvent(def_InfoTerminal.szSymbol);
            EventSetMillisecondTimer(500);
    
            return INIT_SUCCEEDED;
    }
    

    Часть, которая нас действительно интересует в данном коде OnInit, выделена выше. Это полная картина того, как советник должен вести себя в течение недели, не только в определенный день, но и в течение всей недели. Есть активы или рынки, в данном случае рынок Форекс, где торговля ведется практически непрерывно и не прекращается ни в какое время суток. И если бы было нужно настроить систему управления на основе торгового расписания в советнике, который работает 24 часа в сутки, возникли бы проблемы при переходе на следующий день. То есть, как только наступит 23:59:59, советник нужно остановить и в следующую секунду запустить снова, чтобы узнать расписание торговли на следующий период. Но если использовать показанный выше метод, советник может оставаться включенным 24 часа в сутки, 7 дней в неделю, 52 недели в году, не теряясь и не пытаясь выяснить, в каком временном окне торговать дальше. Я знаю, просто посмотрев на этот код, многие могут не понимать, как это происходит на самом деле. Поэтому необходимо протестировать советник, чтобы понять, как работает данная система. Но эта система не нова. Об этом уже говорилось раньше:  Как построить советник, работающий автоматически (Часть 10): Автоматизация (II).


    Заключение

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

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



    Перевод с португальского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/pt/articles/11484

    Прикрепленные файлы |
    Files_-_BOLSA.zip (1358.24 KB)
    Files_-_FOREX.zip (3743.96 KB)
    Files_-_FUTUROS.zip (11397.51 KB)
    Разработка системы репликации (Часть 35): Внесение корректировок (I) Разработка системы репликации (Часть 35): Внесение корректировок (I)
    Прежде чем мы сможем двигаться дальше, нам нужно исправить несколько моментов. Но это не обязательные исправления, а улучшение в способе управления и использования класса. Причина в том, что сбои происходят из-за какого-то взаимодействия внутри системы. Несмотря на попытки узнать причину некоторых неудач, для их последующего устранения, все эти попытки оказались безуспешными, поскольку некоторые из них не имели смысла. Когда мы используем указатели или рекурсию в C / C++, программа аварийно завершается.
    Нейросети — это просто (Часть 86): U-образный Трансформер Нейросети — это просто (Часть 86): U-образный Трансформер
    Мы продолжаем рассмотрение алгоритмов прогнозирования временных рядов. И в данной статье я предлагаю Вам познакомиться с методов U-shaped Transformer.
    Разработка системы репликации (Часть 36): Внесение корректировок (II) Разработка системы репликации (Часть 36): Внесение корректировок (II)
    Одна из вещей, которая может усложнить нашу жизнь как программистов, - это предположения. В этой статье я покажу вам, как опасно делать предположения: как в части программирования на MQL5, где принимается, что у курса будет определенная величина, так и при использовании MetaTrader 5, где принимается, что разные серверы работают одинаково.
    Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 1): Как использовать RestAPI в MQL5 Как разработать агент обучения с подкреплением на MQL5 с интеграцией RestAPI (Часть 1): Как использовать RestAPI в MQL5
    В этой статье мы расскажем о важности интерфейсов программирования API для взаимодействия между различными приложениями и программными системами. В ней подчеркивается роль API в упрощении взаимодействия между приложениями, позволяя им эффективно обмениваться данными и функциональными возможностями.