English Español Português
preview
Разработка системы репликации (Часть 78): Новый Chart Trade (V)

Разработка системы репликации (Часть 78): Новый Chart Trade (V)

MetaTrader 5Примеры |
273 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье "Разработка системы репликации (Часть 77): Новый Chart Trade (IV), мы подробно объяснили, как разработать протокол связи, не усложняя материала. Это позволяет передавать информацию между различными приложениями или программами, независимо от того, находятся ли они в одной среде или нет. В данном случае мы хотим, чтобы индикатор Chart Trade указывал советнику, что делать. То есть, когда пользователь говорит Chart Trade, что хочет продать, советник выполняет продажу. Когда пользователь говорит, что хочет купить, советник покупает по рыночной цене.

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

Но объяснение будет неполным без предварительного знакомства с частью кода получателя, которая присутствует в советнике. Это связано с тем, что MetaTrader 5 запрещает любому индикатору отправлять ордера, манипулировать позициями или делать что-либо, что напрямую связано с системой, отвечающей за передачу сообщений торговому серверу о том, что должно произойти что-то. А это связано с тем, что таким действием может быть открытие, закрытие или даже изменение состава позиции или ордера.

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

В данный момент нас волнует другое. В данном случае наша основная задача заключается в том, чтобы понять, как советнику удается понять происходящее. Это связано с тем, что пользователь никак не взаимодействует с советником. Другими словами, чтобы сообщить советнику, что делать, пользователь будет взаимодействовать с индикатором Chart Trade. Индикатор Chart Trade не будет иметь никакого смысла, если человек, который отправляет ордеры на рынок (на открытие или закрытие позиции), не знает, что происходит. Помните: тот, кто действительно имеет право делать это, - советник, ни одна другая программа не может сделать это в MetaTrader 5.

В данной статье речь пойдет о главном: 


Как советник понимает Chart Trade?

Давайте начнем сегодняшнюю тему с этого вопроса. Если вам непонятно, как это происходит, я бы посоветовал сначала разобраться с предыдущей статьей, где объясняется, как разработать протокол обмена сообщениями. Мы сегодня рассмотрим, как сделать так, чтобы магия заработала. Это происходит, потому что ни индикатор Chart Trade, ни советник не знают о существовании друг друга. И для них нет необходимости знать об этом, достаточно иметь доверие к тому, что сообщение будет доставлено и понято. 

Однако, даже без понимания того, что на графике находится советник, индикатор Chart Trade умудряется сообщить советнику о том, что хочет сделать пользователь. За такими моментами очень интересно наблюдать.

Но прежде всего, если мы поймем это, мы сделаем свои программы или приложения, работающие на MetaTrader 5, гораздо более универсальными. Более того, данные знания выходят далеко за рамки MetaTrader 5.

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

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

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

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

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

Нам нужно что-то как можно более простое, чтобы вы поняли, как работает часть кода получателя. Так будет более понятно, как протокол обмена сообщениями заставляет действовать индикатор Chart Trade, который не знает о существовании советника.

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

Самый простой код для этого показан ниже. Данный код представлен в полном объеме.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Demo version between interaction"
04. #property description "of Chart Trade and Expert Advisor"
05. #property version   "1.78"
06. #property link "https://www.mql5.com/pt/articles/11760"
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Defines.mqh>
09. //+------------------------------------------------------------------+
10. class C_Decode
11. {
12.    private   :
13.       struct stInfoEvent
14.       {
15.          EnumEvents ev;
16.          string     szSymbol;
17.          bool       IsDayTrade;
18.          ushort     Leverange;
19.          double     PointsTake,
20.                     PointsStop;
21.       }info[1];
22.    public   :
23. //+------------------------------------------------------------------+
24.       C_Decode()
25.          {
26.             info[0].szSymbol = _Symbol;
27.          }
28. //+------------------------------------------------------------------+   
29.       bool Decode(const int id, const string sparam)
30.       {
31.          string Res[];
32.       
33.          if (StringSplit(sparam, '?', Res) != 6) return false;
34.          stInfoEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], (bool)(Res[2] == "D"), (ushort) StringToInteger(Res[3]), StringToDouble(Res[4]), StringToDouble(Res[5])};
35.          if ((id == loc.ev) && (loc.szSymbol == info[0].szSymbol)) info[0] = loc;
36.          
37.          ArrayPrint(info, 2);
38.       
39.          return true;
40.       }
41. //+------------------------------------------------------------------+   
42. }*GL_Decode;
43. //+------------------------------------------------------------------+
44. int OnInit()
45. {
46.    GL_Decode = new C_Decode;
47.    
48.    return INIT_SUCCEEDED;
49. }
50. //+------------------------------------------------------------------+
51. void OnTick() {}
52. //+------------------------------------------------------------------+
53. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
54. {
55.    switch (id)
56.    {
57.       case CHARTEVENT_CUSTOM + evChartTradeBuy     :
58.       case CHARTEVENT_CUSTOM + evChartTradeSell    :
59.       case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
60.          (*GL_Decode).Decode(id - CHARTEVENT_CUSTOM, sparam);
61.          break;
62.    }
63. }
64. //+------------------------------------------------------------------+
65. void OnDeinit(const int reason)
66. {
67.    delete GL_Decode;
68. }
69. //+------------------------------------------------------------------+

Исходный код советника

«Ты хочешь сказать, что приведенный выше код прост? Друг, я не понимаю тебя. Данный код мне кажется очень сложным. Я не могу понять в нем практически ничего. Я растерян как никогда. Если этот код прост, то представь себе что-нибудь сложное?» Да, на самом деле этот код действительно очень прост. Однако в нем используются некоторые вещи, которые многие из читателей обычно не используют. Вернее, многие даже не знают, что это можно использовать в MQL5. Поэтому, если это ваш случай, и вы собираетесь в будущем стать профессиональным программистом, следуйте объяснениям, потому что здесь всё гораздо проще, чем может показаться, а код довольно прост и объективен в своей работе.

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

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

После чего перейдем к первой функции, которая действительно нужна для нашего кода. Это OnInit, и она начинается в строке 44. Из-за строки 42 всё может происходить несколько иначе, чем многие себе представляют. В строке 46 мы используем оператор new для выделения памяти и инициализации класса C_Decode. Но зачем выделять для этого память? Не проще ли чтобы компилятор обо всем позаботился? Да, действительно, было бы проще позволить компилятору делать это. Однако мы должны привыкнуть управлять и контролировать, когда и в каком объеме будет использоваться тот или иной класс. Если оставить это на усмотрение компилятора, то в некоторых моментах можно использовать класс с другими значениями, чем можно было ожидать.

Поверьте, нередко приходится использовать класс с разными значениями, особенно когда код довольно большой и класс объявлен в нескольких разных местах. Рано или поздно можно запутаться. Но, используя набор операторов new и delete, мы можем точно определить, где начинается и где заканчивается наш код. Многие начинающие программисты не справляются с данной задачей. Как ни странно, они затрудняются сказать, где заканчивается код. Чаще всего многие даже не знают, с чего всё начинается. Это странно, но так бывает.

Затем, когда будет выполнена строка 46, она выделит достаточно памяти для размещения класса C_Decode. В это же время мы вызываем строку 24. Данная строка является конструктором класса C_Decode. Прошу заметить, что в этом конструкторе инициализируется только одна переменная. Она сохранит название символа, на котором находится советник. Теперь обратите внимание на следующее: в данной модели У НАС НЕ БУДЕТ РАЗРЕШЕНИЯ НА КРОСС-ОРДЕРЫ. На данный момент, я думаю, вам это не совсем понятно. Но я вам предлагаю дальше читать статью, чтобы понять, почему у нас не будет разрешения на совершение кросс-ордерных сделок. Однако с небольшими изменениями мы сможем использовать кросс-ордеры. Пока что не беспокойтесь об этом. Помните, что данный код служит только для того, чтобы понять, как работает протокол сообщений. Он не предназначен для выполнения каких-либо запросов к торговому серверу.

Давайте вернемся к функции OnInit. После выполнения строки 46 мы возвращаем в строке 48 значение, которое указывает на успешное завершение инициализации. Важно сообщить об этом в MetaTrader 5. А это связано с тем, что если возвращаемое нами значение будет отличаться от INIT_SUCCEEDED, то MetaTrader 5 автоматически предпримет меры по отключению советника. Один из таких шагов - запуск события Deinit, которое вызовет процедуру OnDeinit в коде советника.

Если в нашем коде нет данной процедуры, MetaTrader 5 будет действовать по умолчанию. Однако в итоге, даже если наше приложение останется в списке, оно не получит процессорного времени. Другими словами, MetaTrader 5 не будет планировать выполнение нашего приложения.

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

Поэтому рекомендуется всегда заботиться о выполнении процедуры OnDeinit. Часто она мало что дает. Но в нашем случае она содержит только одну строку: 67. В ней мы используем оператор delete, чтобы освободить выделенную память. В этот момент нужно вызывать деструктор класса C_Decode. Поскольку наш класс не имеет явно объявленного деструктора, компилятор создаст неявный деструктор, когда заметит, что в классе используется оператор delete. Это происходит так, потому что оператору delete на самом деле нужен деструктор. Однако, вам не стоит беспокоиться об этом. Я объясняю это только для того, чтобы вы знали, как всё устроено на самом деле.

Хорошо, мы уже увидели, где начинается и где заканчивается код нашего советника, и мы можем перейти к другим пунктам. В строке 51 находится процедура, которая будет вызываться каждый раз, когда символ получает уведомление. Данная процедура обязательна для всех советников. Но вам следует избегать включения кода в эту процедуру, а точнее, размещения кода в ней. О причинах можно прочитать в серии статей о том, как создать советник, работающий на 100% автоматически. Если хотите создать советник с определенным уровнем автоматизации, я вам предлагаю ознакомиться с этой серией.

В ней всего 15 статей, первую из которых можно посмотреть здесь Как построить советник, работающий автоматически (Часть 1): Концепции и структуры. На самом деле, прочтение этой серии очень поможет вам. Но здесь, в этой серии по системе репликации/моделирования, мы будем использовать некоторые вещи, которые были показаны в серии об автоматическом советнике. В любом случае, обязательно посмотрите на серию статей, о которой я говорю.

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


Декодирование сообщения, полученного на событии

В прошлых статьях этой же серии о репликации/моделировании мы объясняли, как взаимосвязаны EventChartCustom и OnChartEvent. Они активно использовалось для того, чтобы сервис репликации/моделирования успешно передавал данные на индикатор управления. Это нужно для того, чтобы индикатору управления было понятно, где мы находимся в процессе репликации или моделирования. Если вы не прочитали статьи об этом (потому что именно к этой последовательности действий вы переходите в данной статье), то можно ознакомиться с ними и понять, как сервису удалось отправить данные на индикатор управления, просмотрев статьи о том, как нажать кнопку воспроизведения сервиса.

Одна из таких статей - Разработка системы репликации (Часть 60): Нажатие кнопки воспроизведения в сервисе (I). Об этом написано семь статей. Однако вам стоит прочитать и другие, поскольку эти статьи помогут вам разобраться в деталях, но главным образом в том, почему следует действовать именно таким образом.

При общении между сервисом и индикатором контроля связь осуществлялась с помощью числовых параметров. Это была самая простая часть системы, поскольку информация была доступна непосредственно в параметрах (в lparam или dparam). Но здесь всё по-другому. На этом этапе мы будем использовать значение параметра sparam. То есть теперь сообщение будет представлять из себя строку. И именно в этой строке мы находим то, что нам нужно. Информация закодирована в той или иной форме. Способ, с помощью которого осуществляется такое кодирование, называется протоколом сообщений. В предыдущей статье мы объяснили эту концепцию. Здесь мы узнаем, как декодировать данное же сообщение в рамках протокола.

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

Поскольку сообщение может быть декодировано по одному и тому же протоколу - между тремя различными типами - мы можем сделать то, что показано в строках с 57 по 59. Прошу заметить, что здесь распознаны три события, которые советник должен перехватить. Так как все три сигнала будут проходить через один и тот же декодирующий фильтр, единственной реально используемой строкой является строка 60. Данная строка вызовет код, расположенный в строке 29. Теперь, пожалуйста, внимательно отнеситесь к тому, что я вам сейчас объясню. Если вы не поняли объяснение, перечитайте его еще раз, потому что это очень важно, хотя встречается крайне редко.

Прощу заметить, что в строке 31 мы объявляем локальную переменную, которая представляет собой динамический массив. Поскольку он является динамическим, программа сама выделяет для него память. Поэтому нам не стоит беспокоиться о нем. Теперь обратите внимание на следующий факт: в строке 33 мы выполняем вызов функции StringSplit. Она предназначена для заполнения динамического массива строками, разделенными каким-то определенным символом. В данном случае - вопросительный знак. Но почему именно этот символ?

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

Теперь обратите внимание на строку 34. Она не очень распространена, но и не является чужеродным кодом. Чтобы понять, что происходит, нужно посмотреть на строку 34 в сочетании со структурой, объявленной в строке 13. ОБРАТИТЕ ВНИМАНИЕ. Каждый элемент, который присутствует в строке 34, имеет соответствующий элемент и ссылается на что-то в структуре stInfoEvent. То, что делается, - не совсем обычная вещь, но мы сделали так специально, чтобы объяснить кое-что. Обратите внимание на порядок объявления переменных в структуре stInfoEvent. Это очень важно. Теперь посмотрите на порядок расположения значений в строке 34. Ух ты! Проверьте, чтобы они совпали. Таким образом, строка 34 будет равнозначна следующему коду:

      stInfoEvent loc;
          
      loc.ev = (EnumEvents) StringToInteger(Res[0]);
      loc.szSymbol = Res[1];
      loc.IsDayTrade = (Res[2] == "D");
      loc.Leverange = (ushort) StringToInteger(Res[3]);
      loc.PointsTake = StringToDouble(Res[4]);
      loc.PointsStop = StringToDouble(Res[5]);

Фрагмент кода - Модель 01

Всё точно так же. Но, тем не менее, использование кода из строки 34, сопряжено с риском. И этот же риск не возникает при использовании кода, приведенного во фрагменте. Он заключается в изменении структуры stInfoEvent. «Но почему так? Я не понял, почему изменение структуры stInfoEvent является проблемой при написании кода, как показано в строке 34, но не при написании кода, как показано в фрагменте».

Причина довольно проста. Это может показаться глупым, но, тем не менее, доставит немало головной боли и многочасовых попыток понять, почему всё идет не так. Давайте представим себе очень простое изменение: в какой-то момент вы решаете поменять переменную, объявленную в строке 19, на переменную, объявленную в строке 20. Для кода, выполненного так, как показано во фрагменте, это изменение никак не повлияет на ситуацию.

Однако для кода, показанного в строке 34, ситуация полностью меняется. Это происходит, потому что значение, которое мы ожидаем поместить в переменную PointsTake, будет помещено в PointsStop. И значение, которое должно было быть в PointsStop, будет помещено в PointsTake. «Но что это за бессмыслица? Вы наверное шутите, этого не может быть. Нет, мой дорогой читатель, это не шутка и не безумие.

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

Но если вы знаете, что делаете, то сможете добиться того, что иначе было бы невозможно. Поэтому используйте данный способ присвоения значений структуре только тогда, когда структура уже завершена, полностью проработана и закрыта. И после составления проекта ВЫ НЕ ДОЛЖНЫ БОЛЬШЕ ЕГО МЕНЯТЬ. Если вы измените его, тогда придется проверить все точки выделения, которые похожи на строку 34. Чтобы понять, чем именно это грозит, используйте данный код для тестирования системы, просто изменив строку 34 или порядок объявления переменных в структуре stInfoEvent.

А пока давайте продолжим объяснение. Когда код доходит до 35-й строки, мы проверяем, соответствуют ли всё тому, что мы ожидаем: и имя символа, и тип события. В этом случае мы присваиваем значение статическому массиву. ВНИМАНИЕ: данное присвоение будет выполняться только на данном этапе тестирования и демонстрации. Это связано с тем, что структура stInfoEvent должна находиться в массиве. А причина в строке 37. В этой строке мы используем другую функцию MQL5 - ArrayPrint. Данная функция распечатает для нас массив, так что результат его содержимого будет выведен на терминал. Результат одного из выполнений можно увидеть на изображении ниже.

Изображение


Заключение

По причинам, уже описанным в этой статье, советник пока не может отправлять запросы на торговый сервер. Если вам непонятно, как работает код и, что более важно- то, как на самом деле работает протокол связи между Chart Trade и советником, вы рискуете совершить глупость. Но, несмотря на это, было интересно оставить всё так, потому что мы воспользовались возможностью объяснить, с чем можно столкнуться в какой-то момент без понимания того, почему это работает или не работает.

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


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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Управление рисками (Часть 1): Основы построения класса по управлению рисками Управление рисками (Часть 1): Основы построения класса по управлению рисками
В этой статье мы рассмотрим основы управления рисками в трейдинге и узнаем, как создать свои первые функции для расчета подходящего лота для сделки, а также стоп-лосса. Кроме того, мы подробно рассмотрим, как работают эти функции, объясняя каждый шаг. Наша цель — дать четкое понимание того, как применять эти концепции в автоматической торговле. В конце мы применим все на практике, создав простой скрипт с разработанным нами включаемым файлом.
Нейросети в трейдинге: Распутывание структурных компонентов (Окончание) Нейросети в трейдинге: Распутывание структурных компонентов (Окончание)
В статье подробно раскрывается SCNN-архитектура и один из вариантов её реализация средствами MQL5. Мы покажем, как декомпозиция временных рядов сочетается с нейросетевыми методами и вниманием.
Теория графов: Алгоритм Дейкстры в трейдинге Теория графов: Алгоритм Дейкстры в трейдинге
Алгоритм Дейкстры — классическое решение по поиску кратчайшего пути в теории графов, которое позволяет оптимизировать торговые стратегии путем моделирования рыночных сетей. Трейдеры могут использовать его для поиска наиболее эффективных маршрутов в данных свечного графика.
Квантовая нейросеть на MQL5 (Часть III): Виртуальный квантовый процессор с кубитами Квантовая нейросеть на MQL5 (Часть III): Виртуальный квантовый процессор с кубитами
Создаем торговую систему с настоящим квантовым симулятором вместо математических аналогий. Система использует 3 виртуальных кубита, квантовые гейты и принципы суперпозиции для анализа рынков. Реализована как торговый советник для MetaTrader 5 на MQL5. Главное достижение — переход от имитации к реальным квантовым принципам обработки финансовой информации.