Моделирование рынка: Position View (III)
Введение
Здравствуйте и приветствую всех в очередной статье из серии о том, как создать систему репликации/моделирования.
В предыдущей статье, Моделирование рынка: Position View (II), мы создали очень простую вещь, цель которой — показать на графике, где расположены ценовые линии при открытой позиции. Хотя данный индикатор и может предоставить нам необходимую информацию, до действительно практического применения нам ещё далеко, поскольку необходимо решить ряд незначительных проблем. Но в этом нет ничего сложного, это лишь то, что вам, уважаемый читатель, необходимо понять, прежде чем мы сможем продвинуться немного дальше в реализации кода.
Чтобы немного упростить вещи и сделать их более доступными для восприятия, мы рассмотрим это в отдельной теме, посвященной только этой задаче. Итак, перейдем к делу без лишних промедлений.
Понимание свойства ZOrder
Не знаю, заметили ли вы, уважаемый читатель, что в объектах MQL5 есть значение, которое в целом крайне редко используется или определяется в коде. Я сам изучил множество различного кода и не нашел никого, кто действительно определяет это свойство объектов. Речь идёт о свойстве ZOrder. Если вы следите за этой серией статей, то наверняка заметили, что уже довольно давно это свойство определяется в классе C_Terminal при создании объектов с помощью вызова CreateObjectGraphics, который является процедурой этого класса.
И в этих последних статьях я упоминал, что в определенные моменты нам необходимо задавать значение для этого свойства. Но почему? Причина в том, что многие коды, добавляющие объекты на график, просто не используют или, точнее, не определяют значение для этого свойства. Дело в том, что я здесь не для того, чтобы говорить, что должен или не должен делать каждый программист, или как он должен или не должен писать свой код. Я здесь для того, чтобы показать вам, уважаемый читатель, и всем, кто действительно хочет понять внутреннее устройство процессов, что именно происходит за кулисами.
Без понимания того, что я собираюсь объяснить, или без должного усвоения определенных концепций использования MetaTrader 5 или даже программирования на MQL5, вы, без сомнения, в конечном итоге столкнетесь с крайне негативным опытом работы с платформой. И одна из этих концепций связана именно со свойством ZOrder.
Чтобы максимально наглядно показать, о чём идет речь, мы воспользуемся приведенным ниже кодом:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. input char user00 = 0; //ZOrder to Chart #01 08. input char user01 = 0; //ZOrder to Chart #02 09. //+------------------------------------------------------------------+ 10. void ChartOfTest(string sz1, int x, int y, int zOrder) 11. { 12. long id = ChartID(); 13. 14. ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0); 15. ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x); 16. ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y); 17. ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder); 18. ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true); 19. } 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. ChartOfTest("Chart #01", 160, 140, user00); 24. ChartOfTest("Chart #02", 200, 200, user01); 25. 26. return INIT_SUCCEEDED; 27. } 28. //+------------------------------------------------------------------+ 29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 30. { 31. return rates_total; 32. } 33. //+------------------------------------------------------------------+ 34. void OnDeinit(const int reason) 35. { 36. ObjectsDeleteAll(ChartID(), "Chart #"); 37. } 38. //+------------------------------------------------------------------+
Исходный код индикатора
Этот простой код делает именно то, что нам нужно, чтобы понять, как свойство ZOrder может и будет влиять на опыт использования MetaTrader 5. Обратите внимание, что в строках 23 и 24 мы вызовем процедуру, отвечающую за создание двух объектов и размещение их на экране графика, открытого в данный момент. Создаваемые объекты имеют тип OBJ_CHART, как можно видеть в строке 14. Для удобства, уважаемый читатель, в строках 07 и 08 мы предусмотрели две точки настройки. Их цель — избавить нас от необходимости постоянно корректировать значения ZOrder объектов.
По умолчанию значения всегда равны нулю. Поэтому пока мы оставим их как есть. Результат выполнения показан на следующем изображении:

Что-то простое и очевидное, как и следовало ожидать. "А что, если мы изменим таймфрейм графика? Что произойдет в этом окне, где у нас есть индикатор, целью которого является создать фон для графика? Не окажемся ли мы в ситуации, когда больше не сможем взаимодействовать с объектами OBJ_CHART?" Хорошо, это можно увидеть в следующей анимации:

Обратите внимание, что хотя фоновый рисунок, то есть объект типа OBJ_BITMAPLABEL, находится поверх объектов OBJ_CHART, обычно это могло бы вызвать проблемы. Фон объекта OBJ_CHART теперь не черный, а отображает содержимое области, в которой он расположен, поверх объекта OBJ_BITMAPLABEL. Однако, поскольку в строке 17 мы задаем нулевое значение для ZOrder, а значение ZOrder объекта с фоновым рисунком меньше, в данном случае -1, как мы видели в предыдущих статьях, отклик на клик или способ выбора объектов OBJ_CHART не пострадали, что видно на анимации выше.
Однако у нас возникнет отчетливая иллюзия, что объект на переднем плане будет иметь приоритет, как показано в следующей анимации:

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

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

Попробуйте сделать следующее: добавьте ещё один OBJ_CHART, но на этот раз с помощью комбинации клавиш, показанной ранее, и поиграйте со значениями индикатора, как показано на предыдущей анимации. Без сомнения, вы начнете понимать важность использования подходящего значения в свойстве ZOrder. Но где именно это применяется в том, что мы разрабатываем в данный момент? Что ж, чтобы ответить на это наиболее подходящим образом и тем самым разделить темы, мы рассмотрим это в новой теме.
Используем полученные знания
Хорошо, знание и понимание изложенного крайне важно для того, чтобы вы могли понять, что мы будем делать во время реализации. В дальнейшем это потеряет смысл, так как мы будем использовать другой подход. Но на данный момент это важно, особенно в зависимости от того, как вы модифицируете или адаптируете код для своего личного использования.
В предыдущей статье мы создали три линии: одну для цены открытия позиции, одну для уровня стоп-лосс и одну для уровня тейк-профит. Отлично, до этого момента в том, что было сделано, нет ничего неправильного. Горизонтальная линия цены открытия ни в коем случае не должна смещаться. Скоро мы это исправим. В отличие от этого, горизонтальные линии стоп-лосс и тейк-профит можно свободно перемещать. И именно здесь у нас возникает проблема, когда обе линии, линия стоп-лосса и линия тейк-профита, оказываются на одной и той же цене.
В таком случае, какая линия должна иметь приоритет при попытке выбрать одну из них? Это необходимо для того, чтобы система получала определенное событие, например, перемещение линии или даже ее удаление. Вы могли бы сказать, что это стоп-лосс, в то время как другой трейдер мог бы сказать, что это тейк-профит. Кто в такой ситуации окажется прав на самом деле?
Видите, в чем здесь проблема? В некотором смысле, порядок создания линий повлияет на это решение, если у обеих линий одинаковое значение ZOrder. Таким образом, простое изменение порядка создания решило бы многие из проблем. Однако давайте доведем эту ситуацию до ещё более экстремальной. Нет ничего странного в том, что некоторые трейдеры, особенно на счетах HEDGING, имеют более одной открытой позиции. В этом случае может потребоваться установить разный ZOrder между линией стоп-лосса и линией тейк-профита. Но даже если это сделано, что устранило бы проблему совпадения линии стоп-лосса и линии тейк-профита, возникла бы другая проблема: когда две линии, например, две линии стоп-лосса, случайно перекрываются.
Вы заметили, что любое изменение ZOrder становится бессмысленным? Даже если использовать разные значения для решения одной задачи, всегда найдется другая, которую нужно будет решить. Однако по мнению некоторых профессиональных трейдеров, которые консультируют меня по ряду аспектов, связанных с разработкой и внедрением системы репликации/моделирования, нет практической причины держать на сервере более двух, трех или максимум четырех таких сущностей/записей. Другими словами, по их мнению, если мы используем счет HEDGING, то нет смысла иметь более двух открытых позиций одновременно, поскольку на счете NETTING такой проблемы нет. На данном начальном этапе я буду рассматривать только счета HEDGING. Но это не означает, что индикатор не будет работать на счетах NETTING.
Итак, вернёмся к коду, который мы рассматривали в предыдущей статье. Там мы реализовали линии стоп-лосс и тейк-профит с одинаковым ZOrder. Чтобы это изменить, нам потребуется внести небольшие изменения в код. Изменения настолько незначительны, что не будем вдаваться в подробности. Просто посмотрите, как будет выглядеть измененный код:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_SufixLinePrice "Price" 09. #define def_SufixLineTake "Take" 10. #define def_SufixLineStop "Stop" 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Auxiliar\C_Terminal.mqh> 13. //+------------------------------------------------------------------+ 14. input color user00 = clrRoyalBlue; //Color Line Price 15. input color user01 = clrForestGreen; //Color Line Take Profit 16. input color user02 = clrFireBrick; //Color Line Stop Loss 17. //+------------------------------------------------------------------+ 18. C_Terminal *Terminal; 19. struct st 20. { 21. long id; 22. string szPrefixName; 23. }glVariables; 24. //+------------------------------------------------------------------+ 25. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 26. { 27. if (price <= 0) return; 28. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 29. ObjectSetDouble(glVariables.id, szObjName, OBJPROP_PRICE, price); 30. ObjectSetString(glVariables.id, szObjName, OBJPROP_TEXT, szDescription); 31. ObjectSetString(glVariables.id, szObjName, OBJPROP_TOOLTIP, szDescription); 32. ObjectSetInteger(glVariables.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 33. } 34. //+------------------------------------------------------------------+ 35. int OnInit() 36. { 37. ZeroMemory(glVariables); 38. Terminal = new C_Terminal(); 39. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 40. glVariables.id = (*Terminal).GetInfoTerminal().ID; 41. glVariables.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 42. CreateLineInfos(glVariables.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 43. CreateLineInfos(glVariables.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 44. CreateLineInfos(glVariables.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 45. 46. return INIT_SUCCEEDED; 47. } 48. //+------------------------------------------------------------------+ 49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 50. { 51. return rates_total; 52. } 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. delete Terminal; 57. if (glVariables.id > 0) 58. ObjectsDeleteAll(glVariables.id, glVariables.szPrefixName); 59. } 60. //+------------------------------------------------------------------+
Indicador Position View
Прошу заметить, что теперь линия стоп-лосса будет иметь приоритет над линией тейк-профита. Это объясняется более высоким значением ZOrder. Тогда в случае, если у нас открыты две позиции, и линия стоп-лосс находится по той же цене, что и линия тейк-профит, при любой попытке взаимодействия с этими линиями приоритет будет у линии стоп-лосс. Обратите внимание, что единственное изменение, которое привело к этому, произошло в строке 28. Сравните эту же строку с той, что была в предыдущем коде, чтобы понять изменение.
Однако, хотя данный индикатор и работает, он не подходит для использования на счетах HEDGING. Причина в том, что система всегда будет искать либо первую открытую позицию, либо последнюю. И даже если вы сделаете что-то, чтобы изменить это, так или иначе, не даст реального эффекта. Нам необходимо реализовать решение с более эффективными результатами и более общего характера. Кроме того, можно заметить, что мы используем разные цвета, чтобы индикатор понимал, что представляет собой каждая линия. И подобные решения полезны для тестирования. Однако для более широкого применения они совершенно непригодны. Поэтому нам предстоит решить и исправить множество проблем.
На этом этапе многие из тех, кто только начинает свой путь в программировании, часто сдаются или вносят так много изменений, что в конечном итоге теряют контроль над создаваемым кодом. Поэтому, если вы уже умеете программировать и обладаете правильными базовыми знаниями, я прошу вас проявить терпение к тем, у кого меньше опыта, так как я собираюсь показать, как стоит начинать мыслить новичкам, чтобы они смогли искренне полюбить искусство программирования и научились создавать свои собственные решения.
Приступаем к исправлению ситуации
Если вы, уважаемый читатель, столкнулись с кодом, подобным приведенному выше, который нуждается в улучшении, не начинайте его изменять без каких-либо критериев. Начните с разделения задачи на части, но всегда следите за тем, чтобы код оставался работоспособным. Нет, и я повторяю: НЕ меняйте ничего, пока не разделите компоненты. Один из самых простых и подходящих способов сделать это — поместить некоторые части кода в заголовочный файл, чтобы затем сразу приступить к его модификации. Но поскольку гораздо проще объединить все части в один класс, мы так и поступим. Но почему проще поместить код в класс, чем разделить его на заголовочные файлы?
Причина проста: Если вы поместите код в класс, то позже сможете перенести его в заголовочный файл. Однако простое перемещение его в заголовочный файл не упрощает управление. Это связано с тем, что в будущем могут возникнуть конфликты имен. Подобные конфликты нередко встречаются в кодовых базах, содержащих множество файлов. Вместо этого, когда мы переносим элементы в класс, вероятность конфликтов значительно снижается. Дело не в том, что они полностью устраняются, в чем вы сами убедитесь по мере продвижения в программировании. Однако разрешение таких конфликтов станет значительно проще и быстрее. Зная это, давайте начнем с переноса элементов в новый класс внутри системы репликации/моделирования.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_SufixLinePrice "Price" 09. #define def_SufixLineTake "Take" 10. #define def_SufixLineStop "Stop" 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Auxiliar\C_Terminal.mqh> 13. //+------------------------------------------------------------------+ 14. input color user00 = clrRoyalBlue; //Color Line Price 15. input color user01 = clrForestGreen; //Color Line Take Profit 16. input color user02 = clrFireBrick; //Color Line Stop Loss 17. //+------------------------------------------------------------------+ 18. class C_IndicatorPosition 19. { 20. private : 21. struct st00 22. { 23. long id; 24. string szPrefixName; 25. }m_Infos; 26. //+------------------------------------------------------------------+ 27. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 28. { 29. if (price <= 0) return; 30. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 31. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 32. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 33. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 34. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 35. } 36. //+------------------------------------------------------------------+ 37. public : 38. //+------------------------------------------------------------------+ 39. C_IndicatorPosition(const long Id) 40. { 41. ZeroMemory(m_Infos); 42. m_Infos.id = Id; 43. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 44. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 45. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 46. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 47. } 48. //+------------------------------------------------------------------+ 49. ~C_IndicatorPosition() 50. { 51. if (m_Infos.id > 0) 52. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 53. } 54. //+------------------------------------------------------------------+ 55. }; 56. //+------------------------------------------------------------------+ 57. C_Terminal *Terminal; 58. C_IndicatorPosition *Positions; 59. //+------------------------------------------------------------------+*/ 60. int OnInit() 61. { 62. Terminal = new C_Terminal(); 63. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 64. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID); 65. 66. return INIT_SUCCEEDED; 67. } 68. //+------------------------------------------------------------------+ 69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 70. { 71. return rates_total; 72. } 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. delete Terminal; 77. delete Positions; 78. } 79. //+------------------------------------------------------------------+
Исходный код Position View
В приведенном ниже коде можно представить, что мы добавили сложности. Но на самом деле мы всего лишь поместили код, который раньше находился вне класса, внутрь класса. Таким образом, вопреки тому, что может показаться, мы не добавляем сложности, а улучшаем способ реализации элементов, поскольку тот же код, который существовал ранее, теперь имеет лучшую защиту и позволяет нам качественнее разделять задачи. Я говорю о более высокой степени защиты, потому что функция CreateLineInfos теперь является приватной. То есть вызывающему коду или пользователю не нужно знать, как именно будет создан индикатор. Он просто запрашивает создание индикатора. Способ реализации этого может сильно измениться со временем, но для пользователя он всегда будет оставаться прежним.
Однако выполнение того, что я только что показал, не решает ни одну из имеющихся у нас проблем. Это лишь упрощает реализацию кода более эффективным способом. Обратите внимание, что весь код остается точно таким же, как и прежде, и его работа никак не изменилась. Это первое, что мы всегда должны гарантировать. Не изменяйте код, пока он не будет работать точно так же, как и до того, как вы поместили его внутрь класса. После этого дальнейшие действия будут зависеть от того, что вам нужно или хочется сделать в первую очередь. Но поскольку класс уже создан, мы можем сразу же поместить его в заголовочный файл. Таким образом, мы избегаем излишней перегрузки основного кода. Затем мы создадим файл с тем же именем, что и класс, который будет в него помещен. Хотя это является хорошей практикой программирования, она не является обязательной. Таким образом, новый основной код можно увидеть ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #include <Market Replay\Auxiliar\C_Terminal.mqh> 09. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 10. //+------------------------------------------------------------------+ 11. input color user00 = clrRoyalBlue; //Color Line Price 12. input color user01 = clrForestGreen; //Color Line Take Profit 13. input color user02 = clrFireBrick; //Color Line Stop Loss 14. //+------------------------------------------------------------------+ 15. C_Terminal *Terminal; 16. C_IndicatorPosition *Positions; 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. Terminal = new C_Terminal(); 21. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 22. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+ 27. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 28. { 29. return rates_total; 30. } 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. delete Terminal; 35. delete Positions; 36. } 37. //+------------------------------------------------------------------+
Исходный код Position View
А куда делся код класса? Итак, теперь он находится в файле, расположение которого указано в строке 09 приведенного выше кода. Прошу заметить, что класс перенёс с собой некоторые данные, которые ранее находились здесь, в основном коде. Это связано с тем, что, как говорилось ранее, пользователю не нужно знать, как всё устроено. Вам нужно лишь знать, что именно нужно вызвать и какие параметры передать коду класса. То, как класс будет обрабатывать данные, не интересует вызывающий код. Итак, посмотрите на код заголовочного файла C_IndicatorPosition, который приведен ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. class C_IndicatorPosition 09. { 10. private : 11. struct st00 12. { 13. long id; 14. string szPrefixName; 15. }m_Infos; 16. //+------------------------------------------------------------------+ 17. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 18. { 19. if (price <= 0) return; 20. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 21. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 22. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 23. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 24. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 25. } 26. //+------------------------------------------------------------------+ 27. public : 28. //+------------------------------------------------------------------+ 29. C_IndicatorPosition(const long Id) 30. { 31. ZeroMemory(m_Infos); 32. m_Infos.id = Id; 33. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 34. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 35. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 36. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 37. } 38. //+------------------------------------------------------------------+ 39. ~C_IndicatorPosition() 40. { 41. if (m_Infos.id > 0) 42. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 43. } 44. //+------------------------------------------------------------------+ 45. }; 46. //+------------------------------------------------------------------+ 47. #undef def_SufixLinePrice 48. #undef def_SufixLineTake 49. #undef def_SufixLineStop 50. //+------------------------------------------------------------------+
Исходный код для C_IndicatorPosition.mqh
Обратите внимание, что код остался прежним. В конец файла добавили всего три новые строки. Их цель — исключить определения, которые имеет смысл оставлять только в этом заголовочном файле. То есть мы используем принцип наименьших привилегий. Или, чтобы вы поняли: никому не нужно знать то, что его не касается. Это предотвращает утечку информации без вашего ведома. Кроме того, это, конечно же, является хорошей практикой программирования, так как другие коды могут использовать те же имена, что вы определили здесь. И если вы не устраните их здесь, вам придется делать это в другом месте, что вызывает множество неудобств при улучшении очень большого кода, поскольку из-за конфликтов в определениях появляются ошибки, которые не имеют никакого смысла.
Хорошо. Но если вы теперь посмотрите только на код в заголовочном файле, вы заметите, что там объявляются и используются значения, которые зависят от основного кода. Это создает проблему, поскольку при изменении основного кода данный заголовочный файл также придется изменить. Иными словами, это огромная работа, поэтому нам необходимо это исправить. Хорошее решение — потребовать от основного кода сообщать значения независимо друг от друга. Таким образом, ниже представлен новый заголовочный файл с уже внесенными изменениями.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. class C_IndicatorPosition 09. { 10. private : 11. struct st00 12. { 13. long id; 14. string szPrefixName; 15. color corPrice, corTake, corStop; 16. }m_Infos; 17. //+------------------------------------------------------------------+ 18. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 19. { 20. if (price <= 0) return; 21. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 22. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 23. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 24. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 25. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 26. } 27. //+------------------------------------------------------------------+ 28. public : 29. //+------------------------------------------------------------------+ 30. C_IndicatorPosition(const long Id, color corPrice, color corTake, color corStop) 31. { 32. ZeroMemory(m_Infos); 33. m_Infos.id = Id; 34. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 35. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice = corPrice, "Position opening price."); 36. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake = corTake, "Take Profit point."); 37. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop = corStop, "Stop Loss point."); 38. } 39. //+------------------------------------------------------------------+ 40. ~C_IndicatorPosition() 41. { 42. if (m_Infos.id > 0) 43. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 44. } 45. //+------------------------------------------------------------------+ 46. }; 47. //+------------------------------------------------------------------+ 48. #undef def_SufixLinePrice 49. #undef def_SufixLineTake 50. #undef def_SufixLineStop 51. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Прошу заметить, что теперь конструктор должен будет принимать три новых аргумента. Они передают цвета для каждой из линий. Таким образом, основной код должен быть изменен в следующем месте, которое показано в приведенном ниже фрагменте:
17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. Terminal = new C_Terminal(); 21. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 22. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID, user00, user01, user02); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+
Фрагмент из индикатора
Всё просто. Теперь у нас есть практически полностью независимый код. Это связано с тем, что мы всё ещё зависим от считывания основным кодом информации о наличии или отсутствии какой-либо позиции. Это делается в строке 21, в предыдущем фрагменте. Итак, давайте это исправим, потому что мы хотим, чтобы индикатор работал на счетах HEDGING. Поскольку на них мы сможем иметь более одной открытой позиции одновременно и по одному и тому же символу. Но для решения данной проблемы нам придётся внести в код несколько более существенные изменения. Однако, поскольку мы разделили вещи, делать это изменение становится намного проще и приятнее. Таким образом, новый код индикатора можно увидеть ниже:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_ShortName "Position View" 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 11. //+------------------------------------------------------------------+ 12. input color user00 = clrRoyalBlue; //Color Line Price 13. input color user01 = clrForestGreen; //Color Line Take Profit 14. input color user02 = clrFireBrick; //Color Line Stop Loss 15. //+------------------------------------------------------------------+ 16. C_IndicatorPosition *Positions; 17. //+------------------------------------------------------------------+*/ 18. int OnInit() 19. { 20. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 21. Positions = new C_IndicatorPosition(user00, user01, user02); 22. if (!Positions.CheckCatch()) 23. { 24. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 25. return INIT_FAILED; 26. } 27. 28. return INIT_SUCCEEDED; 29. } 30. //+------------------------------------------------------------------+ 31. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 32. { 33. return rates_total; 34. } 35. //+------------------------------------------------------------------+ 36. void OnDeinit(const int reason) 37. { 38. delete Positions; 39. } 40. //+------------------------------------------------------------------+
Исходный код Position View
Обратите внимание, что в коде процедуры OnInit внесены довольно простые изменения. По сути, сейчас мы работаем таким образом, что в случае сбоя индикатора он будет удален с графика. Это достигается с помощью строк 20 и 24. Строка 24 — это именно та, которая удалит индикатор, если он не сможет остаться на графике. Но какие именно условия препятствуют тому, чтобы индикатор оставался на графике? Хорошо, на данном этапе осталось только одно условие. Чтобы это понять, давайте посмотрим на код, который вызывается в строке 22. Таким образом, переходим к новому коду класса:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. #include "..\Auxiliar\C_Terminal.mqh" 09. //+------------------------------------------------------------------+ 10. class C_IndicatorPosition : private C_Terminal 11. { 12. private : 13. struct st00 14. { 15. ulong ticket; 16. string szPrefixName; 17. color corPrice, corTake, corStop; 18. }m_Infos; 19. //+------------------------------------------------------------------+ 20. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 21. { 22. if (price <= 0) return; 23. CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 24. ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price); 25. ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription); 26. ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription); 27. ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 28. } 29. //+------------------------------------------------------------------+ 30. public : 31. //+------------------------------------------------------------------+ 32. C_IndicatorPosition(color corPrice, color corTake, color corStop) 33. :C_Terminal() 34. { 35. ZeroMemory(m_Infos); 36. m_Infos.corPrice = corPrice; 37. m_Infos.corTake = corTake; 38. m_Infos.corStop = corStop; 39. } 40. //+------------------------------------------------------------------+ 41. ~C_IndicatorPosition() 42. { 43. if (m_Infos.ticket != 0) 44. ObjectsDeleteAll(GetInfoTerminal().ID, m_Infos.szPrefixName); 45. } 46. //+------------------------------------------------------------------+ 47. bool CheckCatch(void) 48. { 49. for (int count = PositionsTotal() - 1; count >= 0; count--, m_Infos.ticket = 0) 50. if ((m_Infos.ticket = PositionGetTicket(count)) > 0) 51. if (PositionGetString(POSITION_SYMBOL) == GetInfoTerminal().szSymbol) 52. { 53. m_Infos.szPrefixName = IntegerToString(m_Infos.ticket); 54. if (ObjectFind(GetInfoTerminal().ID, m_Infos.szPrefixName + def_SufixLinePrice) < 0) 55. break; 56. } 57. if (m_Infos.ticket == 0) return false; 58. IndicatorSetString(INDICATOR_SHORTNAME, IntegerToString(m_Infos.ticket)); 59. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price."); 60. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point."); 61. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point."); 62. 63. return true; 64. } 65. //+------------------------------------------------------------------+ 66. }; 67. //+------------------------------------------------------------------+ 68. #undef def_SufixLinePrice 69. #undef def_SufixLineTake 70. #undef def_SufixLineStop 71. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Прошу заметить, что код разрастается и становится всё более сложным. Тем не менее, я продвигаюсь шаг за шагом, чтобы вы могли действительно следить за тем, что мы делаем. Это связано с тем, что глубокое понимание этого кода будет очень важным в будущем. Так что не торопитесь. Спокойно и внимательно следите за происходящими изменениями, потому что я буду показывать и объяснять их постепенно, чтобы понимание было как можно более полным.
Обратите внимание, что с момента последнего просмотра данного кода в нем отсутствовали директивы include. Теперь это так. И именно это позволяет основному коду требовать написания меньшего объема кода, а также, конечно, помогает в других аспектах написания кода. В строке 10 мы включаем посредством наследования класс C_Terminal в класс C_IndicatorPosition. Такое наследование позволяет нам использовать, по крайней мере на данный момент, публичные методы класса C_Terminal непосредственно в классе C_IndicatorPosition. Создается впечатление, что в классе C_IndicatorPosition больше функций, чем существует в MQL5, но это всего лишь иллюзия, поскольку используемые функции на самом деле находятся в классе C_Terminal.
Однако имейте в виду, что вы не сможете использовать эти же функции класса C_Terminal вне класса C_IndicatorPosition. Это связано с тем, что наследование является приватным, что предотвращает внешний доступ к методам класса C_Terminal через класс C_IndicatorPosition.
Но поскольку мы наследуем класс C_Terminal, нам необходимо инициализировать его перед использованием класса C_IndicatorPosition. Это делается в строке 33. Теперь обратите внимание, что в конструкторе мы удалили находившийся там ранее код и оставили только тот код, который будет инициализировать внутренние переменные класса. Хорошо. Теперь перейдем к строке 47, где у нас находится функция, которая проверяет должен ли индикатор оставаться на графике во время инициализации.
Начиная с этого момента, в цикле строки 49 мы переходим к рассмотрению позиций на счетах HEDGING. До этого момента мы рассматривали только счета NETTING. Обратите внимание, что мы будем перебирать список открытых позиций в поисках позиции, которая ещё не была захвачена. Итак, как же нам это сделать? Хорошо, на каждой итерации цикла мы будем проверять строку 50. Если эта проверка окажется положительной, мы выполним новую в строке 51. А теперь внимание: Если эта вторая проверка в строке 51 окажется положительной, в строке 53 мы сгенерируем префикс для имен создаваемых объектов. До настоящего момента всё остаётся по-прежнему. Конечно, за исключением того, что теперь мы сможем учитывать/обрабатывать более одной открытой позиции по одному и тому же символу, что как раз и происходит на счетах HEDGING.
Отлично. А вот теперь начинается самое интересное. Как индикатор узнает, была ли позиция уже проанализирована или нет? Обратите внимание на это. Каждый индикатор позиции, присутствующий на графике, будет отслеживать, или, точнее, относиться к, одной конкретной позиции. Возможно, имеется несколько открытых. Однако каждый индикатор будет относиться только к одной позиции. Один и тот же индикатор НЕ будет отслеживать более одной позиции. Таким образом, чтобы индикатор знал, анализируется ли уже позиция другим индикатором позиции или нет, мы перебираем объекты графика. Это делается в строке 54.
Если MetaTrader 5 сообщает, что объект уже находится на графике, мы будем искать новую открытую позицию по тому же символу. Цикл в строке 49 будет запущен снова. Подобные ситуации будут повторяться до тех пор, пока не будет выполнено одно из двух условий. Первое заключается в том, что у нас больше не будет открытых позиций. Во-вторых, выполнение строки 55.
В любом случае, строка 57 будет выполнена в какой-то момент. Если переменная ticket не содержит значения, мы вернем значение false, указывая основному коду на необходимость удаления индикатора с графика. Если в переменной ticket есть какое-то значение, мы зададим индикатору новое краткое имя, чтобы иметь возможность обратиться к нему позже, и создадим линии так, как это было показано ранее. Наконец, в строке 63 мы вернем значение true. Таким образом, мы гарантируем, что индикатор будет работать одинаково как на счетах HEDGING, так и на счетах NETTING, без каких-либо дополнительных проверок.
Заключительные идеи
В этой статье мы представили необходимые изменения, чтобы индикатор открытых позиций стал действительно функциональным на счетах HEDGING и NETTING. Действительно, и это легко заметить, данный индикатор пока что не делает ничего, кроме как показывает линии, на которых находятся цены, соответствующие данной позиции. Кроме того, данный индикатор не может определить, что позиция больше не существует. Для того чтобы вы действительно заметили это, вам, как трейдеру, необходимо изменить таймфрейм графика.
Таким образом, индикатор обнаружит, что позиция была закрыта. Заметив это, он будет удалён с графика и, таким образом, будет удален естественным путем, без какого-либо дополнительного вмешательства. В следующей статье мы еще больше улучшим этот индикатор, добавив в него несколько новых функций.
| Файл | Описание |
|---|---|
| Experts\Expert Advisor.mq5 | Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study). |
| Indicators\Chart Trade.mq5 | Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study). |
| Indicators\Market Replay.mq5 | Создает элементы управления для взаимодействия с сервисом репликации/моделирования (для взаимодействия требуется Mouse Study) |
| Indicators\Mouse Study.mq5 | Обеспечивает взаимодействие между графическими элементами управления и пользователем (необходимо как для работы системы репликации/моделирования, так и на реальном рынке). |
| Indicators\Order Indicator.mq5 | Отвечает за отображение рыночных ордеров, обеспечивая взаимодействие с ними и контроль над ними. |
| Indicators\Position View.mq5 | Отвечает за отображение рыночных позиций, обеспечение взаимодействия с ними и контроль над ними. |
| Services\Market Replay.mq5 | Создает и поддерживает сервис репликации/моделирования рынка (основной файл всей системы). |
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/13167
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
От начального до среднего уровня: Песочница и MetaTrader
Нейросети в трейдинге: От рыночного шума к устойчивому торговому плану (Окончание)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования