Español Português
preview
От начального до среднего уровня: Объекты (III)

От начального до среднего уровня: Объекты (III)

MetaTrader 5Примеры |
46 0
CODE X
CODE X

Введение

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

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

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

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

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


Пользовательский индикатор

То, что мы хотим здесь показать, уже было темой другой нашей статьи Разработка системы репликации (Часть 30): Проект советника — класс C_Mouse (IV) . Там мы показали, как реализовать индикатор мыши с помощью объектно-ориентированного программирования (ООП). Но здесь мы увидим нечто похожее, хотя и с гораздо более простой программой, ориентированной на начинающих. Как и вы, уважаемый читатель.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10. //+----------------+    
11.     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
12.     ChartSetInteger(0, CHART_CONTEXT_MENU, false);
13.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
14. //+----------------+
15.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, false);
16.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
17. //+----------------+
18. 
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
28. {
29.     string sz = "";
30. 
31.     switch (id)
32.     {
33.         case CHARTEVENT_KEYDOWN:
34.             break;
35.         case CHARTEVENT_MOUSE_MOVE:
36.             Comment(sz);
37.             sz += "Mouse position X: " + (string)(short)lparam;
38.             sz += "\nMouse position Y: " + (string)(short)dparam;
39.             Comment(sz);
40.             if ((uchar)sparam != 0) PrintFormat("Hexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
41.             break;
42.         case CHARTEVENT_MOUSE_WHEEL:
43.             break;
44.     }
45. };
46. //+------------------------------------------------------------------+
47. void OnDeinit(const int reason)
48. {
49.     Comment("");
50.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
51. //+----------------+    
52.     ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
53.     ChartSetInteger(0, CHART_CONTEXT_MENU, true);
54.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
55. //+----------------+
56.     ChartSetInteger(0, CHART_KEYBOARD_CONTROL, true);
57.     ChartSetInteger(0, CHART_QUICK_NAVIGATION, true);
58. //+----------------+
59. };
60. //+------------------------------------------------------------------+

Код 01

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

Изображение 01

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

Если вы практиковались с кодом, показанным в предыдущей статье, вы должны были заметить, что каждый из вызовов, осуществленных в событии Init, то есть в функции OnInit, которая находится на строке 06 из кода 01, включает или выключает стандартное свойство MetaTrader 5. Однако, поскольку нам нужно создать только пользовательское перекрестие анализа, нам не нужно отключать все эти события, так как это слишком сильно ограничивает использование MetaTrader 5 для некоторых пользователей. Лучше всего отключить только то событие, которое мы не хотим, чтобы MetaTrader 5 использовал по умолчанию. В данном случае это будет событие в строке 13 из кода 01.

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

В статье От начального до среднего уровня: Объекты (II) мы показали, как можно одновременно работать с двумя объектами на графике: вертикальной и горизонтальной линиями.

Итак, оба упомянутых объекта НЕ ИСПОЛЬЗУЮТ ЭКРАНОВЫЕ КООРДИНАТЫ, поскольку координаты X и Y известны применительно к компьютерной графике. Оба объекта, OBJ_VLINE и OBJ_HLINE, используют координаты котировок. В этих координатах используются цена и время. А поскольку операционная система предоставляет нам координаты X и Y, нам необходимо преобразовать эти экранные координаты в котировочные координаты. Выполнение данной задачи вручную — довольно утомительное дело, поскольку необходимо учитывать минимальную и максимальную цену, а также минимальное и максимальное время, чтобы установить взаимосвязь между ценой и временем со значениями X и Y, предоставляемыми операционной системой.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
09.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
10.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
11. 
12.     return INIT_SUCCEEDED;
13. };
14. //+------------------------------------------------------------------+
15. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
16. {
17.     return rates_total;
18. };
19. //+------------------------------------------------------------------+
20. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
21. {
22.     string      sz = "";
23.     short       x, y;
24.     int         sub;
25.     datetime    dt;
26.     double      price;
27. 
28.     switch (id)
29.     {
30.         case CHARTEVENT_KEYDOWN:
31.             break;
32.         case CHARTEVENT_MOUSE_MOVE:
33.             x = (short)lparam;
34.             y = (short)dparam;
35.             ChartXYToTimePrice(0, x, y, sub, dt, price);
36.             Comment(sz);
37.             sz += "Mouse position X: " + (string)x;
38.             sz += "\nMouse position Y: " + (string)y;
39.             sz += "\nMouse position time: " + TimeToString(dt, TIME_DATE | TIME_MINUTES);
40.             sz += "\nMouse position price: " + (string)price;
41.             Comment(sz);
42.             if ((uchar)sparam != 0) PrintFormat("Hexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
43.             break;
44.         case CHARTEVENT_MOUSE_WHEEL:
45.             break;
46.     }
47. };
48. //+------------------------------------------------------------------+
49. void OnDeinit(const int reason)
50. {
51.     Comment("");
52.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
53.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
54. };
55. //+------------------------------------------------------------------+

Код 02

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

Анимация 01

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

В самом деле эти значения отображаются именно потому, что процедура ChartXYToTimePrice НЕ ПРЕОБРАЗУЕТ значения в ожидаемый нами формат, а преобразует экранные координаты в котировочные координаты. А поскольку небольшие колебания по оси приводят к дробным значениям, эти значения в конечном итоге становятся значениями, присутствующими в течение дня. Вот почему мы видим значения, которые на первый взгляд кажутся бессмысленными. Следовательно, это не ошибка, а всего лишь мелочь, связанная с переходом от одного типа координат к другому.

В любом случае, исходя из того, что мы здесь рассмотрели, и проявив немного воображения и здравого смысла, вы скоро поймете, что у нас уже есть всё необходимое, чтобы подумать о том, как управлять объектами OBJ_HLINE и OBJ_VLINE. Это изменяет то, что было показано в статье От начального до среднего уровня: Объекты (II), где управление осуществлялось с помощью клавиатуры. Но сейчас мы воспользуемся для этого мышью.

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)ObjectsTotal(0)
07. //+------------------------------------------------------------------+
08. string  gl_Objs[2];
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
13.     ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0);
14.     ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0);
15.     ObjectSetString(0, gl_Objs[0], OBJPROP_TOOLTIP, "\n");
16.     ObjectSetString(0, gl_Objs[1], OBJPROP_TOOLTIP, "\n");
17.     ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrBlue);
18.     ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrBlue);
19. 
20.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
21.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
22. 
23.     return INIT_SUCCEEDED;
24. };
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. };
30. //+------------------------------------------------------------------+
31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
32. {
33.     int         sub;
34.     datetime    dt;
35.     double      price;
36. 
37.     switch (id)
38.     {
39.         case CHARTEVENT_KEYDOWN:
40.             break;
41.         case CHARTEVENT_MOUSE_MOVE:
42.             ChartXYToTimePrice(0, (short)lparam, (short)dparam, sub, dt, price);
43.             ObjectMove(0, gl_Objs[0], 0, dt, price);
44.             ObjectMove(0, gl_Objs[1], 0, dt, price);
45.             if ((uchar)sparam != 0) PrintFormat("Hexadecimal mask of mouse buttons is: 0x%02X", (uchar)sparam);
46.             break;
47.         case CHARTEVENT_MOUSE_WHEEL:
48.             break;
49.     }
50.     ChartRedraw();
51. };
52. //+------------------------------------------------------------------+
53. void OnDeinit(const int reason)
54. {
55.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
56.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
57. 
58.     ObjectsDeleteAll(0, def_Prefix);
59.     ChartRedraw();
60. };
61. //+------------------------------------------------------------------+

Код 03

Применение кода 03 к графику даст результат, похожий на этот:

Анимация 02

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

Поскольку то, что здесь делается, уже было частично рассмотрено в статье От начального до среднего уровня: Объекты (II), мы сосредоточимся только на том, что здесь нового. В данном случае движение теперь связано именно со значениями, возвращаемыми процедурой библиотеки при преобразовании экранных координат в котировочные координаты.

Но это было не совсем то, чего мы хотели. Мы хотели отобразить то же самое перекрестие, но только после нажатия центральной кнопки. Таким образом, для этого нам нужно внести некоторые изменения в код. Но прежде, давайте разберемся, зачем нужны строки 15, 16 и 58. Хорошо, строки 15 и 16 мешают MetaTrader 5 отображать небольшое сообщение, когда мы находимся над объектом. Если вы хотите отобразить сообщение, которое поясняет сам объект, но не хотите, чтобы оно постоянно оставалось на графике, используйте прием из строк 15 и 16, чтобы разместить там содержательное сообщение. Данное сообщение будет отображаться, когда курсор мыши в течение короткого времени остается неподвижным над объектом, на который он указывает.

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #define def_Prefix  "Demo"
005. //+------------------------------------------------------------------+
006. #define macro_NameObject  def_Prefix + (string)ObjectsTotal(0)
007. //+------------------------------------------------------------------+
008. enum eBtnMouse  {
009.         MOUSE_LEFT      = 0x01, 
010.         MOUSE_RIGHT     = 0x02,
011.         MOUSE_KEY_SHIFT = 0x04,
012.         MOUSE_KEY_CTRL  = 0x08,
013.         MOUSE_MIDDLE    = 0x10,
014.         MOUSE_EXTRA_1   = 0x20,
015.         MOUSE_EXTRA_2   = 0x40
016.                 };
017. //+------------------------------------------------------------------+
018. struct st_Cross
019. {
020.     private :
021. //+----------------+
022.         bool    IsView;
023.         string  Objs[2];
024. //+----------------+
025.         void Create(char index, ENUM_OBJECT type)
026.         {
027.             ObjectCreate(0, Objs[index] = macro_NameObject, type, 0, 0, 0);
028.             ObjectSetString(0, Objs[index], OBJPROP_TOOLTIP, "\n");
029.             ObjectSetInteger(0, Objs[index], OBJPROP_COLOR, clrBlue);
030.         }
031. //+----------------+
032.     public  :
033. //+----------------+
034.         void Show(void)
035.         {
036.             if (IsView) return;
037.             Create(0, OBJ_VLINE);
038.             Create(1, OBJ_HLINE);
039.             IsView = true;
040.             ChartRedraw();
041.         }
042. //+----------------+
043.         void Hide(void)
044.         {
045.             if (!IsView) return;
046.             for(uint c = 0; c < Objs.Size(); c++)
047.                 ObjectDelete(0, Objs[c]);
048.             ChartRedraw();
049.             IsView = false;
050.         }
051. //+----------------+
052.         void Move(short x, short y)
053.         {
054.             int         sub;
055.             datetime    dt;
056.             double      price;
057. 
058.             if (!IsView) return;
059.             ChartXYToTimePrice(0, x, y, sub, dt, price);
060.             ObjectMove(0, Objs[0], 0, dt, price);
061.             ObjectMove(0, Objs[1], 0, dt, price);
062.             ChartRedraw();
063.         }
064. //+----------------+
065. }gl_Cross;
066. //+------------------------------------------------------------------+
067. int OnInit()
068. {
069.     gl_Cross.Hide();
070.     
071.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
072.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
073.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
074. 
075.     return INIT_SUCCEEDED;
076. };
077. //+------------------------------------------------------------------+
078. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
079. {
080.     return rates_total;
081. };
082. //+------------------------------------------------------------------+
083. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
084. {
085. 
086.     switch (id)
087.     {
088.         case CHARTEVENT_KEYDOWN:
089.             break;
090.         case CHARTEVENT_MOUSE_MOVE:
091.             if (((uchar)sparam & MOUSE_MIDDLE) != 0) gl_Cross.Show();
092.             gl_Cross.Move((ushort)lparam, (ushort)dparam);
093.             break;
094.         case CHARTEVENT_MOUSE_WHEEL:
095.             break;
096.     }
097.     ChartRedraw();
098. };
099. //+------------------------------------------------------------------+
100. void OnDeinit(const int reason)
101. {
102.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
103.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
104. 
105.     gl_Cross.Hide();
106. };
107. //+------------------------------------------------------------------+

Код 04

Хорошо, но что делает этот код 04? В итоге произойдет то, что мы увидим в следующей анимации.

Анимация 03

"Интересно. Но я вижу, что вы перешли на использование структурированного кода. Почему?" Что ж, уважаемый читатель, причина именно в том, что структурированный код проще создавать и поддерживать для самого перекрестия. Помните, что цель структуры состоит именно в создании целого набора функций и процедур, которые напрямую связаны с конкретной целью.

В статье От начального до среднего уровня: Struct (VII), где объясняли, как структурировать код, всё было представлено в абстрактной форме. Но если мы рассмотрим это с менее абстрактной точки зрения, то всё начинает обретать смысл.

В данном случае, в коде 04, мы находимся на границе между структурированным кодом и объектно-ориентированным кодом. Это связано с тем, что в коде 04 видно, что некоторые точки необходимо инициализировать, прежде чем мы сможем использовать саму структуру. И это одна из причин, по которой было создано ООП. Обратите внимание на следующее: когда структура st_Cross объявляется в строке 65 для использования переменной gl_Cross, у нас нет уверенности в значениях, содержащихся в IsView и массиве Objs. Это затрудняет создание определенных типов кода, особенно тех кодов, которые предназначены для работы с конкретными видами данных.

Но поскольку здесь всё идет относительно спокойно, мы можем использовать строку 69 для подготовки структуры и начала работы с ней. Это не самый лучший способ, но, как уже говорилось, здесь мы имеем дело с чем-то относительно простым. Таким образом, при проверке в строке 91, была ли нажата центральная кнопка или нет, мы можем выбрать, отображать или нет объекты, которые создают простое перекрестие анализа. Это делается путем вызова процедуры Show структуры st_Cross.

В любом случае, в строке 92 мы скорректировали положение объектов таким образом, чтобы они были связаны с указателем мыши. Однако, если объекты отсутствуют на графике, значение IsView будет равно false. А в строке 58 это остановит выполнение и немедленно вернет управление вызывающей стороне, которой в данном случае является строка 92. И так будет продолжаться до тех пор, пока не будет нажата центральная кнопка, после чего перекрестие наконец-то будет создано. С этого момента обновление будет происходить при каждом передвижении мыши, поскольку в этом случае IsView будет иметь значение true, что позволит строке 92 обновить позицию, так как условие строки 58 больше не будет выполняться.

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

                   .
                   .
                   .
082. //+------------------------------------------------------------------+
083. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
084. {
085. 
086.     switch (id)
087.     {
088.         case CHARTEVENT_KEYDOWN:
089.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide();
090.             break;
091.         case CHARTEVENT_MOUSE_MOVE:
092.             if (((uchar)sparam & MOUSE_MIDDLE) != 0) gl_Cross.Show();
093.             gl_Cross.Move((ushort)lparam, (ushort)dparam);
094.             break;
095.         case CHARTEVENT_MOUSE_WHEEL:
096.             break;
097.     }
098.     ChartRedraw();
099. };
100. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 05

Это простое изменение, которое можно увидеть в строке 89, позволяет нам сделать то, что показано в следующей анимации.

Анимация 04

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

Изображение 02

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

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

                   .
                   .
                   .
                   
017. //+------------------------------------------------------------------+
018. struct st_Cross
019. {
020.     private :
021. //+----------------+
022.         bool    IsView;
023.         string  Objs[2];
024.         int     nDigits;
025.             double  PointPerTick;
026. //+----------------+
027.         void Create(char index, ENUM_OBJECT type)
028.         {
029.             ObjectCreate(0, Objs[index] = macro_NameObject, type, 0, 0, 0);
030.             ObjectSetString(0, Objs[index], OBJPROP_TOOLTIP, "\n");
031.             ObjectSetInteger(0, Objs[index], OBJPROP_COLOR, clrBlue);
032.         }
033. //+----------------+
034.     public  :
035. //+----------------+
036.         void Init(void)
037.         {
038.             nDigits         = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
039.             PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
040.             Hide();
041.         }
042. //+----------------+
                   .
                   .
                   .
060. //+----------------+
061.         void Move(short x, short y)
062.         {
063.             int         sub;
064.             datetime    dt;
065.             double      price;
066. 
067.             if (!IsView) return;
068.             ChartXYToTimePrice(0, x, y, sub, dt, price);
069.             price = NormalizeDouble(MathRound(price / PointPerTick) * PointPerTick, nDigits);
070.             ObjectMove(0, Objs[0], 0, dt, price);
071.             ObjectMove(0, Objs[1], 0, dt, price);
072.             ChartRedraw();
073.         }
074. //+----------------+
075. }gl_Cross;
076. //+------------------------------------------------------------------+
077. int OnInit()
078. {
079.     gl_Cross.Init();
080. 
081.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
082.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
083.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
084. 
085.     return INIT_SUCCEEDED;
086. };
087. //+------------------------------------------------------------------+
                   .
                   .
                   .

Код 06

Прошу заметить, что мы внесли очень простые изменения в фрагмент кода 06. Сначала мы добавляем новые переменные в структуру. Это помогает нам избежать необходимости постоянно читать одну и ту же информацию. Сразу после этого мы добавили новую процедуру: Init. Это происходит в строке 36, где мы инициализируем структуру несколько более подходящим способом. Поэтому в методе OnInit мы изменили вызов, который ранее выполнялся для процедуры Hide, и перенаправили его в Init, как можно видеть в строке 79.

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

Анимация 05

Просто идеально. С помощью предельно простого кода нам удалось создать наш основной и полностью настраиваемый индикатор. Теперь возникает вопрос: какие виды анализа мы можем провести с использованием данного индикатора? Потому что по этой теме не было проведено никаких конкретных анализов. Что ж, уважаемый читатель, на этот вопрос нет простого ответа. Это объясняется тем, что мы можем проводить абсолютно любые виды анализов. Всё зависит от того, чего мы действительно хотим достичь и как мы планируем использовать созданное нами в дальнейшем.

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

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. enum eBtnMouse  {
05.         MOUSE_LEFT      = 0x01, 
06.         MOUSE_RIGHT     = 0x02,
07.         MOUSE_KEY_SHIFT = 0x04,
08.         MOUSE_KEY_CTRL  = 0x08,
09.         MOUSE_MIDDLE    = 0x10,
10.         MOUSE_EXTRA_1   = 0x20,
11.         MOUSE_EXTRA_2   = 0x40
12.                 };
13. //+------------------------------------------------------------------+
14. struct st_TimePrice
15. {
16.     double      Price;
17.     datetime    Time;
18. };
19. //+------------------------------------------------------------------+
20. struct st_Cross
21. {
22.     private :
23. //+----------------+
24.         bool    IsView;
25.         string  Objs[2];
26.         int     nDigits;
27.         double      PointPerTick;
28. //+----------------+
29.         void Create(char index, ENUM_OBJECT type)
30.         {
31.             ObjectCreate(0, Objs[index] = macro_NameObject, type, 0, 0, 0);
32.             ObjectSetString(0, Objs[index], OBJPROP_TOOLTIP, "\n");
33.             ObjectSetInteger(0, Objs[index], OBJPROP_COLOR, clrBlue);
34.         }
35. //+----------------+
36.     public  :
37. //+----------------+
38.         void Init(void)
39.         {
40.             nDigits         = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
41.             PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
42.             Hide();
43.         }
44. //+----------------+
45.         void Show(void)
46.         {
47.             if (IsView) return;
48.             Create(0, OBJ_VLINE);
49.             Create(1, OBJ_HLINE);
50.             IsView = true;
51.             ChartRedraw();
52.         }
53. //+----------------+
54.         void Hide(void)
55.         {
56.             if (!IsView) return;
57.             for(uint c = 0; c < Objs.Size(); c++)
58.                 ObjectDelete(0, Objs[c]);
59.             ChartRedraw();
60.             IsView = false;
61.         }
62. //+----------------+
63.         st_TimePrice Move(short x, short y)
64.         {
65.             int             sub;
66.             st_TimePrice    tp;
67. 
68.             ChartXYToTimePrice(0, x, y, sub, tp.Time, tp.Price);
69.             tp.Price = NormalizeDouble(MathRound(tp.Price / PointPerTick) * PointPerTick, nDigits);
70.             if (IsView)
71.             {
72.                 ObjectMove(0, Objs[0], 0, tp.Time, tp.Price);
73.                 ObjectMove(0, Objs[1], 0, tp.Time, tp.Price);
74.                 ChartRedraw();
75.             }
76. 
77.             return tp;
78.         }
79. //+----------------+
80. };
81. //+------------------------------------------------------------------+

Код 07

А вот этот соответствует индикатору, который мы хотим внедрить:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)ObjectsTotal(0)
07. //+------------------------------------------------------------------+
08. #include <Tutorial\File 01.mqh>
09. //+------------------------------------------------------------------+
10. st_Cross gl_Cross;
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.     gl_Cross.Init();
15. 
16.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
17.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
18.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
19. 
20.     return INIT_SUCCEEDED;
21. };
22. //+------------------------------------------------------------------+
23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
24. {
25.     return rates_total;
26. };
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30. 
31.     switch (id)
32.     {
33.         case CHARTEVENT_KEYDOWN:
34.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide();
35.             break;
36.         case CHARTEVENT_MOUSE_MOVE:
37.             if (((uchar)sparam & MOUSE_MIDDLE) != 0) gl_Cross.Show();
38.             gl_Cross.Move((ushort)lparam, (ushort)dparam);
39.             break;
40.         case CHARTEVENT_MOUSE_WHEEL:
41.             break;
42.     }
43.     ChartRedraw();
44. };
45. //+------------------------------------------------------------------+
46. void OnDeinit(const int reason)
47. {
48.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
49.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
50. 
51.     gl_Cross.Hide();
52. };
53. //+------------------------------------------------------------------+

Код 08

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

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


Индикатор линии тренда

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)ObjectsTotal(0)
07. //+------------------------------------------------------------------+
08. #include <Tutorial\File 01.mqh>
09. //+------------------------------------------------------------------+
10. st_Cross gl_Cross;
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.     gl_Cross.Init();
15. 
16.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
17.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
18.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
19. 
20.     return INIT_SUCCEEDED;
21. };
22. //+------------------------------------------------------------------+
23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
24. {
25.     return rates_total;
26. };
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     st_TimePrice tp;
31.     static string isPaint = "";
32. 
33.     switch (id)
34.     {
35.         case CHARTEVENT_KEYDOWN:
36.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide();
37.             break;
38.         case CHARTEVENT_MOUSE_MOVE:
39.             if (((uchar)sparam & MOUSE_MIDDLE) != 0)
40.             {
41.                 gl_Cross.Show();
42.                 tp = gl_Cross.Move((ushort)lparam, (ushort)dparam);
43.                 if (isPaint == "")
44.                 {
45.                     ObjectCreate(0, isPaint = macro_NameObject, OBJ_TREND, 0, tp.Time, tp.Price);
46.                     ObjectSetInteger(0, isPaint, OBJPROP_SELECTABLE, true);
47.                     ObjectSetInteger(0, isPaint, OBJPROP_COLOR, clrMagenta);
48.                     ObjectSetInteger(0, isPaint, OBJPROP_WIDTH, 3);
49.                     ObjectSetInteger(0, isPaint, OBJPROP_RAY_RIGHT, true);
50.                 }
51.                 ObjectMove(0, isPaint, 1, tp.Time, tp.Price);
52.             }else
53.             {
54.                 isPaint = "";
55.                 gl_Cross.Hide();
56.             }
57.             break;
58.         case CHARTEVENT_MOUSE_WHEEL:
59.             break;
60.     }
61.     ChartRedraw();
62. };
63. //+------------------------------------------------------------------+
64. void OnDeinit(const int reason)
65. {
66.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
67.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
68. 
69.     gl_Cross.Hide();
70. 
71.     if (reason == REASON_REMOVE)
72.         ObjectsDeleteAll(0, def_Prefix);
73. };
74. //+------------------------------------------------------------------+

Код 09

Код 09 поможет в объяснении того, что не знают многие новички.

В программировании порядок множителей изменяет произведение.

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

Анимация 06

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

Изображение 03

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

Анимация 07

Иными словами, мы сразу же испытываем чувство гордости за то, что нам удалось из одного кода сделать другой. Но даже при этом здесь есть недостаток. Если мы попробуем создать новую линию тренда, как показано в анимации ниже, посмотрите, что произойдет:

Анимация 08

"О, теперь я ничего не понимаю. Как странно. Почему удалилась предыдущая линия тренда? А почему одна из пересекающихся линий не появилась, когда мы пытались создать новую линию тренда? Эта ситуация очень странная. Я не ожидал, что это произойдет, поскольку, судя по всему, код прекрасно работал бы при просмотре анимации 06". Итак, уважаемый читатель, проблема в том, что объекты создаются в неправильном порядке. Звучит странно. Но да, то, что мы видим в анимации 08, — это именно последствия создания объектов в неправильном порядке.

Если вы посмотрите на код 09, то заметите, что сначала мы создали перекрестие. Это делается в строке 41. Сразу после этого, в строке 45, мы построили линию тренда. Проблем с такой последовательностью создания не возникнет, при том условии, конечно, что не будет предпринята попытка создать новый объект. Когда это происходит, как можно видеть в анимации 08, у нас возникает проблема. Хотя на самом деле возникли бы две проблемы. Первая проблема заключается в том, что макрос в строке 6 из кода 09 не проверяет наличие уже существующих объектов на графике. Поэтому, если объекты перечислены с одинаковым префиксом, в какой-то момент мы столкнемся с объектом, уже присутствующим на графике.

Но как это возможно? Хорошо, давайте предположим, что изначально на графике нет объектов. После создания перекрестия у нас будет два объекта: один будет называться "Demo0", а другой — "Demo1". Всё идет нормально. Когда линия тренда будет создана в строке 45 из кода 09, у нас появится третий объект, "Demo2".

Теперь мы подошли к точке, соответствующей линии, показанной в анимации 06. При попытке создания новой линии тренда перекрестие будет пытаться создать объекты “Demo1” и “Demo2”. Но подождите минутку: на графике уже есть объект под названием "Demo2", который будет представлять собой предыдущую линию тренда. Да, уважаемый читатель, именно поэтому одна из линий перекрестия не будет создана. И это создает то, что можно увидеть в начале анимации 08. Однако, если убрать перекрестие, то исчезнут и "Demo1", и "Demo2", а вместе с ними и предыдущая линия тренда. И в этот момент на графике больше не будет никаких объектов, что вернет нас к исходному состоянию для построения линии тренда.

