От начального к среднему уровню: Объекты и подокна (II)
Введение
В предыдущей статье «От начального к среднему уровню: Объекты и подокна (I)» мы рассмотрели, как можно направить объекты, созданные в индикаторе, в подокно. Однако, если вы были внимательны, то, вероятно, заметили, что я упомянул о наличии ошибки в той процедуре. Именно из-за механизма, используемого для определения индекса подокна на графике, и возникает проблема. Кроме того, у нас есть ещё одна проблема, которая, на мой взгляд, заслуживает отдельного внимания и должна быть решена в первую очередь. Эта проблема не упоминалась в предыдущей статье, поскольку, возможно, вы не поняли бы её, пока сами не попробовали то, что там было показано.
В этой статье мы рассмотрим несколько способов исправления некоторых ошибок, которые могут возникнуть при использовании того, что мы изучили до сих пор. Поэтому я хотел бы, чтобы вы на минуту отложили повседневные заботы и сосредоточились на том, что мы будем объяснять, поскольку происхождение некоторых ошибок бывает довольно трудно понять.Объекты и подокна (II)
Если вы практиковались и изучали каждую из этих статей, то, вероятно, уже поняли, что цель языка MQL5 как раз и состоит в том, чтобы позволить программистам в той или иной мере контролировать поведение и работу MetaTrader 5, чтобы платформа генерировала ожидаемый результат. Это простая часть, хотя немногие действительно это понимают.
Сложная часть заключается в том, чтобы понять: не всегда вы сможете что-то сделать, не разобравшись сначала в некоторых механизмах и концепциях, которые использует MetaTrader 5. Одно из этих понятий уже объяснялось ранее и касается именно того, что мы НЕ МОЖЕМ добавить два экземпляра одного и того же индикатора на один график. Это факт, и он гарантируется внутренними механизмами MetaTrader 5. Однако, если изменить какой-либо аргумент или входной параметр индикатора, мы сможем добавить второй экземпляр на тот же график, поскольку в этом случае MetaTrader 5 будет считать, что этот второй, то есть новый, экземпляр ещё отсутствует на графике.
Хорошо, но почему я снова к этому возвращаюсь? Итак, уважаемый читатель, дело в том, что в рассмотренных до сих пор кодах есть некоторые проблемы, которые могут значительно усложнить ситуацию. И эти проблемы возникают именно тогда, когда мы пытаемся что-то улучшить в самом коде. Поэтому мне нужно, чтобы вы хорошо поняли следующее: хотя коды, которые мы рассматривали до сих пор, работают — что можно проверить в каждой из статей с помощью анимаций, точно отражающих работу кода, — их изменение может привести к довольно запутанным результатам. Это связано именно с тем, что в каждом из кодов отсутствуют некоторые детали.
Чтобы это стало понятнее и было проще разобраться, давайте воспользуемся одним из кодов, приведённых в приложении к предыдущей статье. Использовав один из этих кодов, мы получим результат, который можно увидеть в следующей анимации.

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

Изображение 01
А теперь суть вопроса: если вы попытаетесь снова добавить индикатор, показанный в анимации 01, MetaTrader 5 не позволит повторно запустить код. Это не позволит создать новое подокно на графике. Эта блокировка обусловлена именно тем, что на графике присутствует индикатор, выделенный на изображении 01. Однако, если вы удалите этот индикатор, выделенный на изображении 01, ситуация изменится.
Чтобы это было проще понять, вам нужно привести содержимое, показанное на изображении 01, к виду, представленному на изображении 02 ниже.

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

Анимация 02
Обратите внимание, как одна простая деталь полностью изменила результат и, так сказать, сделала всё гораздо более запутанным, что создаёт для пользователя крайне плохой опыт работы с приложением. Итак, как же нам исправить подобную ошибку и избежать этой проблемы? Что ж, лучший и самый простой способ сделать это — связать все коды между собой. Таким образом, если какой-либо элемент будет удалён, основной код получит уведомление и сможет принять соответствующие меры, гарантируя, что на графике ничего не останется и что ничего не будет удалено неправильно или некорректно. Обратите внимание, что здесь упоминаются два типа ситуаций.
Первая заключается в том, что с графика может быть удалено то, что удалять не следовало. Вторая касается того, остаются ли элементы, которые следует удалить, когда основной код удаляется с графика.
Очень важно, чтобы вы понимали, что существуют эти две ситуации, поскольку каждая из них требует совершенно разного подхода. Итак, после этого разграничения мы можем перейти к внесению соответствующих исправлений. Помните, что показанные здесь механизмы можно и нужно адаптировать к каждой конкретной ситуации. Для начала сделаем так, чтобы при удалении основного индикатора с графика одновременно удалялся и индикатор, расположенный в подокне, которое было создано этим самым основным кодом. Хотя, в принципе, удаление индикатора может показаться довольно простым делом, ведь для этого достаточно лишь указать, где он находится и как называется индикатор, который мы хотим удалить, в некоторых случаях это может оказаться довольно сложной задачей. Но давайте начнём с самого простого случая. Для этого сначала нам нужно ознакомиться с кодами, которые мы будем использовать.
Первый код приведён ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #property indicator_chart_window 07. #property indicator_height 150 08. #property indicator_plots 0 09. //+------------------------------------------------------------------+ 10. #define def_ShortName "Test 123." 11. //+------------------------------------------------------------------+ 12. class C_MiniChart 13. { 14. private : 15. //+----------------+ 16. string m_szNameObject; 17. int m_Sub; 18. //+----------------+ 19. void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); } 20. //+----------------+ 21. int WhatSubWin(void) 22. { 23. for (int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++) 24. for (int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--) 25. if (ChartIndicatorName(0, c0, c1) == def_ShortName) 26. return c0; 27. return INVALID_HANDLE; 28. } 29. //+----------------+ 30. public : 31. //+----------------+ 32. C_MiniChart(string szName) 33. { 34. if ((m_Sub = WhatSubWin()) == INVALID_HANDLE) return; 35. ObjectCreate(0, m_szNameObject = szName, OBJ_CHART, m_Sub, 0, 0); 36. ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol); 37. SetInteger(OBJPROP_XDISTANCE, 10); 38. SetInteger(OBJPROP_YDISTANCE, 0); 39. SetInteger(OBJPROP_XSIZE, 300); 40. SetInteger(OBJPROP_YSIZE, indicator_height); 41. SetInteger(OBJPROP_PERIOD, PERIOD_M15); 42. SetInteger(OBJPROP_DATE_SCALE, false); 43. SetInteger(OBJPROP_PRICE_SCALE, false); 44. SetInteger(OBJPROP_SELECTABLE, true); 45. SetInteger(OBJPROP_SELECTED, false); 46. } 47. //+----------------+ 48. ~C_MiniChart() 49. { 50. ObjectDelete(0, m_szNameObject); 51. ChartRedraw(); 52. } 53. //+----------------+ 54. }*gl_MiniChart; 55. //+------------------------------------------------------------------+ 56. int OnInit(void) 57. { 58. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 59. 60. gl_MiniChart = new C_MiniChart("Mini Chart"); 61. 62. return INIT_SUCCEEDED; 63. }; 64. //+------------------------------------------------------------------+ 65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 66. { 67. return rates_total; 68. }; 69. //+------------------------------------------------------------------+ 70. void OnDeinit(const int reason) 71. { 72. delete gl_MiniChart; 73. }; 74. //+------------------------------------------------------------------+
Код 01
Второй код показан ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #property indicator_chart_window 07. #property indicator_plots 0 08. //+------------------------------------------------------------------+ 09. #define def_Resource "Code 01.ex5" 10. //+------------------------------------------------------------------+ 11. #resource def_Resource 12. //+------------------------------------------------------------------+ 13. int OnInit(void) 14. { 15. ChartIndicatorAdd(0, (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource)); 16. 17. return INIT_SUCCEEDED; 18. }; 19. //+------------------------------------------------------------------+ 20. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 21. { 22. return rates_total; 23. }; 24. //+------------------------------------------------------------------+
Код 02
А теперь будьте внимательны, потому что то, что будет объяснено дальше, очень важно. Чтобы удалить индикатор с графика, мы воспользуемся функцией ChartIndicatorDelete, и это не представляет никакой сложности, поскольку в документации очень хорошо объясняется, какие данные необходимо использовать. Важно, что третьим параметром, передаваемым в функцию ChartIndicatorDelete, должно быть имя индикатора, который мы собираемся удалить. Но какое именно имя? Ну, это зависит от каждого конкретного случая. Поэтому вам нужно очень внимательно относиться к тому, что вы реализуете в своём коде. В нашем конкретном случае имя, которое мы должны использовать, указано в строке 10 кода 01. Однако использовать эту функцию нужно в коде 02. Хм, как это так? Похоже, это не имеет смысла.
Однако, поскольку то, что мы здесь реализуем, довольно просто и сделано именно в учебных целях, сделать это очень легко. Достаточно будет изменить код 02, как показано ниже.01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #property indicator_chart_window 07. #property indicator_plots 0 08. //+------------------------------------------------------------------+ 09. #define def_Resource "Code 01.ex5" 10. //+------------------------------------------------------------------+ 11. #resource def_Resource 12. //+------------------------------------------------------------------+ 13. int gl_subWin; 14. //+------------------------------------------------------------------+ 15. int OnInit(void) 16. { 17. ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource)); 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 OnDeinit(const int reason) 28. { 29. ChartIndicatorDelete(0, gl_subWin, "Test 123."); 30. }; 31. //+------------------------------------------------------------------+
Код 03
Обратите внимание, что в строке 13 этого кода 03 мы объявляем переменную. Она будет инициализирована в строке 17, где мы получаем индекс подокна, в которое будет помещён индикатор, который мы собираемся разместить на графике. Но обратите внимание, что в строке 29 этого кода 03 мы используем данные, которые на самом деле указаны в строке 10 кода 01. Это создаст нужную нам связь. Но — а «но» всегда есть — такой способ реализации функциональности, хотя и работает, делает всё гораздо менее удобным и усложняет сопровождение по мере роста кода. Не забывайте, что то, что мы здесь видим, — это всего лишь демонстрация. Так что не дайте себя обмануть кажущейся простотой того, что вы здесь видите. Прежде чем углубляться в эту тему дальше, давайте посмотрим на результат, который мы только что получили, реализовав код по-новому.
Это показано в следующей анимации.

Анимация 03
Великолепно. Эта простая демонстрация анимации 03 уже даёт нам некоторое спокойствие и больший контроль над нашим кодом. Но, как уже было сказано, мы не можем, или, точнее, не должны принимать всё как есть, особенно в коде, который ещё находится в разработке. Поэтому лучшим решением будет внести некоторые изменения, чтобы реализация стала более подходящей. По крайней мере, если думать о дальнейшем развитии кода.
Чтобы этого добиться, мы начнём работать с файлами включения, также называемыми заголовочными файлами. Это простая часть. Сначала мы создаём файл в общем каталоге для любого кода, входящего в состав конечного приложения. Обычно этот файл находится в каталоге include внутри корневого каталога. Однако в некоторых случаях проще и удобнее хранить этот заголовочный файл вместе с файлами исходного кода приложения. Тем не менее, чтобы сохранить определённый порядок, мы поместим всё в папку include внутри папки основного проекта. Это может показаться несколько запутанным и сложным. Однако на практике вы увидите, что это гораздо проще, чем кажется. По сути, мы делаем это так:
1. //+------------------------------------------------------------------+ 2. #define def_Resource "Code 03.ex5" 3. //+------------------------------------------------------------------+ 4. #define def_ShortName "Test 123." 5. //+------------------------------------------------------------------+
Код 04
Этот код 04 станет нашим заголовочным файлом. Теперь мы сосредоточим внимание на кодах индикаторов и изменим их, как показано ниже.01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\defines.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. class C_MiniChart 13. { 14. private : 15. //+----------------+ 16. string m_szNameObject; 17. int m_Sub; 18. //+----------------+ 19. void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); } 20. //+----------------+ 21. int WhatSubWin(void) 22. { 23. for (int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++) 24. for (int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--) 25. if (ChartIndicatorName(0, c0, c1) == def_ShortName) 26. return c0; 27. return INVALID_HANDLE; 28. } 29. //+----------------+ 30. public : 31. //+----------------+ 32. C_MiniChart(string szName) 33. { 34. if ((m_Sub = WhatSubWin()) == INVALID_HANDLE) return; 35. ObjectCreate(0, m_szNameObject = szName, OBJ_CHART, m_Sub, 0, 0); 36. ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol); 37. SetInteger(OBJPROP_XDISTANCE, 10); 38. SetInteger(OBJPROP_YDISTANCE, 0); 39. SetInteger(OBJPROP_XSIZE, 300); 40. SetInteger(OBJPROP_YSIZE, indicator_height); 41. SetInteger(OBJPROP_PERIOD, PERIOD_M15); 42. SetInteger(OBJPROP_DATE_SCALE, false); 43. SetInteger(OBJPROP_PRICE_SCALE, false); 44. SetInteger(OBJPROP_SELECTABLE, true); 45. SetInteger(OBJPROP_SELECTED, false); 46. } 47. //+----------------+ 48. ~C_MiniChart() 49. { 50. ObjectDelete(0, m_szNameObject); 51. ChartRedraw(); 52. } 53. //+----------------+ 54. }*gl_MiniChart; 55. //+------------------------------------------------------------------+ 56. int OnInit(void) 57. { 58. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 59. 60. gl_MiniChart = new C_MiniChart("Mini Chart"); 61. 62. return INIT_SUCCEEDED; 63. }; 64. //+------------------------------------------------------------------+ 65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 66. { 67. return rates_total; 68. }; 69. //+------------------------------------------------------------------+ 70. void OnDeinit(const int reason) 71. { 72. delete gl_MiniChart; 73. }; 74. //+------------------------------------------------------------------+
Код 05
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\defines.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #resource def_Resource 12. //+------------------------------------------------------------------+ 13. #define def_ShortNamePrinc def_ShortName + "_main" 14. //+------------------------------------------------------------------+ 15. int gl_subWin; 16. //+------------------------------------------------------------------+ 17. int OnInit(void) 18. { 19. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc); 20. ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource)); 21. 22. return INIT_SUCCEEDED; 23. }; 24. //+------------------------------------------------------------------+ 25. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 26. { 27. return rates_total; 28. }; 29. //+------------------------------------------------------------------+ 30. void OnDeinit(const int reason) 31. { 32. ChartIndicatorDelete(0, gl_subWin, def_ShortName); 33. }; 34. //+------------------------------------------------------------------+
Код 06
Обратите внимание, что в коде 06 я добавил строку 13, чтобы присвоить имя главному индикатору. Это нам очень скоро пригодится. Но пока нас интересует то, что мы можем настраивать всё гораздо проще и удобнее, не рискуя со временем создать неуправляемого монстра. Есть ещё один способ сделать нечто очень похожее, но без использования заголовочного файла. Однако пока мы будем использовать этот более простой метод. Таким образом, результат выполнения приведённых выше кодов будет таким же, как в анимации 03. Итак, мы можем перейти к следующему шагу.
Теперь нам нужно принять несколько решений, которые определят, что мы будем делать дальше. В любом случае, мне нужно, чтобы вы обладали как минимум базовыми знаниями о том, как генерируются и обрабатываются события. Для этого можно обратиться к предыдущим статьям из этой же серии, где как раз объясняются эти основы. Короче говоря, вам нужно понимать содержание двух статей:
От начального к среднему уровню: События (I)
От начального к среднему уровню: События (II)
Понимание этих двух статей крайне важно для того, чтобы следить за тем, что мы будем делать дальше, независимо от того, какое решение примем. Идея здесь заключается в том, чтобы наладить взаимодействие между разными кодами. И для этого мы будем использовать события. Таким образом, эти два индикатора начнут взаимодействовать друг с другом и выполнять действия, которые мы для них определим.
Не забывайте, что на данном этапе принимаемые нами решения нужно тщательно взвешивать.
Итак, первое решение касается поведения кода, когда мы удалим какой-либо объект, созданный одним из наших индикаторов. Подобная ситуация, которая на первый взгляд может показаться мелочью, может полностью изменить опыт пользователя или оператора. Это также определяет, как мы будем реализовывать наш код. В принципе, мы будем придерживаться следующей идеи: если будет удалён какой-либо объект, созданный одним из индикаторов, то сам индикатор, создавший этот объект, тоже должен быть удалён с графика. Я знаю, что это решение может показаться несколько карательным для пользователя или оператора. Но поскольку здесь цель не в том, чтобы создать что-то конкретное, а в том, чтобы показать, как можно реализовать те или иные вещи, я не вижу никаких проблем в таком подходе.
Понять, был ли объект удалён с графика, — не самая сложная задача. Однако делать это вручную — как минимум пустая трата времени, ведь мы можем указать MetaTrader 5, чтобы он сообщал нам, какой объект был удалён с графика. Зная это, реализовать остальную часть процедуры становится гораздо проще.
Проблема в том, что по умолчанию MetaTrader 5 не генерирует определённые события. Нужно указать ему, что мы хотим получать уведомления, когда это произойдёт. И одно из них — это как раз событие удаления объектов с графика. Но не волнуйтесь: указать MetaTrader 5, что мы хотим получать уведомление при удалении какого-либо объекта, довольно просто. Всё, что нам нужно сделать, — это отправить MetaTrader 5 команду с помощью кода MQL5. Вопрос не в том, как это сделать, а в том, куда поместить эту команду.
Поясню: не обязательно добавлять эту команду во все индикаторы или коды. Достаточно добавить её в один из них, и каждый раз, когда произойдёт это событие, MetaTrader 5 будет сообщать нам об этом. Это относится к конкретному графику. Лично я предпочитаю использовать такую команду в ключевой точке кода. Обычно я использую её в главном файле. В других случаях я использую её в том файле, где создаются объекты, за которыми нужно следить. Решение о том, где разместить команду, остаётся на ваше усмотрение. Поскольку я хочу максимально всё упростить, давайте использовать эту команду в том, что можно считать главным файлом кода. Таким образом, изменив код 06, мы получим то, что показано ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\defines.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #resource def_Resource 12. //+------------------------------------------------------------------+ 13. #define def_ShortNamePrinc def_ShortName + "_main" 14. //+------------------------------------------------------------------+ 15. int gl_subWin; 16. //+------------------------------------------------------------------+ 17. int OnInit(void) 18. { 19. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc); 20. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 21. ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource)); 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 OnDeinit(const int reason) 32. { 33. ChartIndicatorDelete(0, gl_subWin, def_ShortName); 34. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 35. }; 36. //+------------------------------------------------------------------+
Код 07
Обратите внимание, что в двадцатой строке этого кода 07 мы указываем MetaTrader 5, что хотим получать уведомление, когда объект удаляется с графика. С точки зрения хорошей практики программирования также полезно указать, что мы больше не хотим получать эти уведомления, когда они нам уже не нужны. По этой причине мы также включили в строку 34 вызов, который отключает эти уведомления. Но это также можно делать в определённые моменты, когда мы не хотим получать уведомления, пока удаляем с графика какой-нибудь ненужный объект. Хорошо, это первая часть. Теперь перейдём ко второй, которая как раз и состоит в том, чтобы решить, что произойдёт при удалении какого-либо объекта.
Здесь мы можем сделать две вещи. Первый вариант — просто заново создать этот объект. Второй заключается в удалении индикатора или приложения, создавшего этот объект. Поскольку второй подход в принципе несколько проще — как с точки зрения реализации, так и потому, что он избавляет нас от необходимости беспокоиться о внутренних параметрах самого объекта, — мы примем его в качестве исходного механизма обработки.
Вот здесь всё начинает становиться довольно интересным, хотя и может оказаться весьма запутанным. Чтобы это понять, обратите внимание на код 05. В нём мы создаём объекты, которые будут отображаться на графике. Но обратите внимание на один момент. Имя объекта объявляется в шестидесятой строке кода 05. В принципе, такой подход не должен вызвать у нас никаких проблем. Однако может случиться так, что пользователь или трейдер уже разместил на графике объект с таким же именем ещё до добавления индикатора. В таком случае может возникнуть несоответствие. Чтобы этого избежать, нужно, чтобы объект, созданный нашим кодом, имел уникальное и легко узнаваемое имя. Поэтому мы изменим способ формирования его имени.
Чтобы без необходимости не создавать огромный файл, мы вынесем класс из этого кода 05 и тем самым создадим ещё один заголовочный файл. Ниже он приведён уже с исправлением в способе формирования имени объекта.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "defines.mqh" 05. //+------------------------------------------------------------------+ 06. class C_MiniChart 07. { 08. private : 09. //+----------------+ 10. string m_szNameObject; 11. int m_Sub; 12. //+----------------+ 13. void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); } 14. //+----------------+ 15. int WhatSubWin(void) 16. { 17. for (int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++) 18. for (int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--) 19. if (ChartIndicatorName(0, c0, c1) == def_ShortName) 20. return c0; 21. return INVALID_HANDLE; 22. } 23. //+----------------+ 24. public : 25. //+----------------+ 26. C_MiniChart(string szName) 27. { 28. if ((m_Sub = WhatSubWin()) == INVALID_HANDLE) return; 29. ObjectCreate(0, m_szNameObject = def_ShortName + "#" + szName + "#" + (string)ObjectsTotal(0, -1, -1) , OBJ_CHART, m_Sub, 0, 0); 30. ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol); 31. SetInteger(OBJPROP_XDISTANCE, 10); 32. SetInteger(OBJPROP_YDISTANCE, 0); 33. SetInteger(OBJPROP_XSIZE, 300); 34. SetInteger(OBJPROP_YSIZE, indicator_height); 35. SetInteger(OBJPROP_PERIOD, PERIOD_M15); 36. SetInteger(OBJPROP_DATE_SCALE, false); 37. SetInteger(OBJPROP_PRICE_SCALE, false); 38. SetInteger(OBJPROP_SELECTABLE, true); 39. SetInteger(OBJPROP_SELECTED, false); 40. } 41. //+----------------+ 42. ~C_MiniChart() 43. { 44. ObjectDelete(0, m_szNameObject); 45. ChartRedraw(); 46. } 47. //+----------------+ 48. bool operator==(const string szArg) { return (szArg == m_szNameObject); } 49. //+----------------+ 50. }; 51. //+------------------------------------------------------------------+
Код 08
Исправление, позволяющее создать уникальное имя, внесено в двадцать девятой строке этого кода 08. Теперь обратите внимание, что в сорок восьмой строке мы создаём перегруженный оператор для проверки самого класса. Это важно для того, чтобы можно было проверить, является ли удалённый объект одним из тех, которые мы создали в индикаторах. В этой же серии мы объясняем перегрузку операторов. Обратитесь к этим статьям за подробностями; первая из них — «От начального к среднему уровню: Перегрузка операторов (I)». Как я уже говорил, знания со временем накапливаются. Теперь мы можем посмотреть, каким стал прежний код 05. Ниже он приводится полностью.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\C_MiniChart.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. C_MiniChart *gl_MiniChart; 13. //+------------------------------------------------------------------+ 14. int OnInit(void) 15. { 16. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 17. gl_MiniChart = new C_MiniChart("Mini Chart"); 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. switch (id) 30. { 31. case CHARTEVENT_OBJECT_DELETE: 32. if (gl_MiniChart == sparam) 33. { 34. ChartIndicatorDelete(0, ChartWindowFind(0, def_ShortName), def_ShortName); 35. OnDeinit(REASON_INITFAILED); 36. } 37. break; 38. } 39. }; 40. //+------------------------------------------------------------------+ 41. void OnDeinit(const int reason) 42. { 43. delete gl_MiniChart; 44. }; 45. //+------------------------------------------------------------------+
Код 09
Хотя этот код и не отличается особой элегантностью, он вполне функционален. Однако следует обратить внимание на некоторые детали, чтобы избежать неприятных сюрпризов в будущем. Один из них — то, что происходит в строке 31. Обратите внимание, что здесь мы обрабатываем событие CHARTEVENT_OBJECT_DELETE. Однако это событие включается не здесь, в коде 09, а в коде 07, поскольку код 07 считается основным кодом приложения. Следующий момент заключается в том, что в строке 34 мы удаляем сам индикатор с графика, а сразу после этого деинициализируем код вызовом OnDeinit.
Но зачем это делать? Дело в том, что мы хотим полностью удалить любое присутствие индикатора на графике, чтобы следующий этап был немного проще. Здесь мы могли бы ещё немного улучшить ситуацию. Но пока этого будет достаточно. Теперь мы изменим основной код, чтобы код 07 выглядел так, как показано ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\defines.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #resource def_Resource 12. //+------------------------------------------------------------------+ 13. #define def_ShortNamePrinc def_ShortName + "_main" 14. //+------------------------------------------------------------------+ 15. int gl_subWin; 16. //+------------------------------------------------------------------+ 17. int OnInit(void) 18. { 19. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc); 20. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 21. ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource)); 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. Print(__FILE__, " -> ", __FUNCTION__, " ?? ", EnumToString((ENUM_CHART_EVENT)id), " >> ", sparam); 34. }; 35. //+------------------------------------------------------------------+ 36. void OnDeinit(const int reason) 37. { 38. ChartIndicatorDelete(0, gl_subWin, def_ShortName); 39. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 40. }; 41. //+------------------------------------------------------------------+
Код 10
Этот код 10 ещё не завершён. Прежде чем это сделать, я хочу, чтобы вы поняли одну вещь. Обратите внимание, что в строке 33 мы выведем кое-что в терминал. Я хочу, чтобы вы точно поняли, какая именно информация будет выведена. Итак, мы компилируем коды и размещаем на графике только что скомпилированное приложение. Таким образом, как только индикаторы появятся на графике, мы сможем сделать то, что показано в следующей анимации.

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

Изображение 03
Обратите внимание на выделенную область на изображении 03. Хотя многие и не до конца понимают, что именно здесь выводится, я хочу, чтобы вы внимательно посмотрели на эту выделенную область. Обратите внимание, что значение sparam указывает имя объекта, удалённого с графика. Но, поскольку при задании имени мы также использовали часть краткого имени индикатора, теперь мы можем этим воспользоваться.
Для этого достаточно выделить имя индикатора и проверить, совпадает ли оно с тем, которое наш основной код поместил на график. В таком случае мы сможем направить выполнение программы по нужному сценарию. Таким образом, мы подошли к моменту, когда решения, принятые в самом начале, будут иметь решающее значение для результата. Обратите внимание, что к этому моменту мы сделали всё необходимое, чтобы удалить вспомогательный индикатор с графика. Но это можно было сделать и другими способами.
Как мы уже видели, код 10 способен перехватывать события удаления объекта. Таким образом, мы могли бы обработать эту ситуацию довольно прямолинейно, поскольку главная цель как раз и состояла бы в том, чтобы удалять индикаторы, если будет удалён какой-либо из объектов, которые они разместили на графике. Однако, поскольку часть удаления мы уже выполнили непосредственно в коде вспомогательного индикатора, как видно в коде 09, теперь нам остаётся лишь удалить с графика сам основной индикатор, как только он обнаружит, что вспомогательный индикатор был удалён. Чтобы сделать это правильно и без проблем, нам нужно изменить код 10 так, чтобы он выглядел, как показано ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\defines.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #resource def_Resource 12. //+------------------------------------------------------------------+ 13. #define def_ShortNamePrinc def_ShortName + "_main" 14. //+------------------------------------------------------------------+ 15. int gl_subWin; 16. //+------------------------------------------------------------------+ 17. int OnInit(void) 18. { 19. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc); 20. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true); 21. ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource)); 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. switch (id) 34. { 35. case CHARTEVENT_OBJECT_DELETE: 36. { 37. string szRet[]; 38. StringSplit(sparam, '#', szRet); 39. if (szRet[0] == def_ShortName) 40. ChartIndicatorDelete(0, 0, def_ShortNamePrinc); 41. } 42. break; 43. } 44. }; 45. //+------------------------------------------------------------------+ 46. void OnDeinit(const int reason) 47. { 48. ChartIndicatorDelete(0, gl_subWin, def_ShortName); 49. ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false); 50. }; 51. //+------------------------------------------------------------------+
Код 11
А теперь обратите внимание, как мы решили проблему в соответствии с принятым ранее решением. Нужно было лишь добавить способ фильтрации параметра sparam, чтобы получить имя индикатора. После этого мы используем строку 39, чтобы проверить, совпадает ли имя с тем, которое определено в коде 04. Если это условие выполняется, следующим шагом будет завершение работы основного индикатора, как показано в строке 40. И это полностью удалит оба индикатора с графика.
Представленный здесь подход — лишь один из вариантов, который позволяет нам хорошо контролировать то, как наш код представляется пользователю. Я знаю, что в принципе такой подход в итоге наказывает пользователя или оператора, который без разбора удаляет объекты с графика. Однако, на мой взгляд, этот подход значительно проще именно потому, что нам не нужно заботиться о сохранении данных о расположении и характеристиках каждого объекта на графике, а это заметно облегчает понимание кода.
Однако, увидев, что делает этот код 11, вы, возможно, задаётесь вопросом: а не существует ли более простого способа сделать всё это? В конце концов, ещё до этого основной код уже мог удалять вспомогательный индикатор с графика. И да, уважаемый читатель. По этой причине в приложении вы найдёте два разных кода: один покажет то, что мы сделали здесь, а другой позволит убедиться, что в итоге нам требуется гораздо меньше кода. Чтобы выбрать тот или другой вариант, достаточно изменить одну деталь в коде 04. Так что будьте внимательны: в приложении код 04 изначально будет таким же, как и тот, который мы рассматривали к этому моменту статьи. Однако у вас также будет и другой код для вспомогательного индикатора. Он приведён ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #include "include\C_MiniChart.mqh" 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_height 150 10. #property indicator_plots 0 11. //+------------------------------------------------------------------+ 12. C_MiniChart *gl_MiniChart; 13. //+------------------------------------------------------------------+ 14. int OnInit(void) 15. { 16. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 17. gl_MiniChart = new C_MiniChart("Mini Chart"); 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 OnDeinit(const int reason) 28. { 29. delete gl_MiniChart; 30. }; 31. //+------------------------------------------------------------------+
Код 12
Обратите внимание, что этот код 12 практически идентичен коду 09, за исключением того, что в коде 12 отсутствует обработчик событий, который есть в коде 09 и который мы рассмотрели в этой статье. Тем не менее, несмотря на используемую здесь нумерацию, в приложении этот код 09 будет называться CODE 03.MQ5. Следовательно, код в заголовочном файле должен быть тем же, что и в коде 04 этой статьи. Если вы хотите посмотреть, как работает приложение с этим кодом 12, вам нужно заменить содержимое файла DEFINES.MQH на то, что показано ниже.1. //+------------------------------------------------------------------+ 2. #define def_Resource "Code 06.ex5" 3. //+------------------------------------------------------------------+ 4. #define def_ShortName "Test 123." 5. //+------------------------------------------------------------------+
Код 13
Таким образом, будет скомпилирован файл CODE 06.MQ5 вместо CODE 03.MQ5, то есть именно код 12, который мы только что рассмотрели. Это позволит вам более спокойно изучить все детали того, как при необходимости удалять индикаторы с графика. И, поскольку результаты выполнения точно такие же, я не вижу никаких причин снова показывать их здесь.
Заключительные замечания
Эту статью, а также коды, приведённые в приложении, следует изучить не торопясь и очень внимательно. Это связано с тем, что каждая деталь, показанная здесь, решающим образом влияет на качество и тип результата, который вы сможете получить в будущих кодах. Важно отметить, что весь представленный здесь материал имеет единственную цель — показать наилучший способ избежать ненужной проверки того, присутствуют ли на графике те или иные объекты, индикаторы или элементы. Мы можем сделать так, чтобы MetaTrader 5 всё больше помогал нам в задачах такого рода, так что наша настоящая работа будет заключаться лишь в том, чтобы как можно лучше обрабатывать данные, которые MetaTrader 5 отправляет нашему приложению.
Существует ещё один способ выполнить задачу такого типа. Однако, поскольку этот способ значительно более трудоёмок, ведь нам пришлось бы поддерживать список свойств и объектов, чтобы можно было восстановить данные при удалении этих объектов, он, возможно, будет рассмотрен в одной из будущих статей.
В любом случае, теперь вы уже знаете, что не всё нужно программировать так, как многие себе это представляют. Если мы поймём, как работает MetaTrader 5, то сможем создавать приложения гораздо быстрее и с гораздо меньшим риском сбоев именно потому, что наш код будет содержать только то, что действительно необходимо.
| Файл MQ5 | Описание |
|---|---|
| Indicators\Code 01 | Базовая демонстрация |
| Indicators\Code 02 | Базовая демонстрация |
| Indicators\Code 03 | Базовая демонстрация |
| Indicators\Code 04 | Базовая демонстрация |
| Indicators\Code 05 | Базовая демонстрация |
| Indicators\Code 06 | Базовая демонстрация |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/17349
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Нейросети в трейдинге: Диффузионная генерация торговых планов (Вычислительная основа)
Нейронная сеть на практике: Перцептрон
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования