English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Разработка торгового советника с нуля (Часть 16): Доступ к данным в Интернете (II)

Разработка торгового советника с нуля (Часть 16): Доступ к данным в Интернете (II)

MetaTrader 5Интеграция | 7 июля 2022, 17:14
836 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье "Разработка торгового советника с нуля (Часть 15): Доступ к данным в Интернете (I)" мы представили всю логику и идеи, лежащие в основе методов использования платформы MetaTrader 5 для доступа к рыночным данным в Интернете на специализированных сайтах, предоставляющих информацию о рынке.

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


Планирование и реализация

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

Так что давайте приготовимся и приступим к работе.


1. Доступ к интернет-данным через советника

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

Эта логика представлена на рисунке ниже:

Давайте посмотрим, как эксперт взаимодействует непосредственно с веб-сервером, который содержит информацию, которую мы хотим захватить. Ниже можно посмотреть полный пример кода, который работает именно так.

#property copyright "Daniel Jose"
#property version "1.00"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

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


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

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

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


2. Создание канала связи

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

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

На рисунке ниже показано, как продвигать такой канал.

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

#property copyright "Daniel Jose"
#property description "Testing Inner Channel"
#property version "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetInfoInnerChannel());
}
//+------------------------------------------------------------------+

Следующий код представляет собой заголовок, который мы должны использовать. Обратите внимание, что объект здесь объявляется для совместного использования советником и скриптом.

//+------------------------------------------------------------------+
//|                                          Canal Intra Process.mqh |
//|                                                      Daniel Jose |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_NameObjectChannel   "Inner Channel Info WEB"
//+------------------------------------------------------------------+
void CreateInnerChannel(void)
{
        long id;
        
        ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE);
}
//+------------------------------------------------------------------+
void RemoveInnerChannel(void)
{
        ObjectDelete(ChartID(), def_NameObjectChannel);
}
//+------------------------------------------------------------------+
inline void SetInfoInnerChannel(string szArg)
{
        ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg);
}
//+------------------------------------------------------------------+
inline string GetInfoInnerChannel(void)
{
        return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT);
}
//+------------------------------------------------------------------+

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

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        CreateInnerChannel();
        while (!IsStopped())
        {
                SetInfoInnerChannel(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
                Sleep(200);
        }
        RemoveInnerChannel();
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++);
        for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return (_StopFlag ? "" : szInfo);
}
//+------------------------------------------------------------------+

Рассмотрим следующее: есть объект, который видит советник и создается скриптом. Суть в том, что советник и скрипт могут сосуществовать в одном графике, тогда этот объект будет нашим каналом связи между советником и скриптом. Советник будет клиентом, скрипт - сервером, а объект - каналом связи между ними. Таким образом, скрипт будет перехватывать значения на удаленном веб-сервере и помещать нужное значение в объект. Советник время от времени будет видеть, какое значение находится в объекте (если объект существует), потому что если скрипт не запущен, объект не должен быть доступен, однако время, когда советник смотрит на значение в объекте, никак не нарушает выполнение скрипта. Если скрипт заблокируется из-за ожидания ответа от удаленного сервера, то для советника это несущественно, он будет продолжать свою работу независимо от того, что делает скрипт.

Хотя это и прекрасное решение, но оно не является идеальным, в нем есть проблема, и проблема заключается в скрипте.

Чтобы понять это, давайте посмотрим следующее видео, обращая внимание на каждую его деталь.


Всё работает отлично, и этого следовало ожидать, так как этот вид решения широко применяется в программировании при разработке программы типа клиент-сервер, где мы не хотим, чтобы один блокировал другого. Другими словами, мы используем канал для связи между процессами, часто в одной среде этот канал будет создан с использованием памяти, создавая изолированную область, но общую и видимую как для клиента, так и для сервера. Сервер помещает там данные, а клиент отправляется в ту же область и собирает имеющиеся данные, то есть одно не зависит от другого, но оба будут связаны.

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

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

Однако, у нас есть способ создать гораздо лучшее решение (по крайней мере, в некоторых случаях), а если быть честными, то это практически идеальное решение, и мы будем использовать ту же концепцию, представленную выше, только вместо скрипта мы будем использовать сервис.


3. Создание сервиса

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

Программирование - такая вещь, что при попытке решить одну проблему, мы порождаем новую.

Здесь, цель состоит в том, чтобы создать нечто подобное изображению ниже:

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


3.1. Доступ к глобальным переменным

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

Так что эта идея почти не оправдала себя, и мы решили использовать dll, но все-таки хотелось изучить MetaTrader 5 и MQL5, поэтому, заглянув в терминал, мы нашли то, что вы можете видеть на изображении ниже:

         

Это то, что мы искали, здесь мы добавили переменную, чтобы иметь возможность проверить, как можно настроить эту вещь, и мы можем использовать только значения double. Вы могли бы подумать, что это проблема (хотя это так и есть в некоторых случаях, которые нас ограничивают), но это будет более чем достаточно, если мы хотим передавать короткие сообщения, что является нашим случаем. Нужно осознавать, что тип double на самом деле является короткой строкой из 8 символов, а этого достаточно для передачи значений или коротких сообщений между программами.

Итак, первая часть проблемы решена. MetaTrader 5 предоставляет нам средства для создания канала без необходимости создания dll только для этого, но теперь у нас есть другая проблема: как получить доступ к этим переменным через программу? Можно ли создать их внутри программы, будь то советник, скрипт, индикатор или сервис? Или мы обязаны использовать только те, которые объявлены в терминале?

Эти вопросы очень важны для того, чтобы мы могли действительно использовать ресурс. Если невозможно использовать его через программы, нам придется использовать dlls, но это возможно сделать через программы. Подробнее см. в следующей теме: Глобальные переменные терминала.


3.2. Использование терминальной переменной для обмена информацией

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

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+

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

Ниже показываем код, который представляет собой запускаемый сервис.

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        double count = 0;
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                GlobalVariableSet(def_GlobalNameChannel, count);
                count += 1.0;
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

Его работа очень проста, она проверяет, определена ли уже переменная, чем и занимается функция GlobalVariableCheck, если переменная еще не существует, она будет временно создана функцией GlobalVariableTemp и затем получит значение от функции GlobalVariableSet. Другими словами, мы тестируем, создаем и регистрируем информацию, сервис работает как сервер, точно так же, как и скрипт, только мы пока не обращаемся к веб-сайту. Давайте не будем спешить, нам нужно понять, как работает система.

Следующим шагом является создание клиентской программы, которой в данном случае будет советником, как мы показываем ниже:

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        double value;
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
                GlobalVariableGet(def_GlobalNameChannel, value);
                Print(value);           
        }
}
//+------------------------------------------------------------------+

Здесь код снова очень прост, примерно каждую секунду советник будет проверять, существует ли переменная. Если существует, то он будет читать значение с помощью GlobalVariableGet и затем выводить это значение на терминал.

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

Но возможен еще один сценарий, когда сервис остановился, и мы перезапускаем его, в этом случае мы будем действовать следующим образом:

После этого мы проверим переменные терминала и получим следующий результат:

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

Вот и всё, система работает так, как мы хотим. Возможно, вы не знаете об этом, но у нас есть модель, которая показана ниже. Она представляет собой типичный формат клиент-сервер, и это именно то, что мы хотим сделать. Мы ищем способ реализовать это, так как у нас есть преимущества, которые мы упомянули ранее относительно этой модели.

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

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        string szRet;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                szRet = GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D);
                GlobalVariableSet(def_GlobalNameChannel, StringToDouble(szRet));
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

Теперь у нас есть система, которая работает, как показано на рисунке ниже:

То есть, она почти завершена, и при ее запуске мы получаем следующие результаты, но теперь смена таймфрема уже не будет проблемой.



Заключение

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


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

Прикрепленные файлы |
Script_e_EA.zip (3.03 KB)
Serviso_e_EA.zip (2.39 KB)
Модель движения цены и ее основные положения (Часть 2):  Уравнение эволюции вероятностного поля цены и возникновение наблюдаемого случайного блуждания Модель движения цены и ее основные положения (Часть 2): Уравнение эволюции вероятностного поля цены и возникновение наблюдаемого случайного блуждания
Выведено уравнение эволюции вероятностного поля цены, найден критерий приближения ценового скачка, раскрыты суть ценовых значений на графиках котировок и механизм возникновения случайного блуждания этих значений.
Машинное обучение и Data Science (Часть 02): Логистическая регрессия Машинное обучение и Data Science (Часть 02): Логистическая регрессия
Классификация данных — важнейшая вещь для алготрейдера и программиста. В этой статье мы рассмотрим в подробностях один из классификационных логистических алгоритмов, который может помочь нам определить «да» или «нет», рост или падение, покупки или продажи.
DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс
Настала пора заняться оживлением графического интерфейса — делать функционал для взаимодействия объектов с пользователем и другими объектами. И для того, чтобы более сложные объекты могли правильно работать, нам уже необходим функционал взаимодействия объектов друг с другом и с пользователем.
Разработка торгового советника с нуля (Часть 15): Доступ к данным в Интернете (I) Разработка торгового советника с нуля (Часть 15): Доступ к данным в Интернете (I)
Как получить доступ к данным в Интернете в MetaTrader 5. В Интернете у нас есть различные сайты и места, с огромным количеством информации, доступной для тех, кто знает, где искать и как лучше всего использовать эту информацию.