Следует отметить, что первая проблема возникла из-за подсчета объектов, присутствующих на графике. Но вторая проблема связана с порядком создания объектов. Таким образом, для устранения обеих проблем нам необходимо изменить код 09 и оставить его в этом виде:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Prefix  "Demo"
05. //+------------------------------------------------------------------+
06. #define macro_NameObject  def_Prefix + (string)(ObjectsTotal(0) + 1)
07. //+------------------------------------------------------------------+
08. #include <Tutorial\File 01.mqh>
09. //+------------------------------------------------------------------+
10. st_Cross gl_Cross;
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.     gl_Cross.Init();
15. 
16.     IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix);
17.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
18.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false);
19. 
20.     return INIT_SUCCEEDED;
21. };
22. //+------------------------------------------------------------------+
23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
24. {
25.     return rates_total;
26. };
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     st_TimePrice tp;
31.     static string isPaint = "";
32. 
33.     switch (id)
34.     {
35.         case CHARTEVENT_KEYDOWN:
36.             if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide();
37.             break;
38.         case CHARTEVENT_MOUSE_MOVE:
39.             if (((uchar)sparam & MOUSE_MIDDLE) != 0)
40.             {
41.                 tp = gl_Cross.Move((ushort)lparam, (ushort)dparam);
42.                 if (isPaint == "")
43.                 {
44.                     ObjectCreate(0, isPaint = macro_NameObject, OBJ_TREND, 0, tp.Time, tp.Price);
45.                     ObjectSetInteger(0, isPaint, OBJPROP_SELECTABLE, true);
46.                     ObjectSetInteger(0, isPaint, OBJPROP_COLOR, clrMagenta);
47.                     ObjectSetInteger(0, isPaint, OBJPROP_WIDTH, 3);
48.                     ObjectSetInteger(0, isPaint, OBJPROP_RAY_RIGHT, true);
49.                 }
50.                 ObjectMove(0, isPaint, 1, tp.Time, tp.Price);
51.                 gl_Cross.Show();
52.             }else
53.             {
54.                 isPaint = "";
55.                 gl_Cross.Hide();
56.             }
57.             break;
58.         case CHARTEVENT_MOUSE_WHEEL:
59.             break;
60.     }
61.     ChartRedraw();
62. };
63. //+------------------------------------------------------------------+
64. void OnDeinit(const int reason)
65. {
66.     ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
67.     ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true);
68. 
69.     gl_Cross.Hide();
70. 
71.     if (reason == REASON_REMOVE)
72.         ObjectsDeleteAll(0, def_Prefix);
73. };
74. //+------------------------------------------------------------------+

Код 10

Запустив код 10, мы увидим результат в следующей анимации:

Анимация 09

Прошу заметить, что теперь мы можем добавить столько линий тренда, сколько захотим. И это происходит без каких-либо проблем, поскольку строка 06 из кода 10 исправляет проблему конфликта имен. А перекрестие отображается только после создания линии тренда, которая будет размещена на графике. Это можно увидеть в строке 51, где мы фактически создали простое перекрестие анализа. Иными словами, простое изменение порядка выполнения позволило нам решить вторую проблему, которая мешала удерживать желаемый объект на графике.


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

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

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

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

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

Файл MQ5 Описание
Code 01 Демонстрация объектов

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

Прикрепленные файлы |
Anexo.zip (2.42 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Разработка инструментария для анализа Price Action (Часть 61): Структурные пробои наклонных трендовых линий с подтверждением по трем свингам Разработка инструментария для анализа Price Action (Часть 61): Структурные пробои наклонных трендовых линий с подтверждением по трем свингам
Представлен инструмент для анализа пробоев наклонных трендовых линий, который использует проверку по трем свингам для генерации объективных сигналов Price Action. Система автоматизирует выявление свингов, построение трендовых линий и подтверждение пробоев, используя логику пересечения цены с линией, чтобы снизить шум и стандартизировать исполнение сигналов. В статье изложены правила стратегии, показана реализация на языке MQL5 и рассмотрены результаты тестирования; инструмент предназначен для анализа и подтверждения сигналов, а не для автоматической торговли.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
От начального до среднего уровня: События мыши От начального до среднего уровня: События мыши
Данная статья относится к категории тех материалов, где для понимания происходящих процессов определенно недостаточно просто просмотреть и изучить код. Фактически, необходимо создать исполняемое приложение и использовать его в любом графике. Это делается для того, чтобы можно было понимать мелкие детали, которые в ином случае чрезвычайно сложны для восприятия. Такие, например, как совместное использование клавиатуры и мыши для создания определенных элементов.