Español Português
preview
Разработка системы репликации (Parte 46): Проект Chart Trade (V)

Разработка системы репликации (Parte 46): Проект Chart Trade (V)

MetaTrader 5Примеры | 9 сентября 2024, 10:23
42 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 45): Проект Chart Trad (IV) я показал, как запустить функционал индикатора Chart Trade. Но вы, возможно, заметили, что в приложении к той статье было много файлов для переноса. Риск допустить какую-нибудь ошибку, например, забыть добавить файл или случайно что-то важное удалить, делает этот метод не очень привлекательным.

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

Метод, который будет здесь представлен, может оказаться очень полезным, так как в качестве отличного помощника вы сможете использовать сам MetaTrader 5, а также MQL5. И это не так уж трудно и сложно для понимания. Но понимание и умение использовать то, что я объясню в этой статье, может помочь вам избежать проблем с правильным сохранением исполняемых файлов и всего того, что необходимо для их корректной работы.

 

Понимание идеи

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

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

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

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

#define def_IDE_RAD  "Files\\Chart Trade\\IDE_RAD.tpl"
#resource "\\" + def_IDE_RAD;

Если вы попытаетесь скомпилировать исполняемый файл, содержащий приведенный выше код, вы увидите, что компилятор сгенерирует ошибку, поскольку определение ссылается на шаблон. Как правило, это запрещено для исполняемого файла, который будет использоваться на платформе MetaTrader 5.

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

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

На мой взгляд, это довольно утомительно. Многие используют одно и то же устройство для разработки и использования MetaTrader 5. Я хотел бы сказать этим людям, что это большая ошибка, мягко говоря. НИКОГДА, просто НИКОГДА и ни в коем случае не следует использовать одну и ту же машину или установку MetaTrader 5 для торговли и разработки. Дело в том, что поступая таким образом, можно в конечном итоге спровоцировать какой-то сбой или уязвимость в приложении, которые могут повлиять на корректную работу автоматического советника, если вы его используете.

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

Вот почему крайне важно использовать две установки для использования MetaTrader 5: одну для разработки и другую  исключительно для работы на рынке. Это если вы разработчик. В любом другом случае в этом не возникнет необходимости.

Я много думал о том, стоит ли мне показывать, как делать некоторые вещи. У большинства людей не получается разобраться в основах MQL5 или в том, как работает MetaTrader 5, а представьте, что будет, если они попытаются понять то, что я собираюсь показать. Некоторые подумают, что я сумасшедший или хвастун. Но на самом деле вы можете сравнить приложение к этой статье с приложением к предыдущей, как по функциональности, так и по переносимости. Касательно функциональности  оба идентичны. Что же касается портативности, я думаю, что этот вариант гораздо лучше. Вам всего лишь нужно отправить один файл, и не стоит беспокоиться ни о каких структурах каталогов.

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

Итак, давайте посмотрим, как это было реализовано, и как вы можете использовать это в своих собственных кодах.


Ресурсы, ресурсы и еще раз ресурсы

Чтобы использовать эту модель, нам потребуется внести некоторые изменения в код. Хорошо, но несмотря на это, исходный код индикатора остался неизменным, поэтому здесь, в статье, его не будет. Код класса C_ChartFloatingRAD претерпел некоторые небольшие изменения, но поскольку они не затронули весь класс, я сосредоточусь только на том фрагменте, где действительно было произведено изменение.

Ниже вы можете увидеть этот фрагмент с последующим его пояснением.

068. inline void AdjustTemplate(const bool bFirst = false)
069.                    {
070. #define macro_AddAdjust(A) {                   \
071.            (*Template).Add(A, "size_x", NULL); \
072.            (*Template).Add(A, "size_y", NULL); \
073.            (*Template).Add(A, "pos_x", NULL);  \
074.            (*Template).Add(A, "pos_y", NULL);  \
075.                            }
076. #define macro_GetAdjust(A) {                                                                        \
077.            m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x"));  \
078.            m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y"));  \
079.            m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \
080.            m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \
081.                            }
082. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade
083.                            
084.                            C_AdjustTemplate *Template;
085.                            
086.                            if (bFirst)
087.                            {
088.                                    Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true);
089.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0));
090.                                    AdjustEditabled(Template, true);
091.                            }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
092.                            m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage);
093.                            m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage));
094.                            m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage));
095.                            (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
096.                            (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage));
097.                            (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2));
098.                            (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2));
099.                            (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
100.                            (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
101.                            (*Template).Execute();
102.                            if (bFirst)
103.                            {
104.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0);
105.                                    m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x;
106.                                    AdjustEditabled(Template, false);
107.                            };
108.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6));
109.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx));
110.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny));
111. 
112.                            delete Template;
113.                            
114.                            ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
115.                            ChartRedraw(m_Info.WinHandle);
116. 
117. #undef macro_PointsToFinance
118. #undef macro_GetAdjust
119. #undef macro_AddAdjust
120.                    }

Fragmento del código fuente de la clase C_ChartFloatingRAD

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

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

Однако это изменение вскоре поднимет очередной вопрос. Как мы будем использовать шаблон в качестве ресурса, если на самом деле мы не можем скомпилировать шаблон как часть исполняемого файла, то есть включить его как ресурс? На самом деле, мы можем включить в исполняемый файл что угодно. Главное, как именно нам следует это сделать.

Чтобы разобраться в этом вопросе, необходимо посмотреть код класса C_AdjustTemplate, чтобы понять, почему изменение было внесено в строку 88 класса C_ChartFloatingRAD. Что ж, код класса C_AdjustTemplate можно увидеть полностью чуть ниже. Поскольку в нем было сделано больше изменений, хотя и не таких значительных, будет интересно понять, что на самом деле произошло.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "../Auxiliar/C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_PATH_BTN "Images\\Market Replay\\Chart Trade"
007. #define def_BTN_BUY  def_PATH_BTN + "\\BUY.bmp"
008. #define def_BTN_SELL def_PATH_BTN + "\\SELL.bmp"
009. #define def_BTN_DT   def_PATH_BTN + "\\DT.bmp"
010. #define def_BTN_SW   def_PATH_BTN + "\\SW.bmp"
011. #define def_BTN_MAX  def_PATH_BTN + "\\MAX.bmp"
012. #define def_BTN_MIN  def_PATH_BTN + "\\MIN.bmp"
013. #define def_IDE_RAD  "Files\\Chart Trade\\IDE_RAD.tpl"
014. //+------------------------------------------------------------------+
015. #resource "\\" + def_BTN_BUY
016. #resource "\\" + def_BTN_SELL
017. #resource "\\" + def_BTN_DT
018. #resource "\\" + def_BTN_SW
019. #resource "\\" + def_BTN_MAX
020. #resource "\\" + def_BTN_MIN
021. #resource "\\" + def_IDE_RAD as string IdeRad;
022. //+------------------------------------------------------------------+
023. class C_AdjustTemplate
024. {
025.    private :
026.            string m_szName[],
027.                   m_szFind[],
028.                   m_szReplace[],
029.                   m_szFileName;
030.            int    m_maxIndex,
031.                   m_FileIn,
032.                   m_FileOut;
033.            bool   m_bFirst;
034. //+------------------------------------------------------------------+
035.    public  :
036. //+------------------------------------------------------------------+
037.            C_AdjustTemplate(const string szFile, const bool bFirst = false)
038.                    :m_maxIndex(0),
039.                     m_szFileName(szFile),
040.                     m_bFirst(bFirst),
041.                     m_FileIn(INVALID_HANDLE),
042.                     m_FileOut(INVALID_HANDLE)
043.                    {
044.                            ResetLastError();                               
045.                            if (m_bFirst)
046.                            {
047.                                    int handle = FileOpen(m_szFileName, FILE_TXT | FILE_WRITE);
048.                                    FileWriteString(handle, IdeRad);
049.                                    FileClose(handle);
050.                            }
051.                            if ((m_FileIn = FileOpen(m_szFileName, FILE_TXT | FILE_READ)) == INVALID_HANDLE)        SetUserError(C_Terminal::ERR_FileAcess);
052.                            if ((m_FileOut = FileOpen(m_szFileName + "_T", FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess);
053.                    }
054. //+------------------------------------------------------------------+
055.            ~C_AdjustTemplate()
056.                    {
057.                            FileClose(m_FileIn);
058.                            FileClose(m_FileOut);
059.                            FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE);
060.                            ArrayResize(m_szName, 0);
061.                            ArrayResize(m_szFind, 0);
062.                            ArrayResize(m_szReplace, 0);
063.                    }
064. //+------------------------------------------------------------------+
065.            void Add(const string szName, const string szFind, const string szReplace)
066.                    {
067.                            m_maxIndex++;
068.                            ArrayResize(m_szName, m_maxIndex);
069.                            ArrayResize(m_szFind, m_maxIndex);
070.                            ArrayResize(m_szReplace, m_maxIndex);
071.                            m_szName[m_maxIndex - 1] = szName;
072.                            m_szFind[m_maxIndex - 1] = szFind;
073.                            m_szReplace[m_maxIndex - 1] = szReplace;
074.                    }
075. //+------------------------------------------------------------------+
076.            string Get(const string szName, const string szFind)
077.                    {
078.                            for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0];
079.                            
080.                            return NULL;
081.                    }
082. //+------------------------------------------------------------------+
083.            void Execute(void)
084.                    {
085.                            string sz0, tmp, res[];
086.                            int count0 = 0, i0;
087.                                                            
088.                            if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS))
089.                            {
090.                                    sz0 = FileReadString(m_FileIn);
091.                                    if (sz0 == "<object>") count0 = 1;
092.                                    if (sz0 == "</object>") count0 = 0;
093.                                    if (count0 > 0) if (StringSplit(sz0, '=', res) > 1)
094.                                    {
095.                                            if ((m_bFirst) && ((res[0] == "bmpfile_on") || (res[0] == "bmpfile_off")))
096.                                                    sz0 = res[0] + "=\\Indicators\\Replay\\Chart Trade.ex5::" + def_PATH_BTN + res[1];
097.                                            i0 = (count0 == 1 ? 0 : i0);
098.                                            for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0);
099.                                            for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0]))
100.                                            {
101.                                                    if (StringLen(m_szReplace[c0])) sz0 =  m_szFind[c0] + "=" + m_szReplace[c0];
102.                                                    else m_szReplace[c0] = res[1];
103.                                            }
104.                                    }
105.                                    FileWriteString(m_FileOut, sz0 + "\r\n");
106.                            };
107.                    }
108. //+------------------------------------------------------------------+
109. };
110. //+------------------------------------------------------------------+

