English
preview
Знакомство с языком MQL5 (Часть 37): Освоение API и функции WebRequest в языке MQL5 (XI)

Знакомство с языком MQL5 (Часть 37): Освоение API и функции WebRequest в языке MQL5 (XI)

MetaTrader 5Интеграция |
146 0
ALGOYIN LTD
Israel Pelumi Abioye

Введение

И снова приветствуем вас в Части 37 серии "Знакомство с языком MQL5"! В предыдущей статье мы познакомились с основами API-подписей и разобрали, как в языке MQL5 создаются безопасные запросы с использованием хеширования и методов на основе HMAC. Мы сосредоточились на том, почему подписи необходимы, как они защищают чувствительные запросы и как генерация подписей концептуально реализована в языке MQL5.

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

 

Получение временной метки сервера Binance для подписанных запросов

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

Чтобы получить это серверное время, нужно отправить запрос к API Binance. В отличие от локального времени, которое может отличаться в зависимости от часов вашего компьютера или часового пояса, серверная временная метка является официальным ориентиром, который Binance использует для всех авторизованных запросов. Это означает, что перед созданием подписи наша программа на языке MQL5 должна обратиться к Binance, чтобы получить серверное время. Любая подпись, созданная без этого шага, будет отклонена, поскольку временная метка не будет соответствовать требованиям Binance. Чтобы получить текущее серверное время, нужно отправить GET-запрос на определенный эндпоинт Binance. К этому эндпоинту можно обращаться свободно, поскольку он не требует аутентификации. Текущее серверное время в миллисекундах содержится в JSON-объекте, который Binance возвращает в ответ на наш запрос. После этого наш скрипт извлечет это значение из ответа и использует его как сообщение при создании подписи.

Правильно формируя запрос, обрабатывая ответ и интерпретируя временную метку, мы обеспечиваем синхронизацию каждого последующего API-вызова с серверными часами Binance. Хотя это может показаться незначительным, этот шаг имеет решающее значение, поскольку без точной временной метки не сработают ни приватный запрос, ни подпись. Сначала нам нужно научиться надежно получать серверное время, и только потом мы сможем объединить эту временную метку с секретным ключом, чтобы далее по статье сгенерировать корректно подписанный запрос. Хотя я уже говорил об этом раньше, это стоит повторить еще раз. Прежде чем ваш скрипт на языке MQL5 сможет обращаться к Binance, вам нужно включить WebRequest в настройках платформы. Нажмите Ctrl+O, чтобы открыть настройки, а затем добавьте https://api.binance.com в список разрешенных URL в разделе "Советники". Это обязательное предварительное условие перед отправкой любых подписанных API-запросов, поскольку оно гарантирует, что ваш скрипт сможет взаимодействовать с серверами Binance без блокировки.

Пример:
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---

   string url_t = "https://api.binance.com/api/v3/time";
   string headers_t = "";
   char result_t[];
   string response_headers_t;
   char data_t[];

   int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   string time = CharArrayToString(result_t);

   Print(time);
  }

Вывод:

Figure 1. Timestamp

Пояснение:

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

Кроме того, скрипт объявляет массивы для хранения содержимого запроса и ответа. Поскольку GET-запрос не содержит данных, массив для тела запроса остается пустым, тогда как массив для ответа будет содержать необработанные данные, возвращенные сервером. Все заголовки, которые вернет сервер, сохраняются в отдельной переменной. Хотя это и необязательно, такая информация может пригодиться для отладки или проверки дополнительных данных, которые вернул Binance. Затем GET-запрос отправляется в Binance через функцию WebRequest. Эта функция принимает, в частности, тип запроса, URL, заголовки, таймаут в миллисекундах, содержимое запроса, массив для хранения ответа и переменную для возврата заголовков. Функция возвращает код состояния, который показывает, был ли запрос успешным. Обычно успешный запрос возвращает код состояния 200.

