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

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

MetaTrader 5Примеры | 8 июля 2022, 16:30
1 154 1
Daniel Jose
Daniel Jose

1.0 - Введение

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

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

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

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

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

Давайте приступим к работе, а точнее к кодированию альтернативной системы.


2.0 - Планирование

Начнем с того, что мы знаем, что мы можем использовать только переменные типа double в системе каналов, предоставляемой MetaTrader 5, и что этот тип состоит из 8 байтов. Но возможно потом вы подумаете: «Ну и что!? Как это мне поможет?» Успокойтесь, давайте разберемся с таким моментом:

Компьютерные системы работают с байтами, хотя многие забыли об этом понятии: оно важно и необходимо, а каждый байт состоит из 8 бит. И 1 бит — это наименьшее количество, возможное в вычислительной системе, и в этом случае наименьший и простейший тип, присутствующий в языке, — это логический тип, который состоит именно из этого единственного 1 бита. Это самое простое из основного.

Итак, любая информация, какой бы сложной она ни была, будет содержаться в 1 байте. Опять же, какой бы сложной ни была информация, она всегда будет в пределах 1 байта, который состоит из 8 бит. Когда мы соединяем 2 байта, у нас будет первый составной набор в системе, этот первый набор известен как WORD, второй набор как DWORD, который будет 2 WORD, а третий набор будет QWORD, который будет 2 DWORD. Это номенклатура, используемая в assembly, которая является прародителем всех современных языков, поэтому большинство систем используют одни и те же типы, но есть деталь — название, которое получат эти типы.

Мы надеемся, что вам удалось проследить рассуждения до этого момента. Чтобы упростить задачу для тех, кто только начинает, взгляните на изображения ниже:

          

                         

На приведенных выше рисунках мы показываем основные типы, доступные на данный момент, и они охватывают от 1 до 64 бит. Но вы могли бы подумать: «Зачем мне это объяснение!?» Важная деталь заключается в том, что это важно знать, чтобы понимать то, что мы будем делать до конца статьи, так как мы собираемся манипулировать этими типами, чтобы иметь возможность передавать информацию с разными внутренними свойствами.

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

Название Количество байтов Название, основанное на языке ассемблера (изображения выше) 
bool   Используется только 1 бит, в байте может быть 8 логических значений.  Используется только 1 бит, в байте может быть 8 логических значений.
char 1
 Byte
short 2  Word
int  4  DWord
long 8  QWord

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

Название Количество байтов Название, основанное на языке ассемблера (изображение выше) 
Float 4  DWord
Doble 8  QWord

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

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

Действительно, в данных порядок, и думаем, что саму идею можно понять. Большая деталь заключается в том, что если в сообщении больше 8 печатных символов, то его придется фрагментировать на большее количество частей, если же доставку нужно сделать быстро, то есть за 1 цикл, то придется использовать столько глобальных переменных терминала, сколько будет необходимо для передачи сообщения в цикле. Затем объедините их соответствующим образом, пока исходное сообщение не будет восстановлено. Но если его можно доставлять пакетами, то необходимо создать такой сервер, а в данном случае сервис, который знал, что клиент, которым в данном случае является советник, прочитал опубликованное сообщение и дождался следующего блока.

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


3.0 - Реализация

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

3.0.1 - Базовая модель

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

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        char    Info[sizeof(double)];
};
//+------------------------------------------------------------------+

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

В итоге у нас получится вот такая картинка:

Обратите внимание, что это похоже на изображенное выше, но это то, что делает объединение.

Следующий код для модификации - это советник, который соответствует клиенту, полностью код можно увидеть ниже:

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

Обратите внимание, что здесь мы используем функцию CharArrayToString для преобразования массива uchar в строку, но обратите внимание, что мы получаем значение double, которое является единственным возможным для получения из терминальной глобальной переменной, но строка в языке MQL5 работает по тем же принципам, что и в C/C++, поэтому мы не можем использовать никаких символов, кроме как для создания своих собственных. Однако, это уже другая история, мы тут не будем подробно останавливаться на том, как это сделать, так как здесь может использоваться моделирование, в котором данные сжимаются, чтобы превысить ограничение в 8 байтов.

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

//+------------------------------------------------------------------+
#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc;
        char car = 33;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                for (char c0 = 0; c0 < sizeof(uDataServer); c0++)
                {
                        loc.Info[c0] = car;
                        car = (car >= 127 ? 33 : car + 1);
                }
                GlobalVariableSet(def_GlobalNameChannel, loc.value);
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

Посмотрите, это что-то достаточно простое, но чрезвычайно эффективное и функциональное.

Запуск этого на платформе даст нам именно следующий результат


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

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


3.0.2 - Обмен «штампами»

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

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

3.0.2.1 - Тест связи клиент-сервер

Сделаем следующее: давайте посмотрим на код сервиса, который полностью можно увидеть ниже:

#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc, loc1, loc2;
        char car = 33;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalValueInChannel))
                {
                        GlobalVariableTemp(def_GlobalValueInChannel);
                        GlobalVariableTemp(def_GlobalMaskInfo);
                        GlobalVariableTemp(def_GlobalPositionInfos);
                }
                for (char c0 = 0; c0 < sizeof(uDataServer); c0++)
                {
                        loc.Info[c0] = car;
                        car = (car >= 127 ? 33 : car + 1);
                }
                GlobalVariableSet(def_GlobalValueInChannel, loc.value);
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
                Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "   ",loc2.Position[0], "    ", loc2.Position[1]);
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

Обратите внимание на некоторые интересные вещи в новом коде сервера или службы. Теперь мы будем иметь дело не с 1 переменной, а с 3 переменными, и они работают так, чтобы создать канал, достаточно большой для того, чтобы клиент (в данном случае советник), мог общаться с сервером, который в данном случае является службой. Но обратите внимание на следующую строку:

Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "   ",loc2.Position[0], "    ", loc2.Position[1]);

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalValueInChannel        "Inner Channel"
#define def_GlobalMaskInfo                      "Mask Info"
#define def_GlobalPositionInfos         "Positions Infos"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        uint    Position[2];
        char    Info[sizeof(double)];
};
//+------------------------------------------------------------------+

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

Это схема, находящаяся внутри объединения uDataServer.

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

Но вернемся к системе. Следующее, что нужно сделать - это создать код клиента, а точнее советника, и это можно полностью увидеть ниже.

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
enum eWhat {DOW_JONES, SP500};
input eWhat     user01 = DOW_JONES;     //Buscar
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        uDataServer loc;
        
        SetFind();
        if (GlobalVariableCheck(def_GlobalValueInChannel))
        {
                GlobalVariableGet(def_GlobalMaskInfo, loc.value);
                Print(CharArrayToString(loc.Info, 0, sizeof(uDataServer)), "  ", GlobalVariableGet(def_GlobalValueInChannel));
        }
}
//+------------------------------------------------------------------+
inline void SetFind(void)
{
        static int b = -1;
        uDataServer loc1, loc2;
        
        if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
        {
                b = user01;
                switch (user01)
                {
                        case DOW_JONES  :
                                StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 172783;
                                loc2.Position[1] = 173474;
                                break;
                        case SP500              :
                                StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 175484;
                                loc2.Position[1] = 176156;
                                break;
                }
                GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
        }
};
//+------------------------------------------------------------------+

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

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




3.1.2.2 - Создание практической версии

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

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

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalValueInChannel        "Inner Channel"
#define def_GlobalMaskInfo              "Mask Info"
#define def_GlobalPositionInfos         "Positions Infos"
//+------------------------------------------------------------------+
#define def_MSG_FailedConnection        "BAD"
#define def_MSG_FailedReturn            "FAILED"
#define def_MSG_FailedMask              "ERROR"
#define def_MSG_FinishServer            "FINISH"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        uint            Position[2];
        char            Info[sizeof(double)];
};
//+------------------------------------------------------------------+

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

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

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
enum eWhat {DOW_JONES, SP500};
input eWhat     user01 = DOW_JONES;             //Buscar
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        ClientServer();
}
//+------------------------------------------------------------------+
inline void ClientServer(void)
{
        uDataServer loc1, loc2;
        string          sz0;
        
        SetFind();
        if (GlobalVariableCheck(def_GlobalValueInChannel))
        {
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                loc2.value = GlobalVariableGet(def_GlobalValueInChannel);
                sz0 = CharArrayToString(loc2.Info, 0, sizeof(uDataServer));
                if (sz0 == def_MSG_FailedConnection) Print("Failed in connection."); else
                if (sz0 == def_MSG_FailedReturn) Print("Error in Server Web."); else
                if (sz0 == def_MSG_FailedMask) Print("Bad Mask or position."); else
                if (sz0 == def_MSG_FinishServer) Print("Service Stop."); else
                Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "  ", loc2.value);
        }
}
//+------------------------------------------------------------------+
inline void SetFind(void)
{
        static int b = -1;
        uDataServer loc1, loc2;
        
        if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
        {
                b = user01;
                switch (user01)
                {
                        case DOW_JONES  :
                                StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 172783;
                                loc2.Position[1] = 173474;
                                break;
                        case SP500              :
                                StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 175487;
                                loc2.Position[1] = 176159;
                                break;
                }
                GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
        }
};
//+------------------------------------------------------------------+

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

#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc1, loc2;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalValueInChannel))
                {
                        GlobalVariableTemp(def_GlobalValueInChannel);
                        GlobalVariableTemp(def_GlobalMaskInfo);
                        GlobalVariableTemp(def_GlobalPositionInfos);
                }
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
                if (!_StopFlag)
                {
                        GlobalVariableSet(def_GlobalValueInChannel, GetDataURL(
                                                                                "https://tradingeconomics.com/stocks",
                                                                                100,
                                                                                "<!doctype html>",
                                                                                2,
                                                                                CharArrayToString(loc1.Info, 0, sizeof(uDataServer)),
                                                                                loc2.Position[0],
                                                                                loc2.Position[1],
                                                                                0x0D
                                                                               )
                                        );
                        Sleep(1000);
                }
        }
        GlobalVariableSet(def_GlobalValueInChannel, Codification(def_MSG_FinishServer));
}
//+------------------------------------------------------------------+
double 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 Codification(def_MSG_FailedConnection);
        for (int c0 = 0, c1 = StringLen(szTest); (c0 < c1) && (!_StopFlag); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return Codification(def_MSG_FailedReturn);
        for (int c0 = 0, c1 = StringLen(szFind); (c0 < c1) && (!_StopFlag); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return Codification(def_MSG_FailedMask);
        if (_StopFlag) return Codification(def_MSG_FinishServer);
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return StringToDouble(szInfo);
}
//+------------------------------------------------------------------+
inline double Codification(const string arg)
{
        uDataServer loc;
        StringToCharArray(arg, loc.Info, 0, sizeof(uDataServer));
        
        return loc.value;
}
//+------------------------------------------------------------------+

Выделенная строка важна для того, чтобы сервис сообщал, что он больше не работает.

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



Заключение

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

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

Прикрепленные файлы |
Servi0o_-_EA.zip (10.71 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Vadim Zotov
Vadim Zotov | 8 июл. 2022 в 19:17
Данная статья показывает, что MetaQuotes нужно расширить возможности глобальных переменных терминала. Ограничение типа double создаёт много проблем, которых можно избежать, если иметь возможность использовать другие типы. Важнее всего иметь возможность использовать тип string. Тогда танцы с бубном, которые блестяще продемонстрировал автор статьи, станут ненужными. И все пользователи будут очень благодарны разработчикам MQL за существенное облегчение жизни. Надеюсь, автор статьи также будет рад, что это свершилось с его подачи.
Разработка торговой системы на основе Стохастика Разработка торговой системы на основе Стохастика
Это очередная статья из обучающей серии, в которой мы знакомимся с различными индикаторами. В этот раз мы обратимся к другому популярному индикатору — Stochastic Oscillator. Изучим его, рассмотрим стратегии на его основе и создадим торговую систему.
DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс
Настала пора заняться оживлением графического интерфейса — делать функционал для взаимодействия объектов с пользователем и другими объектами. И для того, чтобы более сложные объекты могли правильно работать, нам уже необходим функционал взаимодействия объектов друг с другом и с пользователем.
Разработка торговой системы на основе индикатора ADX Разработка торговой системы на основе индикатора ADX
Эта статья продолжает серию о построении торговых систем с использованием самых популярных индикаторов. На этот раз мы поговорим об индикаторе ADX (Average Directional Index, Индекс среднего направленного движения). Мы подробно изучим этот индикатор, чтобы понять, чем он может быть полезен в торговле. Также с помощью простых стратегий мы узнаем, как его использовать. Изучая самую суть вещей, мы можем получить больше информации и использовать это с максимальной выгодой.
Модель движения цены и ее основные положения (Часть 2):  Уравнение эволюции вероятностного поля цены и возникновение наблюдаемого случайного блуждания Модель движения цены и ее основные положения (Часть 2): Уравнение эволюции вероятностного поля цены и возникновение наблюдаемого случайного блуждания
Выведено уравнение эволюции вероятностного поля цены, найден критерий приближения ценового скачка, раскрыты суть ценовых значений на графиках котировок и механизм возникновения случайного блуждания этих значений.