Исходный код класса C_AdjustTemplate

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

Посмотрите внимательно на определения между строками 6 и 13. Заметьте, что в основном здесь все нам уже давно знакомо. Посмотрите внимательнее на строку 13. Это шаблон. Тот же шаблон, который использовался ранее. Но теперь мы сделаем так, что он перестанет быть отдельным файлом и станет частью исполняемого файла. Способ достижения этой цели представлен ниже.

Обратите внимание, что между строками 15 и 20 мы делаем определения, как обычно. Это делается, когда мы хотим добавить изображения или звуки. Но в строке 21 у нас нечто иное  что-то действительно редко встречающееся в кодах. Это связано с тем, что мы обычно не используем алиасы или псевдонимы в программировании на MQL5, когда собираемся включать ресурсы в исполняемые файлы. Получить представление о том, что будет происходить, можно, заглянув в документацию РЕСУРСЫ. Но кроме этого, нужно еще разобраться и в некоторых других деталях. Только таким образом вы сможете все понять.

Псевдонимы или алиасы очень распространены в некоторых типах языков. Например, в Visual Basic или VBA (Visual Basic для приложений), который часто используется в Excel, эти алиасы служат для доступа к вещам несколько иным способом. Обычно, когда мы обращаемся к ресурсу, мы используем «::», который часто используется в качестве разрешителя области. Но когда мы используем его для доступа к ресурсу, мы обычно оперируем именем определения ресурса. Это кажется сложным, но на самом деле все гораздо проще. Чтобы понять это, посмотрите на следующий фрагмент:

01. #define def_BTN_BUY  "Images\\Market Replay\\Chart Trade\\BUY.bmp"
02. #define def_BTN_SELL "Images\\Market Replay\\Chart Trade\\SELL.bmp"
03. #resource "\\" + def_BTN_BUY
04. #resource "\\" + def_BTN_SELL
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     long id;
09.     string sz;
10.     
11.     ObjectCreate(id = ChartID(), sz = IntegerToString(ObjectsTotal(id)), OBJ_BITMAP_LABEL, 0, 0, 0);
12.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "::" + def_BTN_BUY);
13.     ResourceSave("::" + def_BTN_SELL, "BTN_SELL.bmp");
14.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 1, "\\Files\\BTN_SELL.bmp");
15.     
16.     return INIT_SUCCEEDED;
17. }
18. //+------------------------------------------------------------------+

Фрагмент 01 – Пример использования

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

В строках 1 и 2 мы определяем две строки символов. Содержимое, указанное этими строками, будет скомпилировано и встроено в исполняемый файл компилятором, благодаря строкам 3 и 4. До этого момента у нас ничего грандиозного или сложного, мы работаем обычным образом.

В строке 11 мы указываем, что собираемся создать объект. В данном случае Bitmap. Опять же, ничего особенного. Но теперь наступает первая фаза, когда мы будем использовать ресурсы, встроенные в исполняемый файл. Посмотрите на строку 12, здесь мы используем «::» для указания того, что собираемся использовать ресурс. В данном случае ресурс, присутствующий в исполняемом файле. Мы могли бы обратиться к другой программе, но, чтобы не усложнять, давайте сначала разберемся с этой более простой концепцией. Прочитав строку 12, компилятор поймет следующее:

12.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "::Images\\Market Replay\\Chart Trade\\BUY.bmp");

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

Итак, эту строку 12 было понять легко, но как насчет следующих строк? А теперь начинаются различия. Обычно вы не увидите таких вещей в кодах, но важно их понимать, чтобы по-настоящему разобраться в индикаторе Chart Trade.

Строка 13 возьмет указанный ресурс и сохранит его под указанным именем в указанном месте. Таким образом, для компилятора строка 13 будет выглядеть примерно так:

13.     ResourceSave("::Images\\Market Replay\\Chart Trade\\SELL.bmp", "\\Files\\BTN_SELL.bmp");

Еще раз, я использую здесь самый базовый концепт. На этот раз дело немного сложнее. Но можно кое-что заметить в вызове ResourceSave   ресурс, присутствующий в исполняемом файле, будет сохранен как обычный файл. По сути, это будет эквивалентно использованию функции FileMove. То есть понимайте строку 13 так, как если бы она была чем-то вроде:

FileMove("::Images\\Market Replay\\Chart Trade\\SELL.bmp", 0, "\\Files\\BTN_SELL.bmp", FILE_REWRITE);

Вот как все будет работать на практике.

Итак, в строке 14 вместо доступа к внутреннему ресурсу исполняемого файла мы будем косвенно использовать этот ресурс. Это потому, что мы сохранили его в файл в строке 13 и теперь указываем на него в строке 14. Но обратите внимание вот на что. В отличие от строки 12, в строке 14 мы действительно используем физический файл, присутствующий на диске. Он будет оставаться там до тех пор, пока не будет удален или изменен.

Использование такого подхода, как в строках 13 и 14 не очень распространено в программах MQL5. Обычно мы запускаем ресурс в исполняемом файле и используем его непосредственно в нем. Вот как мы обычно делаем. Но, как я уже говорил, ситуация немного сложнее. Но и в то же время интересней. На самом деле вам не нужно помещать ресурсы в каждый из исполняемых файлов. Более того, это значительно усложнило бы стандартизацию. Можно воспользоваться очень распространенным в программировании приемом  использовать файл для ресурсов. Обычно, в случае Windows, он разместился бы в DLL. Таким образом мы можем немного стандартизировать вещи.

В MQL5 можно сделать нечто подобное. Но имейте в виду, что некоторый тип кода необходимо стандартизировать. В противном случае вы сгенерируете чудовищное количество бесполезных данных.

И как это можно сделать в MQL5? Просто. Достаточно указать имя файла в тот момент, когда вы собираетесь использовать ресурс. Помните, я уже говорил, что все обстоит гораздо сложнее? Именно это и происходило. Там игнорировалось имя исполняемого файла. Итак, представим, что у вас есть исполняемый файл под названием ICONS.LIB, в нем находятся те же изображения из фрагмента 01, и что этот исполняемый файл ICONS.LIB находится в корне папки «Индикаторы». Чтобы сделать то же самое, но теперь с использованием ICONS.LIB, фрагмент 01 должен выглядеть так:

01. //+------------------------------------------------------------------+
02. #define def_BTN_BUY  "Images\\Market Replay\\Chart Trade\\BUY.bmp"
03. #define def_BTN_SELL "Images\\Market Replay\\Chart Trade\\SELL.bmp"
04. #define def_LIB      "\\Indicators\\Icons.Lib"
05. //+------------------------------------------------------------------+
06. int OnInit()
07. {
08.     long id;
09.     string sz;
10.     
11.     ObjectCreate(id = ChartID(), sz = IntegerToString(ObjectsTotal(id)), OBJ_BITMAP_LABEL, 0, 0, 0);
12.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, def_LIB + "::" + def_BTN_BUY);
13.     ResourceSave(def_LIB + "::" + def_BTN_SELL, "BTN_SELL.bmp");
14.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 1, "\\Files\\BTN_SELL.bmp");
15.             
16.     return INIT_SUCCEEDED;
17. }
18. //+------------------------------------------------------------------+

Фрагмент 02

Обратите внимание, что теперь мы определяем исполняемый файл. Поэтому все пояснения, данные выше, применимы к этому фрагменту 02. Конечно, вам также будет необходимо понять, как компилятор видит код. Для этого можете посмотреть переведенный код ниже.

01. //+------------------------------------------------------------------+
02. int OnInit()
03. {
04.     long id;
05.     string sz;
06.     
07.     ObjectCreate(id = ChartID(), sz = IntegerToString(ObjectsTotal(id)), OBJ_BITMAP_LABEL, 0, 0, 0);
08.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "\\Indicators\\Icons.Lib::Images\\Market Replay\\Chart Trade\\BUY.bmp");
09.     ResourceSave("\\Indicators\\Icons.Lib::Images\\Market Replay\\Chart Trade\\SELL.bmp", "\\Files\\BTN_SELL.bmp");
10.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 1, "\\Files\\BTN_SELL.bmp");
11.             
12.     return INIT_SUCCEEDED;
13. }
14. //+------------------------------------------------------------------+

Перевод фрагмента 02 (Расширенный код)

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

Ну что ж, теперь, когда я объяснил эту более простую часть, давайте вернемся к вопросу об алиасах. Когда вы используете алиасы здесь, в MQL5, вы «заставляете» компилятор действовать немного иначе. Произойдет то, что он будет использовать систему сжатия данных для уменьшения размера файлов. В то же время он будет игнорировать любой прямой доступ к ресурсу. То есть вы НЕ сможете напрямую использовать определение. Очень важно, чтобы вы это поняли. Вы можете добавить что угодно в исполняемый файл, но НЕ сможете напрямую использовать то, что было добавлено.

Фактически, чтобы использовать то, что было добавлено в исполняемый файл через алиас, нужно будет использовать этот алиас, а не ресурс. Вас это смущает? Спокойно, давайте попробуем разобраться.

01. #resource "\\Images\\euro.bmp" as bitmap euro[][] 
02. #resource "\\Images\\dollar.bmp" 
03. //+------------------------------------------------------------------+ 
04. void Image(string name,string rc,int x,int y) 
05. { 
06.     ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0); 
07.     ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); 
08.     ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); 
09.     ObjectSetString(0, name, OBJPROP_BMPFILE, rc); 
10. } 
11. //+------------------------------------------------------------------+ 
12. void OnStart() 
13. { 
14.     for(int x = 0; x < ArrayRange(euro, 1); x++) 
15.             euro[ArrayRange(euro, 1) / 2][x] = 0xFFFF0000; 
16.     ResourceCreate("euro_icon", euro, ArrayRange(euro, 1), ArrayRange(euro, 0), 0, 0, ArrayRange(euro, 1), COLOR_FORMAT_ARGB_NORMALIZE); 
17.     Image("Euro" , "::euro_icon", 10, 40); 
18.     Image("USD", "::Images\\dollar.bmp", 15 + ArrayRange(euro,1), 40); 
19.     Image("E2", "::Images\\euro.bmp", 20 + ArrayRange(euro, 1) * 2, 40);
20. }
21. //+------------------------------------------------------------------+ 

Исходный код, приведенный в документации

Приведенный выше исходный код именно это и демонстрирует,  один из способов использования ресурсов, когда мы применяем алиасы. Алиас был определен в строке 1. С этого момента нам больше не следует использовать имя ресурса, мы должны использовать его алиас. Поэтому при выполнении строки 19 у нас возникнет ошибка, поскольку мы пытаемся использовать имя ресурса, тогда как на самом деле нам следует использовать его алиас.

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

В этом коде документации мы используем систему для демонстрации примера использования алиасов. Однако в этом случае нет необходимости сохранять имеющиеся данные как ресурсы исполняемого файла. Причина в том, что это данные, которые мы можем использовать напрямую. Но что, если ресурс является шаблоном? Согласно правилам использования, MetaTrader 5 не позволяет использовать шаблоны в качестве ресурсов. Поэтому необходимо извлечь шаблон из исполняемого файла, чтобы он перестал быть ресурсом и стал обычным файлом.

Этого можно добиться таким же образом, как показано в коде во фрагменте 1. В строке 13 мы преобразуем изображение, включенное в исполняемый файл в качестве ресурса, в файл, чтобы можно было его использовать в объекте в строке 14.

Ну, в теории это работает, но на практике все немного сложнее. Если вы заметили, в строке 13 кода фрагмента 01 мы используем имя ресурса, а не алиас. Способ работы с алиасами несколько отличается от работы с ресурсами, но основная наша проблема не в этом. Наша главная проблема заключается в том, что мы не можем включать шаблоны в качестве ресурсов в исполняемые файлы. Поэтому сосредоточим свое внимание на коде класса C_AdjustTemplate. Это для того, чтобы понять, как мне удалось преодолеть эти две проблемы: невозможность включать шаблоны в исполняемые файлы и возможность использования шаблона, хранящегося в исполняемом файле.

Итак, в строке 13 класса C_AdjustTemplate я определяю шаблон, который собираюсь использовать. Это тот же самый шаблон, который появлялся в предыдущих статьях о Chart Trade. Но обратите внимание: в строке 21, где я преобразую определение в ресурс, я не преобразую его для использования ресурса. Тем не менее, я делаю это с использованием алиаса. Этот алиас основан на типе данных string, поэтому весь код, присутствующий в файле шаблона, добавляется к исполняемому файлу, как если бы это была длинная строка символов. Однако эта строка является как бы большой константой. Очень важно это хорошо понимать. Иными словами, чтобы было еще понятней: файл шаблона будет воспринят системой как константа, хотя на самом деле это не так.

Предполагается, что файл шаблона представляет собой текстовую строку с алиасом IdeRad внутри исполняемого файла. Можем начинать думать, как с ним работать.

Первое, что нужно понять, что мы не можем использовать этот шаблон с алиасом IdeRad напрямую в объекте OBJ_CHART. Это невозможно. Нам нужно снова преобразовать эти данные в файл. Однако функция ResourceSave не может справиться с таким случаем, потому что шаблон – ЭТО НЕ РЕСУРС. Но мы можем сделать кое-что другое. Таким образом появляется код, присутствующий между строками 45 и 50.

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

Итак, давайте разберемся, что происходит между строками 45 и 50. При вызове конструктора для управления шаблоном, мы указываем уровень вызова, а также имя файла, к которому осуществляется доступ. Таким образом, если это первый вызов, строка 45 позволит создать файл шаблона. Файл будет создан в строке 47 и закрыт в строке 49. Если бы существовали только эти две строки, шаблон был бы пустым. Но в строке 48 происходит магия.

Здесь мы помещаем все содержимое строки внутрь файла. И что же это за строка? Та, что находится в переменной IdeRad. Упс, погодите минутку. Хотите сказать, что мы храним шаблон внутри исполняемого файла как ресурс? Чтобы избежать проблем, мы присваиваем ему алиас. Затем, когда мы хотим восстановить его содержимое, мы берем содержимое этого алиаса и помещаем его в файл. Это так? Да, именно так. Теперь вы, возможно, задаетесь вопросом: почему никто раньше не додумался сделать это или показать, как это делается? Ну, я не знаю. Может быть потому, что никто на самом деле не пробовал. Или просто никто не мог представить, как это сделать.

После закрытия файла в строке 49, весь остальной процесс выполняется также, как было объяснено раньше. Это связано с тем, что теперь MetaTrader 5 придется иметь дело не с ресурсом, а с файлом на диске.

И в заключение, есть небольшая деталь, которую я не могу проигнорировать. Многие могут себе что-то представлять или пытаться манипулировать данными, чтобы понять, что происходит при взаимодействии с индикатором Chart Trade. Вопрос в кнопках. Если вы запустите индикатор на графике, который будет присутствовать в приложении, вы не поймете, как получить доступ к кнопкам. Это произойдет только в том случае, если у вас мало опыта в MQL5. Вы нигде не найдете изображений, которые были заявлены на кнопках. Если вы посмотрите на содержимое шаблона, то увидите что-то вроде:

bmpfile_on=\Indicators\Replay\Chart Trade.ex5::Images\Market Replay\Chart Trade\BUY.bmp

bmpfile_off=\Indicators\Replay\Chart Trade.ex5::Images\Market Replay\Chart Trade\BUY.bmp

И это не имеет никакого смысла. Никакого, если вы только начинаете изучать MQL5. Но если вы воспроизведете этот же шаблон на графике, вы заметите, что он отображается правильно. Как это возможно? Где находятся изображения, на которые делается ссылка? Вот в чем вопрос. Изображения находятся внутри исполняемого файла. То есть в шаблоне напрямую указывается изображение, которое должно быть использовано. Это то же самое, что было видно в ранее представленном коде и в переводе фрагмента 02. Код, о котором идет речь, — это именно строка 08, которую я еще раз показываю ниже, чтобы облегчить вам понимание:

08.     ObjectSetString(id, sz, OBJPROP_BMPFILE, 0, "\\Indicators\\Icons.Lib::Images\\Market Replay\\Chart Trade\\BUY.bmp");

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


Заключение

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

Все зависит от креативности, способностей и личности программиста. Есть люди, которые говорят, что что-то сделать невозможно. Другие говорят, что нет средств. Но есть люди, которые пытаются и достигают успеха. Не будьте одним из тех, кто сдается, когда видит вызов. Будьте тем, кто решает проблемы. Мой девиз:

Настоящий профессиональный программист решает проблему, в то время как другие ее только видят.


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

Прикрепленные файлы |
Indicators.zip (149.64 KB)
Создаем и оптимизируем торговую систему на основе волатильности с индикатором Чайкина Создаем и оптимизируем торговую систему на основе волатильности с индикатором Чайкина
В этой статье мы поговорим об индикаторе волатильности Чайкина (Chaikin Volatility, CHV). Разберемся, что делает этот индикатор, как и в каких условиях его можно использовать и как создать пользовательский индикатор волатильности. Проанализируем несколько простых стратегий и протестируем их, чтобы понять, какая стратегия лучше.
Оптимизация бактериальным хемотаксисом — Bacterial Chemotaxis Optimization (BCO) Оптимизация бактериальным хемотаксисом — Bacterial Chemotaxis Optimization (BCO)
В статье представлена оригинальная версия алгоритма бактериальной хемотаксисной оптимизации (BCO) и его модифицированный вариант. Мы подробно рассмотрим все отличия, уделяя особое внимание новой версии BCOm, которая упрощает механизм движения бактерий, снижает зависимость от истории изменений позиций и использует более простые математические операции по сравнению с перегруженной вычислениями оригинальной версией. Также будут проведены тесты и подведены итоги.
Нейросети в трейдинге: Transformer для облака точек (Pointformer) Нейросети в трейдинге: Transformer для облака точек (Pointformer)
В данной статье мы поговорим об алгоритмах использования методов внимания при решении задач обнаружения объектов в облаке точек. Обнаружение объектов в облаках точек имеет важное значение для многих реальных приложений.
Нейросети в трейдинге: Иерархическое обучение признаков облака точек Нейросети в трейдинге: Иерархическое обучение признаков облака точек
Продолжаем изучение алгоритмов для извлечения признаков из облака точек. И в данной статье мы познакомимся с механизмами повышения эффективности метода PointNet.