
Моделирование рынка (Часть 01): Кросс-ордеры (I)
Введение
В предыдущей статье Разработка системы репликации (часть 78): Новый Chart Trade (V), мы рассмотрели, как советник будет интерпретировать информацию, предоставляемую Chart Trade. Информация, которую будет передавать Chart Trade, зависит от взаимодействия пользователя с ним. То есть, когда пользователь нажимает кнопку покупки, продажи или закрытия позиции, на график передается сообщение. Одна из задач советника на графике - перехватить, декодировать и выполнить задание, указанное в сообщении.
Несмотря на то, что данный механизм довольно прост, а также достаточно надежен, у нас есть небольшая проблема. На самом деле, это не столько проблема, сколько недостаток. С этим нужно разобраться до того, как мы отправим ордеры на торговый сервер.
Если вы не понимаете, о чем идет речь, возможно, это связано с тем, что вы не торгуете символами, а точнее, фьючерсными контрактами. У этих символов есть срок действия. Часто торгуются два типа: полный курс, определяемый более высоким объемом, и мини-курс, который обычно понимается как доля полного курса, что облегчает определенные типы стратегий.
Не будем перечислять достоинства и описывать такие стратегии. Но на самом деле для создания стратегии нам часто требуется меньшее количество контрактов. Всем, кто интересуется этой темой, стоит изучить стратегии HEDGE. Для нас, программистов, действительно интересно, как совершить сделку в мини-контроле с полным контрактом на графике.
Однако, помимо этого момента, у нас есть еще одна проблема, связанная с долгосрочными стратегиями. Опять же, мы не будем вдаваться в подробности, но всякий раз, когда истекает срок действия контракта, а данный срок всегда известен всем, то начинается новый контракт, или, скорее, новая серия. И это является большой проблемой для долгосрочного трейдера, так как предполагает использование нескольких последующих серий. Это связано с тем, что все показатели и средние значения должны быть пересчитаны.
Чтобы лучше понять эту проблему, давайте воспользуемся фьючерсным контрактом на доллар, который торгуется на B3 (Бразильская фондовая биржа). Данный контракт имеет ежемесячный срок истечения. То есть каждый месяц одна серия закрывается и начинается новая. Учитывая, что у нас в среднем 20 торговых дней (пять рабочих дней в среднем в течение четырех недель в месяц), у нас возникнут серьезные проблемы при использовании, допустим, 20-периодной скользящей средней, поскольку к тому времени, когда средняя будет фактически рассчитана, контракт уже закончится и начнется новый. При этом учитывается только среднее значение за 20 периодов, без учета других показателей, которым для графического представления требуются еще более длительные периоды. Другими словами, у нас большая проблема.
Поэтому у нас есть возможность использовать историю будущего контракта. Однако тот факт, что мы можем использовать историю, не решает проблему. На самом деле это создает другие проблемы для нас. Помните, что для трейдера, торгующего контрактом, не имеет значения, как сервер получит информацию или как она будет представлена на графике. Ему просто нужно, чтобы информация и операции с ней отображались и выполнялись. Нам, программистам, предстоит придумать, как графически представить информацию и передать ордеры, размещенные на графике, на торговый сервер.
В статье Разработка торгового советника с нуля (Часть 11): Система кросс-ордеров, мы рассказали о ней несколько подробностей. Однако в этой серии, которая также предполагает использование системы репликации/моделирования, наша проблема становится еще более серьезной. Но мы последуем примеру Наполеона: разделяй и властвуй. Мы разделим эту проблему на части, чтобы выработать решение и, таким образом у нас появится почва для окончательной разработки системы моделирования ордеров. Давайте начнем с рассмотрения вопроса об использовании индикаторов во фьючерсных контрактах. Поскольку долларовый фьючерс - самый экстремальный из известных мне случаев, мы сосредоточимся на нем. Но вы должны понимать следующее: то, что будет объяснено, можно и нужно адаптировать к другим случаям, которые не менее сложны или включают те же понятия, которые применяются здесь, например, к сделкам с другими типами контрактов.
Начинаем внедрение решения
В случае с вышеупомянутой статьей, в которой была разработана система кросс-ордеров, было довольно сложно вносить некоторые корректировки для охвата других типов контрактов. Однако, исходя из практических соображений, мы будем действовать именно так, чтобы значительно упростить подобные настройки. Не для трейдера, а для нас, кому необходимо всё запрограммировать. Трейдеру придется приспособиться к тому, что мы будем делать. Но для него это будет намного проще, так как мы дадим ему возможность выбрать, чем он хочет торговать - полным контрактом или мини-контрактом.
Чтобы всё работало, по крайней мере на этом раннем этапе, когда связь будет осуществляться с реальным торговым сервером, нам придется внести небольшие специальные изменения в существующий код. Давайте начнем с понимания следующего факта: как уже говорилось во введении, лучший график для использования - это история контракта. Однако историей контракта нельзя торговать напрямую.
Для этого нам необходимо использовать систему, позволяющую направлять ордеры, выставленные на графике истории, на нужный трейдеру контракт. В этом случае помните, что трейдер может захотеть выполнить операцию по полному контракту или по мини-контракту. Пока что мы не будем об этом беспокоиться. Во-первых, нужно понимать, что информация, представленная на графике, - это то же самое, что и история контракта. И всё.
В случае с B3 (Бразильская фондовая биржа) фьючерсные контракты имеют шесть различных номенклатур. Это касается каждого конкретного контракта. То есть для полных контрактов существует шесть видов, и также для мини-контрактов их шесть. Другими словами, - это серьезное осложнение. Но несмотря на это, на самом деле существует три типа, разделенные на две модели.
И это значительно облегчает нам работу. Однако я советую вам изучить различия между этими тремя типами, так как информация, представленная на графике, значительно отличается для каждого из них. Если вы сами собираетесь программировать решение, обязательно предупредите трейдера об этих трех типах, поскольку многие из них совершенно не знают о них. Но если вы собираетесь не только программировать решение, но и использовать его, я настаиваю обратить внимание на мой совет, потому что вы можете очень сильно пострадать, если не поймете разницы между типами.
Хорошо, мы знаем, что существует три типа номенклатуры, с двумя вариациями каждой. Договорились. Но для нас, программистов, это не главное, - это важно для трейдера. Для нас важно, есть ли в этой номенклатуре какое-либо правило, и если оно существует, то как мы можем использовать это правило для создания системы кросс-ордеров?
Это правило действительно существует. Мы уже используем его, и мы делаем это уже некоторое время. Посмотрите, как это используется в приведенном ниже фрагменте кода.
38. //+------------------------------------------------------------------+ 39. void CurrentSymbol(void) 40. { 41. MqlDateTime mdt1; 42. string sz0, sz1; 43. datetime dt = macroGetDate(TimeCurrent(mdt1)); 44. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 45. 46. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 47. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 48. switch (eTS) 49. { 50. case DOL : 51. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 52. case IND : 53. case WIN : sz1 = "GJMQVZ"; break; 54. default : return; 55. } 56. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 57. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 58. } 59. //+------------------------------------------------------------------+
Фрагмент кода из файла C_Terminal.mqh
Данный фрагмент является частью исходного кода заголовочного файла C_Terminal.mqh. Обратите внимание, что в строке 44 мы определяем названия фьючерсных контрактов, подлежащих хеджированию. Можно добавить к этому списку другие названия, если хотим работать с другими контрактами, например, кукурузой, крупным рогатым скотом, S&P, евро и т.д. Но не забывайте соблюдать правила номенклатуры для каждого контракта, чтобы знать, как определить текущий контракт. Потому что данная процедура, как показано выше, не возвращает прошлый контракт, не говоря уже о контракте, который будет действовать через два или более срока. Она всегда возвращает действующий договор.
Для этого захватим первые три знака из названии символа в строке 46. Независимо от символа, он всегда будет захватывать первые три знака. Это связано с тем, что согласно правилу именования B3 (Бразильская фондовая биржа) первые три знака в наименовании символа указывают на то, какой символ мы используем, поскольку символ на графике не обязательно может быть фьючерсным контрактом. В строке 46 название символа сохраняется в переменной, которая будет содержать имя для всех остальных кодов. Обратите внимание на этот факт.
Теперь, в строке 47, мы осуществляем сканирование в перечислении контрактов, которые мы реализуем. Прошу заметить, что идея здесь заключается в том, чтобы захватить правильное название. Поэтому в перечислении в строке 44 нам нужно, чтобы название было похоже на название контракта. Поскольку в B3 (Бразильская фондовая биржа) используются символы с заглавной буквы, нам нужно, чтобы они были заглавными и в перечислении. Когда контракт будет найден или список закончится, цикл в строке 47 завершится.
Затем в строке 48 можно проверить, какое значение было найдено. Если совпадений не найдено, выполнится код в строке 54. В любом другом случае мы приступим к созданию номенклатуры контрактов. Окончательный вариант номенклатуры приводится в строке 57, где мы проверяем, является ли контракт, созданный процедурой, текущим контрактом, то есть процедура сама просканирует список возможных будущих контрактов, пока не найдет текущий контракт.
Однако во всей этой процедуре есть один момент, который заслуживает внимания. Он должен использовать базовое название контракта, основанное на названии символа. То есть можно переписать историю контрактов только на текущее название контракта. Невозможно перейти от полной истории контракта к текущему контракту (тип которого это мини-контракт), согласно указанному выше коду. Именно данную проблему нам и предстоит решить в этой статье.
Таким образом, мы дадим трейдеру возможность выбрать, использовать ли в сделке полный контракт или мини-контракт с помощью истории одного из контрактов на графике. Хорошо, давайте сделаем это так, чтобы при необходимости можно было бы вносить изменения, и если их нужно внести, то добавить минимум кода, потому что вероятность ошибок возрастает с увеличением изменений и добавлений к коду.
Для достижения данной цели новый код предыдущего фрагмента был модифицирован и представлен ниже:
38. //+------------------------------------------------------------------+ 39. void CurrentSymbol(bool bUsingFull) 40. { 41. MqlDateTime mdt1; 42. string sz0, sz1; 43. datetime dt = macroGetDate(TimeCurrent(mdt1)); 44. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 45. 46. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 47. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 48. switch (eTS) 49. { 50. case DOL : 51. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 52. case IND : 53. case WIN : sz1 = "GJMQVZ"; break; 54. default : return; 55. } 56. sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS))); 57. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 58. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 59. } 60. //+------------------------------------------------------------------+
Фрагмент кода из файла C_Terminal.mqh
Обратите внимание, что изменения минимальны. Первое из изменений касается добавления параметра, который должна получить функция в строке 39. Такой аргумент призван подсказать процедуре, как должен называться «построенный» контракт - полный или мини. Этот выбор должен сделать трейдер. Мы, как программисты, должны предложить трейдеру возможность использовать график по своему усмотрению. Разумеется, при условии, что данные символа, которые будут размещены на графике, каким-то образом представляют собой торгуемый контракт. Мы, как программисты, можем делать гораздо более экзотические вещи, но мы не будем усложнять нашу задачу.
В дополнение к этому изменению в строку 39 добавили новую строку. Хотя такое дополнение не обязательно, поскольку мы можем изменить код, но я решил добавить его, чтобы облегчить объяснение и, прежде всего, чтобы вы могли понять, что мы делаем. Код можно поместить туда, где находится переменная sz0 в строке 58, и он будет работать точно так же. Однако такое объяснение будет слишком запутанным.
Для тех, кто не понимает, что делает строка 56 с переменной sz0, мы приведем подробное объяснение. Обратите внимание, что мы игнорируем всё, что связано с названием символа. Мы преобразуем в строку перечисление из строки 44. Это связано с тем, что MQL5 позволяет сделать это с помощью функции EnumToString.
Теперь перейдем к деталям, которые могут усложнить нам жизнь, если работаем с фьючерсным контрактом, в котором нет полного контракта и мини-контракта. Подобная ситуация довольно часто встречается, когда речь идет о ТОВАРАХ. Но для наших целей, а это индексы и валюты (в данном случае доллар), существуют как и мини, так и полные контракты.
Перечисление всегда начинается со значения, равного нулю, если мы не определили значение для начала перечисления. Таким образом, мини-контракты определяются как четные значения, а полные контракты - как нечетные. Важно, чтобы вы поняли это. Значения представлены в двоичной форме, и это еще один важный момент. Но главное - знать, как выделить тот или иной бит. В случае двоичных значений младший бит, то есть крайний правый, указывает, является ли значение четным или нечетным. Затем, выполнив операцию AND для выделения наименее значимого бита, можно проверить, является ли значение четным или нечетным. Прошу помнить, что в перечислении мини-контракты четные, а полные контракты - нечетные.
Затем, если значение нечетное, выполняем первую часть тернарного оператора. Если значение четное, то выполняется вторая часть тернарного оператора. Пока, я думаю, всё понятно. Затем мы проведем новую проверку для каждой части тернарного оператора, и у нас получится новый тернарный оператор. В этом втором тернарном операторе проверка направлена на корректировку значения переменной eTS, чтобы указать правильное название, которое будет использоваться в контракте.
Давайте разберемся теперь в следующем: если контракт, например, WDO, то значение переменной eTS будет равно двум, то есть четному значению. В этом случае выполнится вторая часть первого тернарного оператора. При выполнении этой второй части новый тернарный оператор будет выполнять новую проверку. Он проверит, хотим ли мы использовать в вызове процедуры полный контракт или мини-контроль.
Если мы хотим использовать полный контракт, этот второй тернарный оператор приведет к увеличению переменной eTS на единицу. То есть, значение, которое раньше было два, станет три, и, посмотрев на третью позицию в перечислении, видно, что точка отсчета - это DOL. Когда MQL5 выполнит функцию EnumToString, данное значение три будет преобразовано в строку "DOL", что позволит использовать название контракта для всего контракта, даже если на графике символ отображается в виде истории мини-долларового контракта.
То же самое касается и обратного процесса. Если мы находимся на графике истории контрактов на полный доллар и сообщаем процедуре, что хотим торговать контрактом на мини-доллар, тесты сделают первый тернарный оператор равным true, что приведет к выполнению первой части. Когда первая часть будет выполнена, второй тернарный оператор не сработает, в результате чего значение eTS, которое было нечетным (три), будет вычтено на единицу и станет равным двум.
Если коротко: Подведем итог: значение, найденное в строке 47, будет сброшено в строке 56, чтобы название контракта соответствовало тому, которое ожидает оператор, независимо от того, хочет ли он управлять мини-контролем или всем контрактом. Единственное требование - график должен принадлежать истории одного из контрактов.
Хорошо, очень мило. Но предположим, что у нас нет мини-контроля, т.е. у нас только полный фьючерсный контракт. Как нам поступить в такой ситуации? В этом случае можно подумать, что есть два возможных решения, верно? Однако решение только одно: если мы задумаем продублировать значение в перечислении, чтобы получить четное и нечетное значение, компилятор не примет этого. Но можно поступить таким образом: расположить контракты в логическом порядке в перечислении, чтобы при тестировании определенного значения, в случае отсутствия мини-контрактов, переменная sz0 не менялась. Тогда, исходя из этого правильно определенного и известного значения, изменения не произойдут. Для этого нам придется пройти дополнительную проверку, но она не сложная.
Это решает первую часть проблемы. Однако проблема решилась не до конца. Чтобы решить еще один момент, нам нужно изменить кое-что еще в заголовочном файле C_Terminal.mqh. Это конструктор класса, так как он отвечает за вызов процедуры, показанной выше. Оригинальный конструктор должен быть изменен в соответствии с приведенной ниже версией.
72. //+------------------------------------------------------------------+ 73. C_Terminal(const long id = 0, const uchar sub = 0, const bool bUsingFull = false) 74. { 75. m_Infos.ID = (id == 0 ? ChartID() : id); 76. m_Mem.AccountLock = false; 77. m_Infos.SubWin = (int) sub; 78. CurrentSymbol(bUsingFull); 79. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 80. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 81. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 82. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 83. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 84. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 85. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 86. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 87. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 88. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 89. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 90. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 91. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 92. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 93. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 94. ChartChange(); 95. } 96. //+------------------------------------------------------------------+
Фрагмент кода из файла C_Terminal.mqh
Прошу заметить, что это два довольно простых изменения. Первое изменение находится в строке 73, где мы добавляем новый параметр. Данный новый параметр используется в строке 78, где мы вызываем процедуру, описанную выше. Обратите внимание, что по умолчанию мы определяем, что будем отдавать предпочтение использованию мини-контрактов. Однако мы хотим, чтобы трейдер сделал тот выбор, который ему больше всего подходит. Таким образом, в некоторых местах кода потребуется внести небольшие изменения.
Поскольку мы пока не изменяем код советника, нам необходимо внести изменения в код Chart Trade, и мы их внесем. Но, чтобы лучше разделить всё, давайте рассмотрим это в новой теме.
Преобразование Chart Trade в систему кросс-ордеров
Изменения в Chart Trade для работы с системой кросс-ордеров довольно просты. Хотя вы, возможно, захотите добавить какой-нибудь объект, позволяющий трейдеру напрямую изменять систему кросс-ордеров, я не буду этого делать. Идея заключается в том, чтобы вносить как можно меньше изменений в код. Добавление объекта, позволяющего трейдеру напрямую изменять тип кросс-ордера в Chart Trade, потребовало бы от нас добавления гораздо большего количества кода, чтобы просто покрыть эту функциональность. Однако мы можем позволить трейдеру изменить систему кросс-ордеров, изменив настройки индикатора. Помимо того, что это очень простое изменение, оно не потребует значительных модификаций в существующем коде. Первое, что мы сделаем, можно увидеть ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Chart Trade Base Indicator." 04. #property description "See the articles for more details." 05. #property version "1.80" 06. #property icon "/Images/Market Replay/Icons/Indicators.ico" 07. #property link "https://www.mql5.com/pt/articles/12536" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh> 12. //+------------------------------------------------------------------+ 13. #define def_ShortName "Indicator Chart Trade" 14. //+------------------------------------------------------------------+ 15. C_ChartFloatingRAD *chart = NULL; 16. //+------------------------------------------------------------------+ 17. enum eTypeContract {MINI, FULL}; 18. //+------------------------------------------------------------------+ 19. input ushort user01 = 1; //Leverage 20. input double user02 = 100.1; //Finance Take 21. input double user03 = 75.4; //Finance Stop 22. input eTypeContract user04 = MINI; //Cross order in contract 23. //+------------------------------------------------------------------+ 24. int OnInit() 25. { 26. chart = new C_ChartFloatingRAD(def_ShortName, new C_Mouse(0, "Indicator Mouse Study"), user01, user02, user03, (user04 == FULL)); 27. 28. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 29. 30. return INIT_SUCCEEDED; 31. } 32. //+------------------------------------------------------------------+ 33. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 34. { 35. return rates_total; 36. } 37. //+------------------------------------------------------------------+ 38. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 39. { 40. if (_LastError < ERR_USER_ERROR_FIRST) 41. (*chart).DispatchMessage(id, lparam, dparam, sparam); 42. } 43. //+------------------------------------------------------------------+ 44. void OnDeinit(const int reason) 45. { 46. switch (reason) 47. { 48. case REASON_INITFAILED: 49. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 50. break; 51. case REASON_CHARTCHANGE: 52. (*chart).SaveState(); 53. break; 54. } 55. 56. delete chart; 57. } 58. //+------------------------------------------------------------------+
Исходный код индикатора Chart Trade
Прошу заметить, что мы добавили строку 17, в которой есть перечисление. Она предназначена для того, чтобы помочь трейдеру или пользователю определить, что будет использоваться. Обратите внимание, что мы используем это перечисление в строке 22. В этот момент трейдер выбирает, будет ли советник использовать полный контракт или мини-контракт. Здесь возникает сложность, поскольку в идеале выбор должен быть сделан в советнике, а не здесь, в Chart Trade. Но, поскольку Chart Trade и советник - это две отдельные и самостоятельные сущности, мы будем действовать следующим образом.
Однако проблема не в Chart Trade и не в советнике, что мы уже рассматривали в предыдущей статье, им можно управлять через Chart Trade. На самом деле проблема кроется в другом, о чем мы расскажем позже. Именно здесь у нас возникает серьезная проблема, поскольку всё будет проходить через советник. Поэтому было бы идеально обустроить в нем всё необходимое. Однако сейчас, в демонстрационных целях, поскольку Chart Trade и советник разделены, мы оставим всё так, как есть.
Это заданное значение используется в строке 26. Но обратите внимание, что мы передаем в конструктор булево значение, а не числовое. Почему? Потому что для пользователя булево значение может быть не таким понятным. Для нас, программистов, булево значение является достаточно выразительным, поскольку мы проверяем только два условия: собирается ли трейдер использовать полный контракт или мини-контроль? Поэтому для программистов подходит булево значение. Таким образом мы можем передать его в конструктор класса, чтобы понять, как это отразится в коде, ведь нам нужно будет изменить только конструктор. Давайте рассмотрим это на примере данного фрагмента.
213. //+------------------------------------------------------------------+ 214. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const short Leverage, const double FinanceTake, const double FinanceStop, const bool bUsingFull) 215. :C_Terminal(0, 0, bUsingFull) 216. { 217. m_Mouse = MousePtr; 218. m_Info.IsSaveState = false; 219. if (!IndicatorCheckPass(szShortName)) return; 220. if (!RestoreState()) 221. { 222. m_Info.Leverage = Leverage; 223. m_Info.IsDayTrade = true; 224. m_Info.FinanceTake = FinanceTake; 225. m_Info.FinanceStop = FinanceStop; 226. m_Info.IsMaximized = true; 227. m_Info.minx = m_Info.x = 115; 228. m_Info.miny = m_Info.y = 64; 229. } 230. CreateWindowRAD(170, 210); 231. AdjustTemplate(true); 232. } 233. //+------------------------------------------------------------------+
Фрагмент файла C_ChartFloatingRAD.mqh
Здесь всё так же просто, как и в конструкторе класса C_Terminal. Мы просто добавляем новый параметр для получения конструктором в строке 214 и передаем его конструктору класса C_Terminal, в строке 215. Проще некуда. Настолько, что даже не нужно объяснять детали.
Как бы хорошо это ни было, нам нужно будет внести еще одно небольшое изменение. На самом деле нам нужно добавить немного кода в класс C_ChartFloatingRAD. Это нужно для того, чтобы Chart Trade мог сообщить советнику о том, что происходит, а точнее, о том, что пользователь ожидает от торговли. Данное изменение можно увидеть в приведенном ниже отрывке.
330. case MSG_BUY_MARKET: 331. ev = evChartTradeBuy; 332. case MSG_SELL_MARKET: 333. ev = (ev != evChartTradeBuy ? evChartTradeSell : ev); 334. case MSG_CLOSE_POSITION: 335. if ((m_Info.IsMaximized) && (sz < 0)) 336. { 337. string szTmp = StringFormat("%d?%s?%s?%c?%d?%.2f?%.2f", ev, _Symbol, GetInfoTerminal().szSymbol, (m_Info.IsDayTrade ? 'D' : 'S'), 338. m_Info.Leverage, FinanceToPoints(m_Info.FinanceTake, m_Info.Leverage), FinanceToPoints(m_Info.FinanceStop, m_Info.Leverage)); 339. PrintFormat("Send %s - Args ( %s )", EnumToString((EnumEvents) ev), szTmp); 340. EventChartCustom(GetInfoTerminal().ID, ev, 0, 0, szTmp); 341. } 342. break;
Фрагмент файла C_ChartFloatingRAD.mqh
То, что меняется на этом фрагменте настолько тонко, что можно и не заметить. Именно в строке 337 мы добавляем новое значение, которое будет передано советнику. Данное значение предназначено для информирования советника о названии символа или контракта, отображаемого в Chart Trade. Я уже предупредил, что данное изменение приведет к очередным изменениям в советнике. Но об этом мы поговорим позже.
Заключительные идеи
Хотя данная статья показывает, что возможно делать в рамках MQL5, есть и другие проблемы, которые нужно решать именно из-за того, что было сделано здесь. Подобные вещи далеко не так просты и далеко не всегда решаются полностью и легко. Это связано с тем, что использование системы кросс-ордеров, которая позволяет пользователю Chart Trade информировать советника о том, что на графике символа не представлен торгуемый символ, вызывает огромные проблемы. Многие из этих проблем не связаны с Chart Trade или советником. Я могу это сразу прояснить.
На самом деле проблема возникает, когда мы используем что-то, чего вы еще не видели, поскольку я еще не упомянул ни одной части кода, который нужно создать. Однако надо заметить, что разрешение пользователю определять в Chart Trade, какой тип контракта будет использоваться, - не самое лучшее решение. По крайней мере, в данный момент считаю именно так.
Возможно, то, что должно быть показано, еще претерпит изменения, которые сделают определение в Chart Trade чем-то интересным и устойчивым. Я всё еще думаю над простым способом показать всё это. Внедрение персонального решения - это просто и быстро, а вот внедрение того, что нужно объяснять и понимать, - сложно и требует много времени. Поэтому стоит ожидать новых изменений в системе Chart Trade - Советник, потому что они обязательно произойдут.
На видео ниже можно увидеть, как выглядит данный процесс на графике.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12536




- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования