Несколько индикаторов на графике (Часть 05): Превращаем MetaTrader 5 в систему RAD (I)
Введение
Несмотря на то, что многие люди не умеют программировать, они достаточно креативны и имеют отличные идеи, но отсутствие знаний или понимания программирования мешает им сделать некоторые вещи. Сегодня создадим собственного интерфейса Chart Trade для отправки рыночных ордеров или настройки параметров, используемых в отложенных ордерах. Мы сделаем это без программирования, просто используя функции, которые будут внутри советника. Мы любопытны, поэтому давайте посмотрим, как это будет выглядеть на наших мониторах:
Можно подумать: "Но как это сделать? Я ничего не знаю о программировании, или того, что я знаю, будет недостаточно". Chart Trade, который вы видите на изображении выше, был создан в самой платформе MT5 и был спроектирован так, как показано на рисунке ниже:
Теперь, зная, о чем пойдет речь в этой статье, можно быть полным этузиазма и идей для создания собственного графика. Но надо будет выполнить несколько шагов, чтобы всё это заработало. И как только вспомогательный код будет настроен, фантазия будет единственным ограничением для дизайна нашего собственного IDE Chart Trade. Эта статья является продолжением предыдущих, поэтому для полного и всестороннего понимания я рекомендую ознакомиться с предыдущими статьями этой серии.
Итак, приступим к работе.
Планирование
Для начала для графика, который будет использоваться в качестве IDE, нужно изменить свойства. Это нужно, чтобы уменьшить возможные побочные эффекты. Не то, чтобы они обязательно будут случаться, но дДело в том, что если график будет чистым, будет легче построить и спроектировать интерфейс Chart Trade. Поэтому открываем свойства графика и настраиваем, как показано на рисунках ниже.
Таким образом, экран будет абсолютно чистым и свободным от всего, что может помешать при разработке нашей IDE. Теперь давайте разберемся в вот в чем. Наша IDE будет сохранена как файл настроек, то есть как TEMPLATE, поэтому можно использовать любой из объектов, доступных в MetaTrader 5, но по практическим причинам мы будем использовать только некоторые из них, см. все доступные объекты в разделе "Типы объектов в MT5".
Объект | Тип координат, которые используются для позиционирования | Интересно для IDE |
---|---|---|
Текст | Дата и цена | НЕТ |
Метка | Расположение по оси X и Y | ДА |
Кнопка | Расположение по оси X и Y | ДА |
График | Расположение по оси X и Y | ДА |
Bitmap | Дата и цена | НЕТ |
Bitmap метка | Расположение по оси X и Y | ДА |
Редактировать | Расположение по оси X и Y | ДА |
Событие | Используется только дата | НЕТ |
Прямоугольная метка | Расположение по оси X и Y | ДА |
Поскольку мы собираемся использовать систему, которая может находиться в любой области экрана, непрактично использовать объект, который не использует систему координат X и Y для позиционирования, так как при использовании таких объекотв IDE может получиться совершенно другой. Поэтому ограничимся шестью объектами, которых более чем достаточно для создания интерфейса.
Идея заключается в том, чтобы расположить объекты в логическом порядке, подобно тому, как вы рисуете что-то на экране. Сначала создем фон, а затем располагаем объекты друг над другом, размещая и настраивая объекты по мере разработки интерфейса. Вот как это происходит:
Все это очень просто, нужно лишь немного практики, чтобы освоить этот способ проектирования и создать собственную среду IDE. Идея здесь очень похожа на ту, которая применяется в RAD-программах, используемых для создания программных интерфейсов, где пользовательский интерфейс может быть очень сложным при проектировании через код. Дело не в том, что мы не можем создать интерфейс непосредственно через код, а в том, что использование этого способа намного быстрее и проще модифицировать, что идеально подходит для тех, кто хочет интерфейс с собственным стилем.
Когда мы закончим, у нас может получиться интерфейс, как на рисунке ниже, или даже круче. Но здесь я постарался использовать как можно больше объектов, чтобы вы могли попробовать их. Вы же можете создать тот интерфейс, который понравится вам.
Это первый этап создания нашей IDE. Теперь нужно создать код, который фактически поддерживает этот интерфейс и делает его функциональным. Хотя тот простой факт, что можно создать свой собственный пользовательский интерфейс, уже является причиной для еще большей мотивации, и эта мотивация будет воплощена в коде.
Следующим шагом будет сохранение этого интерфейса как файла настроек, теперь мы можем сохранить его и, используя код из предыдущей версии, отобразить его как указатель. Это означает, что нам не потребуется вносить значительные изменения в исходный код, но если мы собираемся проверить возможность получения событий или отправки событий в нашу IDE, мы увидим, что это невозможно. Но если интерфейс был создан с использованием собственных объектов MetaTrader 5, почему нельзя отправлять и получать события от этих объектов? Ответ на этот вопрос легче показать, чем объяснить. Добавив следующий код в исходную версию советника, мы можем проверить его.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_OBJECT_CLICK: Print(sparam); break; // .... Остальной код ... } }
Этот код сообщает имя объекта, который получает клик и генерирует событие. В данном случае это событие CHARTEVENT_OBJECT_CLICK. Однако выведенное сообщение будет именем объекта, созданного советником, а не именем объектов в IDE. На первый взгляд это может показаться большой проблемой, которая делает невозможным использование нашей IDE, но есть очень простое решение: читаем файл настроек и затем создаем объекты, как указано в этом файле. Это создаст нашу IDE прямо на графике. Затем, анализируя файл настроек (TPL), находим данные, которые нам нужно использовать.
Ключ | Описание |
---|---|
<chart> | Запускает файл настроек |
</chart> | Завершает файл настроек |
<window> | Запускает структуру элементов, присутствующих на графике. |
</window> | Завершает структуру элементов, присутствующих на графике. |
<indicator> | Запускает структуру, которая сообщает данные, относящиеся к какому-либо индикатора |
</indicator> | Завершает структуру, которая сообщает данные какого-либо индикатора |
<object> | Запускает структуру, сообщающую данные о некотором объекте. |
</object> | Завершает структуру, сообщающую данные об объекте. |
Эта структура выглядит так внутри файла TPL.
<chart> .... ДАННЫЕ <window> ... ДАННЫЕ <indicator> ... ДАННЫЕ </indicator> <object> ... ДАННЫЕ </object> </window> </chart>
Та часть, которая нас действительно интересует, находится между <object> и </object>, и таких структур может быть несколько - каждая из них указывает на уникальный объект. Итак, для начала нам нужно изменить местоположение файла — его нужно поместить в такое место, где его можно прочитать. И это место — каталог FILES. Мможно изменить расположение, которое я использовал, в любом случае файл должен быть внутри дерева ФАЙЛОВ.
Важный момент: хоть система и получила модификацию, позволяющую очищать график при использовании файла настроек IDE, в идеале также иметь чистый файл с таким же именем в каталоге Profiles\Templates — это позволяет свести к минимуму некоторые остатки, присутствующие в шаблоне по умолчанию, как показано в предыдущих статьях. Основные изменения можно увидеть ниже:
#include <Auxiliar\Chart IDE\C_Chart_IDE.mqh> //+------------------------------------------------------------------+ class C_TemplateChart : public C_Chart_IDE { .... Другие части из кода .... //+------------------------------------------------------------------+ void AddTemplate(const eTypeChart type, const string szTemplate, int scale, int iSize) { if (m_Counter >= def_MaxTemplates) return; if (type == SYMBOL) SymbolSelect(szTemplate, true); SetBase(szTemplate, (type == INDICATOR ? _Symbol : szTemplate), scale, iSize); if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl"); if (szTemplate == "IDE") C_Chart_IDE::Create(m_IdSubWin); ChartRedraw(m_handle); } //+------------------------------------------------------------------+ void Resize(void) { #define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B) int x0 = 0, x1, y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, m_IdSubWin)); x1 = (int)((ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1)); for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++) { macro_SetInteger(OBJPROP_XDISTANCE, x0); macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1)); macro_SetInteger(OBJPROP_YSIZE, y); if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0); } ChartRedraw(); #undef macro_SetInteger } //+------------------------------------------------------------------+ ... Остальной код }
Обратите внимание, что мы добавляем интерфейс IDE в качестве нового класса, и он наследуется нашим исходным классом. Это означает, что функциональность исходного класса будет расширена, и это не вызовет никаких побочных эффектов в исходном коде.
До сих пор это была простая часть. Теперь нужно сделать кое-что посложнее, что будет поддерживать нашу IDE. Сначала нужно создать протокол сообщений, который будет использовать система. Этот протокол позволит системе работать как показано ниже:
Обратите внимание, что мы можем изменить системные данные, что на данный момент невозможно, но добавив протокол обмена сообщениями, можно будет сделать нашу IDE функциональной, поэтому давайте определим несколько вещей:
Сообщение | Цель |
---|---|
MSG_BUY_MARKET | Отправляет рыночный ордер BUY |
MSG_SELL_MARKET | Отправляет рыночный ордер SELL |
MSG_LEVERAGE_VALUE | Данные о факторе плеча |
MSG_TAKE_VALUE | Данные о значении финансовой прибыли |
MSG_STOP_VALUE | Данные о значении финансового стопа |
MSG_RESULT | Данные о текущем результате открытой позиции |
MSG_DAY_TRADE | Сообщает, будет ли операция закрыта в конце дня или нет |
Этот протокол является очень важным шагом, и после его определения нам придется внести изменения в файл настроек, поэтому, когда мы откроем список объектов, придется его изменить, чтобы он выглядел следующим образом:
Интерфейс, который я показываю, будет иметь список объектов, подобных изображению. Обратите внимание на тот факт, что NAME объектов соответствует каждому из сообщений, которые мы собираемся использовать. Какие другие объекты будут названы, не имеет значения, так как они используются для помощи в моделирования IDE, хотя объекты с именами сообщений будут получать и отправлять сообщения. Если вы хотите использовать больше сообщений или другой тип сообщений, просто внесите необходимые изменения в код класса, и MetaTrader 5 сам предоставит средства для обмена сообщениями между IDE и кодом советника.
Но нам все еще нужно изучить файл TPL, чтобы узнать, как создать наш класс объектов. Давайте теперь посмотрим, как объявляются объекты внутри файла TPL. Это правда, что у нас будет меньше доступа к свойствам объектов в файле TPL, чем через программирование, поскольку сам интерфейс терминала дает меньше доступа к свойствам объектов. Но даже уже имеющегося у нас доступа будет достаточно, чтобы наша IDE работала.
Итак, внутри файла TPL находится интересующая нас структура, от <object> до </object>. Из данных внутри этой структуры может показаться непонятным, как узнать, о каком типе объекта идет речь. Но если взглянуть повнимательнее, можно увидеть, что тип объекта определяется переменной type. Он получает разные значения для каждого из объектов. Итак у нас получается вот такая таблица, основанная на объектах, которые мы хотим использовать:
Значение переменной TYPE | Ссылаемый объект |
---|---|
102 | OBJ_LABEL |
103 | OBJ_BUTTON |
106 | OBJ_BITMAP_LABEL |
107 | OBJ_EDIT |
110 | OBJ_RECTANGLE_LABEL |
Что ж, наш класс уже может начать обретать форму, поэтому первая разработанная функция будет такой:
bool Create(int nSub) { m_CountObject = 0; if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false; FileReadInteger(m_fp, SHORT_VALUE); for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = ""; m_SubWindow = nSub; m_szLine = ""; while (m_szLine != "</chart>") { if (!FileReadLine()) return false; if (m_szLine == "<object>") { if (!FileReadLine()) return false; if (m_szLine == "type") { if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false; if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false; if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false; if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false; if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false; } } } FileClose(m_fp); return true; }
Обратите внимание, что первое, что нужно сделать — это открыть файл в режиме чтения и как бинарный файл. Это делается, чтобы ничего не пропустить, потому что при использовании HEXA-редактора мы имеем следующий вид TPL-файла. Обратите внимание, что он начинается с очень любопытного значения.
Звучит запутанно? В самом деле это не так. Файл использует кодировку UTF-16. Мы знаем, что организация осуществляется по строкам, поэтому создадим функцию для чтения всей строки сразу. Для этого создадим следующий код:
bool FileReadLine(void) { int utf_16 = 0; bool b0 = false; m_szLine = m_szValue = ""; for (int c0 = 0; c0 < 500; c0++) { utf_16 = FileReadInteger(m_fp, SHORT_VALUE); if (utf_16 == 0x000D) { FileReadInteger(m_fp, SHORT_VALUE); return true; } else if (utf_16 == 0x003D) b0 = true; else if (b0) m_szValue = StringFormat("%s%c", m_szValue, (char)utf_16); else m_szLine = StringFormat("%s%c", m_szLine, (char)utf_16); if (FileIsEnding(m_fp)) break; } return (utf_16 == 0x003E); }
Чтение старается быть максимально эффективным, поэтому, когда встречается знак равенства ( = ), разбивка происходит уже во время чтения, чтобы не делать этого позже. Цикл ограничивает строку максимум 500 символами, но это значение указано произвольно и может быть изменено при необходимости. При каждой новой встреченной строке функция возвращается, нам содержимое строки, чтобы можно было приступить к соответствующему разбору.
Понадобятся определенные переменные для поддержки нашего протокола сообщений. Они показаны в коде ниже:
class C_Chart_IDE { protected: enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP}; //+------------------------------------------------------------------+ #define def_HeaderMSG "IDE_" #define def_MaxObject eEDIT_STOP + 32 //+------------------------------------------------------------------+ private : int m_fp, m_SubWindow, m_CountObject; string m_szLine, m_szValue; bool m_IsDayTrade; struct st0 { string szName; int iPosX; }m_ArrObject[def_MaxObject]; // ... Остальной код класса ....
Определение def_MaxObject указывает на максимальное количество объектов, которое можно иметь. Это число зависит от количества сообщений, плюс дополнительное количество объектов, которые мы собираемся использовать. В данном случае у нас будет максимум 40 объектов, но его можно увеличить при необходимости. Первые 8 объектов будут использоваться для передачи сообщений между IDE и MT5, а псевдоним этих сообщений можно увидеть в перечислении eObjectsIDE, важно помнить об этом на случай, если вы захотите расширить систему или адаптировать её под что-то другое.
Это только первая часть системы поддержки. На что еще стоит обратить внимание: константа, которая имеет отношение именно к системе сообщений. На самом деле то, как язык MQL5 работает с константами, немного смущает тех, кто уже программирует на C / C++. Дело в том, что в этих языках константа объявляется в самом объявлении переменной, но здесь сам способ создания может сделать код немного сложнее. Однако, с этим можно жить, так как константы в принципе используются редко. Ниже выделенным шрифтом показано, как можно сделать это.
public : static const string szMsgIDE[]; // ... Остальной код класса .... }; //+------------------------------------------------------------------+ static const string C_Chart_IDE::szMsgIDE[] = { "MSG_RESULT", "MSG_BUY_MARKET", "MSG_SELL_MARKET", "MSG_DAY_TRADE", "MSG_CLOSE_POSITION", "MSG_LEVERAGE_VALUE", "MSG_TAKE_VALUE", "MSG_STOP_VALUE" }; //+------------------------------------------------------------------+
Давайте посмотрим на определенные константы, поскольку они являются точно такими же значениями, используемыми в именах объектов в интерфейсе. Система была спроектирована так, чтобы быть нечувствительной к регистру, то есть для нее разницы нет. При желании это поведение можно изменить, но я не советую этого делать.
После выполнения всех этих шагов можно перейти к следующему, поэтому давайте вернемся к файлу TPL и проанализируем его. Посмотрите на фрагмент файла ниже:
После определения типа объекта использования у нас есть ряд данных, которые сообщают свойства объекта, такие как имя, положение, цвет, шрифт и т.д. Эти же свойства нужно передать нашим внутренним объектам, и поскольку это часто повторяющаяся работа, мы можем создать общую функцию, которая показана ниже:
bool LoopCreating(ENUM_OBJECT type) { #define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) #define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) int c0; bool b0; string sz0 = m_szValue; while (m_szLine != "</object>") if (!FileReadLine()) return false; else { if (m_szLine == "name") { b0 = false; StringToUpper(m_szValue); for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++); c0 = (b0 ? c0 : m_CountObject); m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue); ObjectDelete(Terminal.Get_ID(), m_ArrObject[c0].szName); ObjectCreate(Terminal.Get_ID(), m_ArrObject[c0].szName, type, m_SubWindow, 0, 0); } if (m_szLine == "pos_x" ) m_ArrObject[c0].iPosX = (int) StringToInteger(m_szValue); if (m_szLine == "pos_y" ) macro_SetInteger(OBJPROP_YDISTANCE , StringToInteger(m_szValue)); if (m_szLine == "size_x" ) macro_SetInteger(OBJPROP_XSIZE , StringToInteger(m_szValue)); if (m_szLine == "size_y" ) macro_SetInteger(OBJPROP_YSIZE , StringToInteger(m_szValue)); if (m_szLine == "offset_x" ) macro_SetInteger(OBJPROP_XOFFSET , StringToInteger(m_szValue)); if (m_szLine == "offset_y" ) macro_SetInteger(OBJPROP_YOFFSET , StringToInteger(m_szValue)); if (m_szLine == "bgcolor" ) macro_SetInteger(OBJPROP_BGCOLOR , StringToInteger(m_szValue)); if (m_szLine == "color" ) macro_SetInteger(OBJPROP_COLOR , StringToInteger(m_szValue)); if (m_szLine == "bmpfile_on" ) ObjectSetString(Terminal.Get_ID() , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 0, m_szValue); if (m_szLine == "bmpfile_off" ) ObjectSetString(Terminal.Get_ID() , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 1, m_szValue); if (m_szLine == "fontsz" ) macro_SetInteger(OBJPROP_FONTSIZE , StringToInteger(m_szValue)); if (m_szLine == "fontnm" ) macro_SetString(OBJPROP_FONT , m_szValue); if (m_szLine == "descr" ) macro_SetString(OBJPROP_TEXT , m_szValue); if (m_szLine == "readonly" ) macro_SetInteger(OBJPROP_READONLY , StringToInteger(m_szValue) == 1); if (m_szLine == "state" ) macro_SetInteger(OBJPROP_STATE , StringToInteger(m_szValue) == 1); if (m_szLine == "border_type" ) macro_SetInteger(OBJPROP_BORDER_TYPE , StringToInteger(m_szValue)); } m_CountObject += (b0 ? 0 : (m_CountObject < def_MaxObject ? 1 : 0)); return true; #undef macro_SetString #undef macro_SetInteger }
Каждому объекту будет присвоено имя, храниться он будет в соответствующем месте, но выделенная строка показывает что-то другое. Когда мы создаем IDE, она должна начинаться в верхнем левом углу графика, но эта позиция X не обязательно будет левым верхним углом подокна, эта позиция должна соответствовать верхнему левому углу объекта OBJ_CHART, к которому будет привязана IDE. Этот объект указывается при загрузке шаблона IDE, поэтому он может быть где угодно внутри подокна. Если это не исправить, то IDE не будет отображаться в правильном месте. Ппоэтому мы сохраняем значение X и используем его позже для отображения объекта в правильном месте. Функцию, которая правильно отображает IDE, можно увидеть ниже.
Уже определена основная информация, используемая в объектах, но если необходимо добавить какую-либо другую информацию, просто включите ее в набор команд и измените свойство с соответствующим значением.
void Resize(int x) { for (int c0 = 0; c0 < m_CountObject; c0++) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); };
Прежде чем посмотреть, как обрабатываются сообщения, надо изучать две другие не менее важные функции, поскольку система может получать от советника значения, которые получены во время инициализации. Эти значения должны быть правильно представлены и скорректированы, чтобы в случае использования Chart Trade можно было конфигурировать ордера непосредственно в нём либо для отправки рыночного ордера, либо для отложенного ордера, без необходимости использования советника. Обе функции показаны ниже:
void UpdateInfos(bool bSwap = false) { int nContract, FinanceTake, FinanceStop; nContract = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT)); FinanceTake = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT)); FinanceStop = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT)); m_IsDayTrade = (bSwap ? (m_IsDayTrade ? false : true) : m_IsDayTrade); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade); NanoEA.Initilize(nContract, FinanceTake, FinanceStop, clrNONE, clrNONE, clrNONE, m_IsDayTrade); } //+------------------------------------------------------------------+ void InitilizeChartTrade(int nContracts, int FinanceTake, int FinanceStop, color cp, color ct, color cs, bool b1) { NanoEA.Initilize(nContracts, FinanceTake, FinanceStop, cp, ct, cs, b1); if (m_CountObject < eEDIT_STOP) return; ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT, IntegerToString(nContracts)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT, IntegerToString(FinanceTake)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT, IntegerToString(FinanceStop)); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade = b1); }
Обратите внимание, что IDE привязана к системе ордеров, поэтому изменения, внесенные в систему, будут отражаться в системе ордеров, таким образом нам не нужно будет менять данные в советнике, как это делалось раньше. Теперь мы можем это сделать непосредственно в IDE или в нашей Chart Trade, и это делается с помощью этих двух предыдущих функций, связанных с системой сообщений, которую мы сейчас увидим.
void DispatchMessage(int iMsg, string szArg, double dValue = 0.0) { if (m_CountObject < eEDIT_STOP) return; switch (iMsg) { case CHARTEVENT_CHART_CHANGE: if (szArg == szMsgIDE[eRESULT]) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2)); } break; case CHARTEVENT_OBJECT_CLICK: if (StringSubstr(szArg, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG) return; szArg = StringSubstr(szArg, 9, StringLen(szArg)); StringToUpper(szArg); if ((szArg == szMsgIDE[eBTN_SELL]) || (szArg == szMsgIDE[eBTN_BUY])) NanoEA.OrderMarket(szArg == szMsgIDE[eBTN_BUY]); if (szArg == szMsgIDE[eBTN_CANCEL]) { NanoEA.ClosePosition(); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false); } if (szArg == szMsgIDE[eCHECK_DAYTRADE]) UpdateInfos(true); break; case CHARTEVENT_OBJECT_ENDEDIT: UpdateInfos(); break; } }
И возникает вопрос: Неужели это все? Да, это система обмена сообщениями, которая позволяет платформе MetaTrader 5 взаимодействовать с IDE. Она очень проста, должен признаться, но без этой функции IDE не работала бы, и нельзя было бы построить систему. Может показаться немного сложным то, как заставить это работать в советнике, но это не так — благодаря ООП-программированию код советника останется сверхпростым. То, что будет немного сложнее — это обновление значения результата, который появится в среде IDE. Значения обновляются в функции OnTick, но для упрощения я использовал данные, предоставленные в MetaTrader 5, поэтому функция выглядит так. Эта часть является наиболее важная, поскольку данная функция является наиболее востребованной из всех, поэтому она также должна быть самой быстрой.
void OnTick() { SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT], NanoEA.CheckPosition()); }
Иными словами, при каждой новой котировке классу отправляется сообщение и полученное значение обновляется в операции. Напомню, что эта функция должна быть хорошо оптимизирована, иначе мы можем иметь серьезные проблемы.
Заключение
Иногда выполнение каких-то вещей кажется невозможным, но мне нравятся вызовы. Поэтому данная презентация того, как сделать систему RAD на платформе, которая изначально не была разработана для этого, была довольно интересным опытом. Я надеюсь, что эта система, начавшаяся с чего-то простого и незатейливого, сможет мотивировать вас попытаться пойти туда, куда идут немногие.
Скоро я добавлю в этот советник что-то новое, а пока можете сами позаниматься этой интресной работой...!
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/10277
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования