Español Português
preview
Моделирование рынка (Часть 08): Сокеты (II)

Моделирование рынка (Часть 08): Сокеты (II)

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

Введение

В предыдущей статье, "Моделирование рынка (Часть 07): Сокеты (I)", мы показали первые шаги в изучении сокетов. Однако, возможно, приложение, которое мы рассмотрели, было не таким захватывающим. На самом деле, это была скорее программа "HELLO WORLD" - первая программа, которую мы попытались сделать, когда начали изучать программирование. Это первый шаг на долгом пути обучения.

Ну что ж, раз так, то в этой статье мы покажем, как сделать кое-что более интересным. Честно говоря, это не будет особенно полезно, но, изучая соответствующие концепции, можно будет поэкспериментировать и весело провести время. Как и в предыдущей статье, нам также понадобится внешнее программирование. Причина использования внешнего программирования та же, что и в предыдущей статье: мы не хотим добавлять DLL-библиотеки в MetaTrader 5, по крайней мере пока.

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


Планирование

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

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

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


Базовая реализация

Данная часть реализации будет довольно интересной, особенно когда речь идет о MQL5, поскольку мы будем использовать своеобразный способ выполнения задач. Сначала нам нужно создать окно для взаимодействия с мини-чатом. Самый простой способ сделать это в MQL5 (чтобы использовать его в MetaTrader 5), - это индикатор. Но есть один важный момент: индикаторам не разрешается использовать сокеты, потому что, в зависимости от того, как запрограммирован сокет, он может блокировать поток вычислений индикаторов. Это связано с тем, что все индикаторы являются частью одной области. Как же создать такое окно? Простейший код выглядит так:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_separate_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.    return INIT_SUCCEEDED;
09. }
10. //+------------------------------------------------------------------+
11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
12. {
13.    return rates_total;
14. }
15. //+------------------------------------------------------------------+

Начальный код индикатора

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. //+------------------------------------------------------------------+
05. #define def_IndicatorMiniChat   "Indicators\\Mini Chat\\Mini Chat.ex5"
06. #resource "\\" + def_IndicatorMiniChat
07. //+------------------------------------------------------------------+
08. long gl_id;
09. int subWin;
10. //+------------------------------------------------------------------+
11. int OnInit()
12. {
13.    gl_id = ChartID();
14.    subWin = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL);
15.    
16.    ChartIndicatorAdd(gl_id, subWin, iCustom(NULL, 0, "::" + def_IndicatorMiniChat));
17. 
18.    return INIT_SUCCEEDED;
19. }
20. //+------------------------------------------------------------------+
21. void OnDeinit(const int reason)
22. {
23.    ChartIndicatorDelete(gl_id, subWin, ChartIndicatorName(gl_id, subWin, 0));
24. }
25. //+------------------------------------------------------------------+
26. void OnTick()
27. {
28. }
29. //+------------------------------------------------------------------+
30. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
31. {
32. }
33. //+------------------------------------------------------------------+

Код запуска советника

Это основной каркас нашего советника по открытию окна. Однако прошу заметить, что в строке 05 мы определяем, что в строке 06 мы будем использовать индикатор в качестве внутреннего ресурса советника. Зачем мы так делаем? Причина в том, что мы хотим, чтобы индикатор создал для нас окно. А советник не может этого сделать. Именно поэтому мы используем индикатор.

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

Как же решить данную проблему? Всё очень просто. Мы изменим индикатор так, чтобы пользователь не мог поместить его на график. Однако после компиляции советника исполняемый файл индикатора может быть удален, потому что индикатор будет встроен в советник. В любом случае, теперь посмотрим, как решить проблему в случае, если пользователь может получить доступ к исполняемому файлу индикатора и разместить его на графике. Решение можно увидеть в приведенном ниже коде:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Base indicator for Mini Chat."
04. #property description "It cannot be used without outside assistance."
05. #property version   "1.00"
06. #property indicator_chart_window
07. #property indicator_plots 0
08. //+------------------------------------------------------------------+
09. #define def_ShortName "Mini Chat"
10. //+------------------------------------------------------------------+
11. int OnInit()
12. {
13.    long id = ChartID();
14.    string sz0 = def_ShortName + "_TMP";
15.    int i0;
16.    
17.    IndicatorSetString(INDICATOR_SHORTNAME, sz0);
18.    for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--)
19.       if (ChartIndicatorName(id, c0, 0) == def_ShortName)
20.       {
21.          ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0);
22.          return INIT_FAILED;
23.       }
24.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);   
25.    i0 = ChartWindowFind(id, def_ShortName);
26.    if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1))
27.    {
28.       ChartIndicatorDelete(id, i0, def_ShortName);
29.       return INIT_FAILED;
30.    }
31.    
32.    return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.    return rates_total;
38. }
39. //+------------------------------------------------------------------+

Первая модификация кода индикатора

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

В строке 09 мы определяем имя индикатора. Это имя будет важно для нас во время тестирования. Теперь обратите внимание: чтобы удалить индикатор из списка, нам нужно, чтобы у него было имя. Данное имя ДОЛЖНО БЫТЬ НЕ ОКОНЧАТЕЛЬНЫМ, а временным. Это делается в строке 14. Затем мы задаем это временное имя в качестве имени индикатора, что делается в строке 17.

Теперь самое интересное: В цикле в строке 18 мы будем искать название индикатора в списке индикаторов на графике. Однако имя, которое мы будем искать, находится в строке 09. Тест, который это делает, находится в строке 19. Когда индикатор найден, выполняется строка 21. Данная строка удалит не тот индикатор, который уже находится на графике, а тот, который мы пытаемся разместить. Сразу после этого, в строке 22, мы сообщаем, что инициализация завершилась неуспешно.

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

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

Но сначала нам нужно узнать, где разместим индикатор. Это получается в строке 25. Прошу заметить: если индикатор расположен в главном окне, то значение строки 25 будет равно нулю. Иначе он должен указывать, в каком подокне размещается индикатор. Поэтому при проверке строки 26 мы анализируем данное значение. Если значение равно нулю, индикатор надо удалить. Мы также сделаем это, если в указанном окне имеется более одного индикатора.

Теперь вы, может быть, немного запутались. «Если советник собирается поместить индикатор на график, разве он не будет делать это в главном окне? Разве не на это указывает строка 06, где говорится, что индикатор будет размещен в главном окне?» В действительности, когда советник помещает индикатор на график, он создает новое окно на нем. Поэтому, если значение, возвращаемое в строке 25, равно нулю, мы можем смело удалить индикатор. Но мы также можем удалить его, если в том же возвращаемом окне есть любой другой элемент, так как это укажет на то, что именно пользователь пытается разместить индикатор.

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


Реализуем объекты взаимодействия

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay         "RePlay"
14. #define def_MaxPosSlider          400
15. #define def_MaskTimeService      0xFED00000
16. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame         4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.    double    dValue;
22.    long      _long;                                 // 1 Information
23.    datetime _datetime;                              // 1 Information
24.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents    {
30.          evTicTac,                     //Event of tic-tac
31.          evHideMouse,                  //Hide mouse price line
32.          evShowMouse,                  //Show mouse price line
33.          evHideBarTime,                //Hide bar time
34.          evShowBarTime,                //Show bar time
35.          evHideDailyVar,               //Hide daily variation
36.          evShowDailyVar,               //Show daily variation
37.          evHidePriceVar,               //Hide instantaneous variation
38.          evShowPriceVar,               //Show instantaneous variation
39.          evCtrlReplayInit,             //Initialize replay control
40.          evChartTradeBuy,              //Market buy event
41.          evChartTradeSell,             //Market sales event 
42.          evChartTradeCloseAll,         //Event to close positions
43.          evChartTrade_At_EA,           //Event to communication
44.          evEA_At_ChartTrade,           //Event to communication
45.          evChatWriteSocket,            //Event to Mini Chat
46.          evChatReadSocket              //Event To Mini Chat
47.                   };
48. //+------------------------------------------------------------------+

Исходный код файла Defines.mqh

Прошу заметить, что в старом файле Defines.mqh появились две новые строки: 45 и 46. Эти строки обеспечивают связь через сокеты. Данные события были добавлены только потому, что мы будем отделять мини-чат от внутреннего кода советника. То есть нам нужно сделать так, чтобы индикатор мог отображать сообщения, полученные через сокет. Так как индикатор не может использовать сокеты, мы попросим советника следить за сокетом и отправлять доступные сообщения на мини-чат. Тогда мы сможем получить доступ к ним.

Хорошо. Теперь давайте посмотрим на заголовочный файл, который отвечает за создание объектов на графике. Мы видим его чуть ниже:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include  "..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ShortName    "Mini Chat"
007. #define def_MaxRows      256
008. #define def_FontName     "Lucida Console"
009. #define def_FontSize     12
010. #define def_SizeControls (m_txtHeight + 6)
011. #define macroColorRGBA(A) ((uint)((0xFF << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16)))
012. //+------------------------------------------------------------------+
013. class C_Chat
014. {
015.    private   :
016.       long   m_id;
017.       int    m_sub;
018.       int    m_txtHeight;
019.       bool   m_full;
020.       ushort m_Width,
021.              m_Height,
022.              m_index;
023.       uint   m_Pixel[];
024.       string m_ObjEdit,
025.              m_ObjBtn,
026.              m_ObjPanel;
027.       struct st0
028.       {
029.          string info;
030.          bool   loc;
031.       }m_Msgs[def_MaxRows + 1];
032. //+------------------------------------------------------------------+
033.       void Add(string szMsg, bool isloc = false)
034.       {
035.          m_Msgs[m_index].info = szMsg;
036.          m_Msgs[m_index].loc = isloc;
037.          if ((++m_index) > def_MaxRows)
038.          {
039.             m_full = true;
040.             m_index = 0;
041.          }
042.          Paint();
043.       };
044. //+------------------------------------------------------------------+
045.       void Paint(void)
046.       {
047.          int max, count, p0, p1;
048.          
049.          ArrayInitialize(m_Pixel, macroColorRGBA(clrBlack));
050.          if ((p0 = m_Height - def_SizeControls) < 0) return;
051.          max = (int)(floor(p0 / (m_txtHeight * 1.0)));
052.          p1 = m_index - max;
053.          if (m_full)
054.             count = (max > def_MaxRows ? m_index + 1 : (p1 > 0 ? p1 : (def_MaxRows + p1 + 1)));
055.          else
056.             count = (p1 > 0 ? p1 : 0);         
057.          for (ushort row = 0; row < p0; count++)
058.          {
059.             count = (count > def_MaxRows ? 0 : count);
060.             if (count == m_index) break;
061.             TextOut(m_Msgs[count].info, 2, row, 0, m_Pixel, m_Width, m_Height, macroColorRGBA(m_Msgs[count].loc ? clrSkyBlue : clrLime), COLOR_FORMAT_ARGB_NORMALIZE);
062.             row += (ushort) m_txtHeight;
063.          }
064.          ResourceCreate("::" + m_ObjPanel, m_Pixel, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);
065.          ChartRedraw();
066.       }
067. //+------------------------------------------------------------------+
068.       void CreateObjEdit(const string szArg)
069.       {
070.          ObjectCreate(m_id, szArg, OBJ_EDIT, m_sub, 0, 0);
071.          ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2);
072.          ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0);
073.          ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls);
074.          ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName);
075.          ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize);
076.          ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrDarkGray);
077.          ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack);
078.          ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrNavy);
079.       }
080. //+------------------------------------------------------------------+
081.       void CreateObjButton(const string szArg, const string szTxt)
082.       {
083.          ObjectCreate(m_id, szArg, OBJ_BUTTON, m_sub, 0, 0);
084.          ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0);
085.          ObjectSetInteger(m_id, szArg, OBJPROP_XSIZE, 70);
086.          ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls);
087.          ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName);
088.          ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize);
089.          ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrSkyBlue);
090.          ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack);
091.          ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrBlack);
092.          ObjectSetString(m_id, szArg, OBJPROP_TEXT, szTxt);
093.       }
094. //+------------------------------------------------------------------+
095.       void CreateObjPanel(const string szArg)
096.       {      
097.          ObjectCreate(m_id, szArg, OBJ_BITMAP_LABEL, m_sub, 0, 0);
098.          ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2);
099.          ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, m_txtHeight + 8);
100.          ObjectSetString(m_id, szArg, OBJPROP_BMPFILE, "::" + m_ObjPanel);
101.       }
102. //+------------------------------------------------------------------+
103.    public   :
104. //+------------------------------------------------------------------+
105.       C_Chat()
106.          :m_index(0),
107.           m_full(false),
108.           m_Width(0),
109.           m_Height(0)
110.       {         
111.          int tmp;
112.          
113.          m_sub = ChartWindowFind(m_id = ChartID(), def_ShortName);
114.          TextSetFont(def_FontName, -10 * def_FontSize, 0, 0);
115.          TextGetSize("M", tmp, m_txtHeight);
116.          CreateObjEdit(m_ObjEdit = def_ShortName + " Edit" + (string)ObjectsTotal(m_id));
117.          CreateObjButton(m_ObjBtn = def_ShortName + " Button" + (string)ObjectsTotal(m_id), "Send");
118.          CreateObjPanel(m_ObjPanel = def_ShortName + " Panel" + (string)ObjectsTotal(m_id));
119.       }
120. //+------------------------------------------------------------------+
121.       ~C_Chat()
122.       {
123.          ObjectsDeleteAll(m_id, def_ShortName);
124.       };
125. //+------------------------------------------------------------------+
126.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
127.       {
128.          switch(id)
129.          {
130.             case CHARTEVENT_CHART_CHANGE:
131.                m_Width = (ushort)ChartGetInteger(m_id, CHART_WIDTH_IN_PIXELS, m_sub);
132.                m_Height = (ushort)ChartGetInteger(m_id, CHART_HEIGHT_IN_PIXELS, m_sub);
133.                ObjectSetInteger(m_id, m_ObjEdit, OBJPROP_XSIZE, m_Width - 75);
134.                ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_XDISTANCE, m_Width - 72);
135.                ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_XSIZE, m_Width - 4);
136.                ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_YSIZE, m_Height - 4);
137.                ArrayResize(m_Pixel, m_Width * m_Height);
138.                Paint();
139.                break;
140.             case CHARTEVENT_OBJECT_CLICK:
141.                if (sparam == m_ObjBtn)
142.                {
143.                   string sz0 = ObjectGetString(m_id, m_ObjEdit, OBJPROP_TEXT);
144.                   if (sz0 != "")
145.                   {
146.                      EventChartCustom(m_id, evChatWriteSocket, 0, 0, sz0);
147.                      Add(sz0, true);
148.                      ObjectSetString(m_id, m_ObjEdit, OBJPROP_TEXT, "");
149.                      ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_STATE, 0);
150.                   }                  
151.                }
152.                break;
153.             case CHARTEVENT_CUSTOM + evChatReadSocket:
154.                Add(sparam);
155.                break;
156.             }
157.       }
158. //+------------------------------------------------------------------+
159. };
160. //+------------------------------------------------------------------+
161. #undef macroColorRGBA
162. #undef def_MaxRows
163. //+------------------------------------------------------------------+

Код файла C_Chat.mqh

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

Теперь перейдем к объяснениям. В строке 04 мы добавляем заголовочный файл, содержащий некоторые из наших определений. Но на самом деле мы будем использовать те определения, которые только что добавили. Итак, в строке 06 мы определяем имя для нашего индикатора. В строке 07 мы определяем максимальное количество строк, которые будет отображать наш мини-чат. Позже вы лучше поймете это значение. В строках 08 и 09 мы задаем название и размер используемого шрифта. Это позволит нам легче настраивать эти параметры, поскольку, как только мы зададим размеры здесь, весь мини-чат будет подстраиваться под них. В строке 10 указывается высота элементов управления, чтобы избежать проблем с их отображением. Строка 11 - это просто макрос для размещения текста на графике. Потом вы поймете, как это делается.

Итак, после того, как внесли эти определения, мы можем приступить к работе над нашим классом. В строке 15 мы объявляем приватную часть, так что все переменные и функции, появляющиеся после этого условия, будут принадлежать только классу C_Chat. Переменные, определенные между строками 16 и 26, являются минимально необходимыми. Но если вы хотите добавить в мини-чат больше функциональности, вам придется подумать об увеличении количества объектов. Так что сделайте это в данный момент. Структура, объявленная в строке 27, служит вспомогательной памятью, позволяя быстро получать доступ к сообщениям, размещенным другими пользователями.

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

Теперь посмотрите на строку 31. Здесь мы используем объявление, сделанное в строке 07. Но обратите внимание, что мы прибавим единицу. Это связано с тем, что, как и в C/C++, в MQL5 начинаем считать с нуля. Поэтому, добавляя единицу, мы указываем, что действительно хотим сохранить последние 256 строк, как указано в строке 07. Можно установить больше или меньше строк - это зависит от вас.

Теперь начинается самая интересная часть: процедуры и функции, которые позволяют мини-чату работать. Первая процедура находится в строке 33. Её цель - добавить новые сообщения в структуру, объявленную в строке 27. Здесь мы просто присваиваем значения, соответствующим своим величинам. Это делается в строках 35 и 36.

А теперь внимание: В строке 37 мы проверяем, достиг ли наш счетчик лимита сообщений. Когда это произойдет, мы должны сбросить счетчик в нулевое положение. И это делается в строке 40. Чтобы узнать, когда данный лимит будет достигнут, в строке 39 мы присваиваем переменной значение true, указывающее на то, что список переполнен сообщениями.

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

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

Но до того, как рассмотреть эту процедуру, давайте проанализируем некоторые другие моменты, чтобы правильно понять процедуру в строке 45. После этого мы переходим в строку 95. В ней мы создадим панель, которая будет содержать текст кругового списка. Мы делаем это очень просто. Итак, в строке 97 мы создаем объект OBJ_BITMAP_LABEL. В строках 98 и 99 мы указываем местоположение верхнего угла объекта. Однако, действительно важной является строка 100, поскольку именно в ней мы указываем, какое растровое изображение будет использоваться.

«Но подождите секунду. Что вы имеете в виду под растровым изображением? Если наш мини-чат будет показывать только текстовые сообщения, зачем использовать растровое изображение? Это бессмысленно». На самом деле, мы будем отображать текстовые сообщения, но в MQL5 нет простого способа разместить текст непосредственно на графике именно так, как нам нужно. Функции библиотеки Comment для нас недостаточно, к тому же она и не подходит. Мы действительно должны контролировать, где и как будет отображаться текст. Поэтому правильнее всего использовать растровое изображение, т.е. MetaTrader 5 нарисует буквы на растровом изображении, которое будет отображаться для чтения на графике.

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

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

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

Таким образом, мы производим корректировку между строками 53 и 56. Да, переменная, которая будет использоваться для подсчета, на самом деле указывает на самую старую строку, которая будет отображаться. Затем мы входим в цикл в строке 57. Данный цикл может показаться вам странным, но он очень прост. Всё, что он делает, - проходит по круговому списку позицию за позицией и в строке 61 использует вызов библиотеки MQL5 для рисования текста на растровом изображении.

Когда данный цикл завершится, растровое изображение будет содержать нарисованный на нем текст. Далее нам нужно сообщить MetaTrader 5, что растровое изображение теперь может быть использовано. Это делается в строке 64. Прошу заметить последовательность событий: сначала мы определяем имя растрового изображения, затем рисуем его и, наконец, сообщаем MetaTrader 5, что оно может быть отображено на графике. Это происходит в строке 64. Всё просто, не так ли?

Процедуры между строками 68 и 93 являются практически обычными в программировании на MQL5, поэтому они не требуют дополнительных объяснений. Однако мы ещё не закончили. Теперь обратимся к конструктору класса, который находится после объявления публичной части в строке 103; всё, что находится после строки 103, будет видно за пределами класса. В конструкторе, присутствующем в строке 105, мы инициализируем наш мини-чат. Это делается за несколько довольно просто шагов.

Сначала нам нужно захватить индекс окна, в котором будет отображаться мини-чат. Это делается в строке 113, а затем в строке 114 мы сообщаем MetaTrader 5, как определится шрифт, который будет использоваться для создания растрового текста. В строке 115 мы захватываем высоту букв, поскольку в зависимости от настройки ОС у них может быть разный размер. Таким образом, чтобы избежать непропорционального представления, учитываем высоту текста.

Затем, между строками 116 и 118, мы создаем необходимые объекты. Если вам нужно добавить больше объектов, сделайте это на данном этапе, но обязательно следуйте правилам создания объектов, указанным в коде.

Деструктор из строки 121, выполняет только задачу удаления созданных объектов с графика. Поэтому он содержит только строку 123.

Наконец, мы переходим к строке 126, где обрабатываем сообщения, которые MetaTrader 5 будет посылать нашему индикатору. Помните, что мы всё ещё находимся внутри кода индикатора. Чтобы максимально упростить задачу, мы здесь поработаем только с тремя типами событий или сообщений. Один из них - CHARTEVENT_CHART_CHANGE, который определит правильное положение и размер объектов. Это первое сообщение, которое выдает MetaTrader 5, как только поместим наш код на графике.