После получения ответа от сервера необработанный массив байтов преобразуется в читаемую строку. Это преобразование позволяет извлекать данные в виде, удобном для человека. Сервер Binance отправляет временную метку как последовательность байтов в формате JSON. Наконец, скрипт выводит временную метку, чтобы убедиться, что запрос выполнен успешно и что у нас есть текущее серверное время Binance в миллисекундах.

Аналогия:

Допустим, вы хотите узнать, сколько времени в городе вашего друга, прежде чем назначить звонок. Чтобы ваше письмо дошло до адресата, сначала нужно указать его адрес. Это похоже на то, как скрипт задает URL эндпоинта Binance, тем самым указывая программе точный адрес для отправки запроса. Он указывает программе на языке MQL5 точное место, куда нужно отправить запрос, чтобы получить официальное серверное время. Крайне важно использовать именно это серверное время, поскольку оно гарантирует, что впоследствии ваши действия будут синхронизированы с часами Binance. Если вместо этого опираться на собственные локальные часы, Binance может отклонить ваш запрос. 

Теперь представьте, что в конверте могут быть дополнительные инструкции или пометки. Заголовки WebRequest можно сравнить именно с такими пометками. Для этого конкретного запроса, который касается серверного времени, дополнительные пометки не нужны, поскольку Binance и так понимает, что именно вы запрашиваете. Подобно тому как в заголовки добавляют API-ключ, для других типов запросов, например, при передаче конфиденциальных данных, может понадобиться указать секретный код или идентификатор.

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

Когда вы открываете посылку, внутри наконец оказывается нужная информация; однако, сначала она может быть записана в таком виде, который вы не сможете сразу прочитать. Перевести это скрытое сообщение на обычный язык – все равно что преобразовать массив байтов в читаемый текст. Вывод временной метки, которая показывает точное серверное время в миллисекундах, похож на чтение уведомления от Binance. Зная, что ваше время совпадает с часами Binance, вы можете уверенно планировать дальнейшие действия. Из вывода WebRequest видно, что ответ сервера представлен в формате JSON. Помимо самих чисел, ответ содержит структурированные ключи и значения. JSON-объект этого конкретного эндпоинта содержит единственный ключ serverTime, значение которого указывает текущее серверное время в миллисекундах. Благодаря формату ответа извлечь временную метку довольно просто.

При создании API-подписи мы будем использовать в качестве сообщения значение, связанное с serverTime, поэтому именно оно нам и нужно. Выделив это числовое значение, мы сможем гарантировать, что наши последующие подписанные запросы будут синхронизированы со временем сервера Binance и, следовательно, будут приняты приватными эндпоинтами.

Пример:
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---

   string url_t = "https://api.binance.com/api/v3/time";
   string headers_t = "";
   char result_t[];
   string response_headers_t;
   char data_t[];

   int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   string time = CharArrayToString(result_t);

   string pattern = "{\"serverTime\":";
   int pattern_lenght = StringFind(time,pattern);
   pattern_lenght += StringLen(pattern);
   int end = StringFind(time,"}",pattern_lenght + 1);

   string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght);
   Print(server_time);

  }

Пояснение:

Первая строчка задает строку pattern, которая содержит "serverTime". Именно с такого фрагмента начинался JSON-объект, который вернул сервер Binance. Распознав этот шаблон, мы можем определить, где именно в полном JSON-ответе начинается реальное значение временной метки. Первая строчка задает строку pattern, которая содержит "serverTime". Именно с такого фрагмента начинался JSON-объект, который вернул сервер Binance. Распознав этот шаблон, мы можем определить, где именно в полном JSON-ответе начинается реальное значение временной метки.

Следующий шаг – найти этот шаблон в строке ответа. Функция возвращает начальный индекс этого шаблона в ответе. К этой позиции мы добавляем длину шаблона, поскольку нас интересует значение, которое идет сразу после него. Так мы получаем позицию, с которой в строке начинается серверная временная метка.

Аналогия:

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

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

 

Создание API-ключа Binance и секретного ключа

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

Чтобы сгенерировать эти ключи, сначала нужно войти в свою учетную запись Binance. После входа в систему перейдите в свой профиль, а затем выберите "API Management". Здесь отображается опция создания нового API-ключа. Binance попросит вас задать для него имя или метку. Выберите понятное имя, например, "MQL5 Trading Bot", чтобы в будущем вы могли быстро распознать этот ключ, особенно если создадите несколько API-ключей. После того как вы зададите имя, Binance создаст API-ключ и секретный ключ. Секретный ключ отображается только один раз, тогда как API-ключ будет сразу раскрыт.

Очень важно надежно сохранить секретный ключ, поскольку Binance больше не покажет его. Его можно хранить в надежном менеджере паролей или в защищенном файле. Никогда не передавайте свой секретный ключ посторонним и не включайте его в скрипты, которые могут увидеть другие люди. Когда ключи будут сгенерированы, следующим шагом нужно настроить разрешения для API-ключа. Обычно для получения баланса счета и отправки запросов из языка MQL5 нужно включить "Read Info". Если ваш бот будет совершать сделки, нужно также включить торговлю. Чтобы предотвратить вывод средств с вашего счета, никогда не включайте возможность вывода, если в этом нет крайней необходимости.

Когда у вас есть и секретный ключ, и API-ключ, программа на языке MQL5 уже может создавать подписи и отправлять безопасные запросы к Binance. Секретный ключ имеет решающее значение, поскольку вместе с сообщением и датой он используется для создания уникальной подписи. Это защищает ваши запросы от вмешательства и гарантирует, что только ваша программа сможет получить доступ к вашему аккаунту.

 

Создание подписей API Binance на языке MQL5

Теперь, когда мы понимаем, как с помощью GET-запроса к API получать время сервера Binance, и уже успешно создали API-ключ и секретный ключ, следующим шагом будет создание подписи. Как мы обсуждали в предыдущей статье, подпись – это надежный способ подтвердить, что запросы, отправляемые в Binance, подлинны и не были изменены. Разница в том, что на этот раз мы создаем подпись с использованием точных параметров, необходимых приватному API Binance. Мы создаем уникальную подпись, объединяя секретный ключ с текущей серверной временной меткой в миллисекундах. Добавив эту подпись к API-запросу, Binance сможет проверить подлинность запроса и целостность данных.

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

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

Пример:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---

   string url_t = "https://api.binance.com/api/v3/time";
   string headers_t = "";
   char result_t[];
   string response_headers_t;
   char data_t[];

   int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   string time = CharArrayToString(result_t);

   string pattern = "{\"serverTime\":";
   int pattern_lenght = StringFind(time,pattern);
   pattern_lenght += StringLen(pattern);
   int end = StringFind(time,"}",pattern_lenght + 1);

   string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght);

   string message = "timestamp=" + server_time;
   string secrete_key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE";

   uchar uMsg[], uSKey[];
   StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8);
   StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8);

  }

Пояснение:

На этом этапе мы начинаем с указания сообщения, которое будет подписано. Значение серверного времени, которое мы ранее извлекли из ответа API Binance, объединяется со строкой "timestamp=", образуя сообщение. Поскольку Binance требует, чтобы подписанные запросы строго соответствовали синтаксису параметров, добавление этого префикса обязательно. Сырые числовые значения сервер не подписывает. Подписываются именно пары ключ-значение. Если мы попытаемся использовать только значение серверного времени без указания "timestamp=", сгенерированная подпись не совпадет с той, которую вычислит Binance на своей стороне, и запрос будет отклонен. По этой причине сообщение должно быть сформировано в точности так, как этого требует спецификация API Binance.

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

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

Аналогия:

Предположим, вы хотите отправить заказное письмо в службу безопасности, которая принимает письма только в определенном формате. Сначала вы пишете текст письма. В данном случае содержимым письма является время. Однако просто число, напечатанное на бумаге, такая служба не примет. Она ожидает, что время будет четко обозначено, например, в виде пометки "timestamp=время". Если вы отправите только число без такой метки, служба отклонит письмо, потому что оно не будет соответствовать нужному формату. По этой причине перед серверным временем необходимо поставить "timestamp=". Без этого письмо никогда не будет принято.

Теперь представьте секретный ключ как личную печать, которая есть только у вас. Эта печать никогда не помещается в конверт. Вместо этого вы используете ее у себя на рабочем месте, чтобы пометить письмо перед отправкой. Благодаря этой печати служба может проверить, что письмо действительно отправили именно вы. Если кто-то другой попытается воспроизвести ваше письмо без этой печати, служба сразу поймет, что оно поддельное. Представьте теперь, что рукописные письма такая служба не читает. Она понимает только текст, набранный в определенной раскладке клавиатуры. Поэтому при переписывании сообщения и сведений о секретной печати вам нужно использовать именно этот формат. Чтобы при чтении письма не возникло путаницы, сообщение и секретный ключ преобразуются в массивы символов с использованием UTF-8 – это похоже на набор всего текста на разрешенной клавиатуре.

Пример:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---

   string url_t = "https://api.binance.com/api/v3/time";
   string headers_t = "";
   char result_t[];
   string response_headers_t;
   char data_t[];

   int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   string time = CharArrayToString(result_t);

   string pattern = "{\"serverTime\":";
   int pattern_lenght = StringFind(time,pattern);
   pattern_lenght += StringLen(pattern);
   int end = StringFind(time,"}",pattern_lenght + 1);

   string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght);

   string message = "timestamp=" + server_time;
   string secrete_key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE";

   uchar uMsg[], uSKey[];
   StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8);
   StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8);

   int blockSize = 64;
   uchar ipad[], opad[];
   ArrayResize(ipad, blockSize);
   ArrayResize(opad, blockSize);

   for(int i = 0; i < blockSize; i++)
     {
      ipad[i] = uSKey [i] ^ 0x36;
      opad[i] = uSKey [i] ^ 0x5C;
     }

   uchar innerData[], innerHash[];
   ArrayCopy(innerData, ipad);
   ArrayCopy(innerData, uMsg, blockSize);
   CryptEncode(CRYPT_HASH_SHA256, innerData, uSKey, innerHash);

   uchar outerData[], finalHash[];
   ArrayCopy(outerData, opad);
   ArrayCopy(outerData, innerHash, blockSize);
   CryptEncode(CRYPT_HASH_SHA256, outerData, uSKey, finalHash);

   string signature = "";
   for(int i = 0; i < ArraySize(finalHash); i++)
      signature += StringFormat("%02x", finalHash[i]);

   Print(signature);
  }

Пояснение:

Сначала мы задаем размер блока – 64 байта. Это значение определяется требованиями алгоритма хеширования. Использование правильного размера блока гарантирует, что передаваемые нами данные будут соответствовать требованиям алгоритма, поскольку он работает с фрагментами данных фиксированного размера. Затем объявляются два массива байтов. Один из них представляет внутренний паддинг-блок, а другой – внешний паддинг-блок. Размер обоих массивов задается в соответствии с размером блока. На этом этапе эти массивы представляют собой пустые контейнеры, каждый ровно на 64 байта. Эти паддинг-блоки необходимы, чтобы отделить внутренний и внешний этапы хеширования, и вскоре они будут заполнены значениями, полученными из секретного ключа.

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

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

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

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

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

Аналогия:

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

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

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

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

 

Отправка аутентифицированных запросов к API Binance

После того как мы разобрались, как создавать API-подписи в MQL5, следующим шагом становится отправка аутентифицированных запросов к API Binance. Если публичные эндпоинты можно вызывать без дополнительной защиты, то эндпоинты, связанные с аккаунтом, например, для получения баланса, требуют аутентификации. Именно здесь объединяются серверное время, созданная вами подпись и API-ключ. Чтобы получить баланс счета по каждому активу на Binance, нужно отправить подписанный запрос на приватный эндпоинт. Заголовки запроса содержат ваш API-ключ, а параметры – временную метку и подпись. Чтобы убедиться, что запрос подлинный и данные не были изменены, Binance проверяет все эти детали.

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

Пример:
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---

   string url_t = "https://api.binance.com/api/v3/time";
   string headers_t = "";
   char result_t[];
   string response_headers_t;
   char data_t[];

   int status_t = WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t);
   string time = CharArrayToString(result_t);

   string pattern = "{\"serverTime\":";
   int pattern_lenght = StringFind(time,pattern);
   pattern_lenght += StringLen(pattern);
   int end = StringFind(time,"}",pattern_lenght + 1);

   string server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght);

   string message = "timestamp=" + server_time;
   string secrete_key = "ABCDER2y8VAzqopxzLVEhVYABCDEV2AxIYLueComud3aSEez8Z8fvgHPZTXABCDE";

   uchar uMsg[], uSKey[];
   StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8);
   StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8);

   int blockSize = 64;

   uchar ipad[], opad[];
   ArrayResize(ipad, blockSize);
   ArrayResize(opad, blockSize);

   for(int i = 0; i < blockSize; i++)
     {
      ipad[i] = uSKey [i] ^ 0x36;
      opad[i] = uSKey [i] ^ 0x5C;
     }

   uchar innerData[], innerHash[];
   ArrayCopy(innerData, ipad);
   ArrayCopy(innerData, uMsg, blockSize);
   CryptEncode(CRYPT_HASH_SHA256, innerData, uSKey, innerHash);

   uchar outerData[], finalHash[];
   ArrayCopy(outerData, opad);
   ArrayCopy(outerData, innerHash, blockSize);
   CryptEncode(CRYPT_HASH_SHA256, outerData, uSKey, finalHash);

   string signature = "";
   for(int i = 0; i < ArraySize(finalHash); i++)
      signature += StringFormat("%02x", finalHash[i]);

   Print(signature);

   string query_string = "";
   query_string += "&signature=" + signature;

   string time_stamp = "timestamp=" + server_time;
   string url = "https://api.binance.com/api/v3/account?"+ time_stamp + query_string;

   string API_KEY = "ABcdeviQHg3LD9ncT0mabcdepfz2a3qkNvrI0abcdeIZYU5umZgeowabcdeMbq8A";
   string headers = "X-MBX-APIKEY: " + API_KEY + "\r\n";

   char   result[];
   string response_headers;
   char   datas[];

   int status = WebRequest("GET", url, headers, 5000, datas, result, response_headers);
   string server_result = CharArrayToString(result);

   Print(server_result);

  }

Пояснение:

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

После этого API-ключ готов. API-ключ не добавляется к URL, в отличие от подписи. Вместо этого он передается в заголовках запроса с использованием специального имени заголовка, которое требует Binance. Подпись подтверждает, что запрос был сформирован с использованием правильного секретного ключа, а такой заголовок показывает Binance, от чьего аккаунта отправляется запрос. WebRequest требует, чтобы заголовки соответствовали правильному синтаксису HTTP, поэтому перенос строки в конце строки заголовка имеет решающее значение.

Затем объявляются переменные для получения ответа от сервера. К ним относятся дополнительный массив символов, который функция WebRequest использует внутри себя, строка для записи заголовков ответа, возвращенных сервером, и массив символов для хранения содержимого ответа. После обработки запроса MetaTrader использует эти переменные как контейнеры для хранения всего, что вернет Binance. Поскольку для получения баланса счета не требуется отправлять данные в теле запроса, затем вызывается функция WebRequest с методом GET. Функция получает итоговый URL, значение таймаута и заголовки с API-ключом. Binance проверяет запрос, подтверждает подпись и временную метку и, если все верно, возвращает информацию о вашем аккаунте.

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

Аналогия:

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

Подпись служит доказательством того, что только вы имеете право отправить это сообщение, подобно личной восковой печати. Когда банк получает ваш запрос, оба этих элемента уже прикреплены к конверту для проверки. Затем вы подготавливаете удостоверение, которое для банка служит аналогом вашего API-ключа. Чтобы банк понимал, для какого счета предназначен запрос, это удостоверение помещается в конверт в видимый карман. Банк видит ваше удостоверение, но, поскольку ему не известна ваша секретная печать, он не может подделать вашу подпись.

Теперь вы передаете конверт надежному курьеру. Этот курьер и представляет функцию WebRequest в MQL5. Доставив письмо в банк, курьер ждет ответа. После проверки вашей личности, временной метки и восковой печати банк составляет ответ, в котором указывается баланс всех активов на вашем счете. Когда курьер возвращается, вы открываете конверт. Ответ закодирован, словно сообщение, записанное в формате секретного реестра (JSON). Чтобы легко увидеть балансы, вы преобразуете этот ответ в понятный вид. Вывести его из программы – все равно что разложить бухгалтерскую книгу у себя на столе, чтобы можно было просмотреть каждую запись. 

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

Пример:

string filename = "Binace_Balance.txt";
int handle = FileOpen(filename, FILE_WRITE|FILE_TXT|FILE_SHARE_READ|FILE_ANSI);

if(handle != INVALID_HANDLE)
  {

   FileWrite(handle, server_result);
   FileClose(handle);
   Print("EA successfully wrote the data to " + filename);
  }
else
  {
   Print("Error opening file for writing. Error code: ", + GetLastError());
  }

Пояснения:

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

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

 

Заключение

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

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

Прикрепленные файлы |
Возможности Мастера MQL5, которые вам нужно знать (Часть 70): Использование паттернов SAR и RVI с сетью экспоненциального ядра Возможности Мастера MQL5, которые вам нужно знать (Часть 70): Использование паттернов SAR и RVI с сетью экспоненциального ядра
В предыдущей статье мы представили пару индикаторов SAR и RVI. Здесь мы рассмотрим, как их можно расширить с помощью машинного обучения. SAR и RVI представляют собой взаимодополняющую пару, сочетающую в себе тренд и импульс. Наш подход к машинному обучению использует сверточную нейронную сеть (convolution neural network), которая задействует экспоненциальное ядро (Exponential kernel) для определения размеров своих ядер и каналов при настройке прогнозов этой пары индикаторов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для создания советника.
Моделирование рынка (Часть 21): Первые шаги на SQL (IV) Моделирование рынка (Часть 21): Первые шаги на SQL (IV)
Многие из вас, возможно, обладают гораздо большим опытом в области работы с базами данных, чем я, и, следовательно, имеют другое мнение. Поскольку было необходимо дать объяснение, почему базы данных создаются именно так, как они создаются, и нужно объяснить, почему SQL имеет именно такой формат и, прежде всего, почему появились первичные ключи и внешние ключи, поэтому пришлось оставить некоторые вещи немного абстрактными.
Внедрение в MQL5 практических модулей из других языков (Часть 1): Создание библиотеки SQLite3 как в Python Внедрение в MQL5 практических модулей из других языков (Часть 1): Создание библиотеки SQLite3 как в Python
Модуль sqlite3 в Python предлагает простой способ работы с базами данных SQLite, быстрый и удобный. В этой статье мы создадим подобный модуль поверх встроенных функций MQL5 для работы с базами данных, чтобы упростить работу с базами данных SQLite3 в MQL5 так же, как это реализовано в Python.
Преодоление ограничений машинного обучения (Часть 4): Как уменьшить неустранимую ошибку с помощью нескольких горизонтов прогноза Преодоление ограничений машинного обучения (Часть 4): Как уменьшить неустранимую ошибку с помощью нескольких горизонтов прогноза
Машинное обучение часто рассматривается через призму статистики или линейной алгебры, но в этой статье особое внимание уделяется геометрической перспективе предсказаний моделей. В ней демонстрируется, что модели на самом деле не приближают цель к действительности, а скорее переносят ее в новую систему координат, создавая неизбежное смещение, которое приводит к неустранимой ошибке. В статье предполагается, что многоступенчатые прогнозы, сравнивающие прогнозы модели на разных горизонтах, предлагают более эффективный подход, чем прямые сравнения с целевым показателем. Применяя этот метод к торговой модели, авторы статьи демонстрируют значительное повышение прибыльности и точности без изменения базовой модели.