Мы также обрабатываем сообщение CHARTEVENT_OBJECT_CLICK, которое генерируется при нажатии на график. В этом случае в строке 141 мы проверяем, был ли сделан клик по кнопке отправки сообщения. Если ответ да, то в строке 143 мы захватываем текст из области редактирования, чтобы сразу же отправить его. Однако в строке 144 мы проверяем, пуст ли текст или нет. Если не пуст, то в строке 146 мы запускаем пользовательское событие, чтобы отправить этот же текст советнику.

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

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

В качестве последней части кода, которую мы рассмотрим в этой статье, давайте взглянем на финальный код для индикатора MiniChat. Можно ознакомиться с ним чуть ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Base indicator for Mini Chat."
04. #property description "It cannot be used without outside assistance."
05. #property version   "1.00"
06. #property link "https://www.mql5.com/pt/articles/12672"
07. #property indicator_chart_window
08. #property indicator_plots 0
09. //+------------------------------------------------------------------+
10. #include <Market Replay\Mini Chat\C_Chat.mqh>
11. //+------------------------------------------------------------------+
12. C_Chat *Chat;
13. //+------------------------------------------------------------------+
14. int OnInit()
15. {
16.    long id = ChartID();
17.    string sz0 = def_ShortName + "_TMP";
18.    int i0;
19.    
20.    IndicatorSetString(INDICATOR_SHORTNAME, sz0);
21.    for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--)
22.       if (ChartIndicatorName(id, c0, 0) == def_ShortName)
23.       {
24.          ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0);
25.          return INIT_FAILED;
26.       }
27.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);   
28.    i0 = ChartWindowFind(id, def_ShortName);
29.    if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1))
30.    {
31.       ChartIndicatorDelete(id, i0, def_ShortName);
32.       return INIT_FAILED;
33.    }
34.    
35.    Chat = new C_Chat();
36.    
37.    return INIT_SUCCEEDED;
38. }
39. //+------------------------------------------------------------------+
40. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
41. {
42.    return rates_total;
43. }
44. //+------------------------------------------------------------------+
45. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
46. {
47.    (*Chat).DispatchMessage(id, lparam, dparam, sparam);
48. }
49. //+------------------------------------------------------------------+
50. void OnDeinit(const int reason)
51. {
52.    delete Chat;
53. }
54. //+------------------------------------------------------------------+

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


Заключительные идеи

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

ФайлОписание
Experts\Expert Advisor.mq5
Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study).
Indicators\Chart Trade.mq5Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study).
Indicators\Market Replay.mq5Создайте элементы управления для взаимодействия с сервисом репликации/моделирования (для взаимодействия требуется Mouse Study).
Indicators\Mouse Study.mq5Позволяет взаимодействовать между графическими элементами управления и пользователем (необходимо как для воспроизведения, так и для торговли на реальном рынке).
Servicios\Market Replay.mq5Создает и поддерживает сервис репликации/моделирования рынка (основной файл всей системы).
Код VS C++ Server.cppСоздает и поддерживает сокет-сервер, разработанный на C++ (версия мини-чат).
Код на Python Server.pyСоздание и поддержка сокета Python для связи между MetaTrader 5 и Excel.
ScriptsCheckSocket.mq5Позволяет проверить соединение с внешним сокетом.
Indicators\Mini Chat.mq5Позволяет реализовать мини-чат с помощью индикатора (для работы требуется сервер).
Experts\Mini Chat.mq5Позволяет реализовать мини-чат в советнике (для работы требуется использование сервера).

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

Прикрепленные файлы |
Anexo.zip (560.03 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Торгуем опционы без опционов (Часть 4): Более сложные опционные стратегии Торгуем опционы без опционов (Часть 4): Более сложные опционные стратегии
В этой статье мы рассмотрим, как можно снизить риски (и возможно ли это сделать) для опционных стратегий, где изначально риск не ограничен. Это относится к стратегиям, основанным на продаже опционов, то есть к флэтовым. Также рассмотрим варианты фиксации прибыли для опционных стратегий, основанных на покупке опционов, то есть трендовых. Как всегда, добавим в наш эксперт новые полезные функции и улучшим старые.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Тенденции и традиции: Использование функций Радемахера в трейдинге Тенденции и традиции: Использование функций Радемахера в трейдинге
Несмотря на то, что функции, о которых пойдет речь, известны уже довольно давно, их применение в области трейдинга до сих пор остается terra incognita. В этой статье мы рассмотрим некоторые возможности, которые эти новые старые функции открывают для разработки торговых стратегий, и оценим их потенциал.