Знакомство с языком MQL5 (Часть 38): Освоение API и функции WebRequest в языке MQL5 (XII)
Введение
И снова приветствуем вас в Части 38 серии "Знакомство с языком MQL5"! В предыдущей статье мы сосредоточились на создании надежной основы для взаимодействия языка MQL5 с внешними системами. Мы рассмотрели создание API-эндпоинтов, работу функции WebRequest, подготовку компонентов аутентификации, таких как метки времени и подписи, а также получение и интерпретацию ответов сервера. При работе с приватными или конфиденциальными API эти принципы являются основополагающими.
В этой статье, как и в предыдущих разделах серии, используется полностью проектный подход. Вместо того чтобы обсуждать теорию в отрыве от практики, мы применим каждую идею в реальном рабочем процессе. Такой подход помогает понять как работу отдельных компонентов, так и их взаимодействие в рамках реальной интеграции. Эта статья завершает серию материалов об API и подводит итог всему пути.
В этой статье мы создадим советник на языке MQL5, который будет получать и анализировать рыночные данные, взаимодействуя с внешней платформой (API Binance). Проект начинается с выбора крнкретной монеты на платформе и использования API для получения свечных данных. Чтобы найти бычий паттерн поглощения в самой последней закрытой свече, мы получим три последние закрытые 30-минутные свечи. Мы разберем механизм программного распознавания этого паттерна, чтобы вы могли точно определять его с помощью языка MQL5. Мы также обсудим, как через API фактически отправляются ордера при выполнении этих условий.
Мы рассмотрим, как получать и анализировать рыночные данные, как MetaTrader 5 безопасно формирует запросы и как в ходе этого процесса программно оцениваются торговые паттерны. Этот проект показывает, как язык MQL5 может служить мостом между MetaTrader 5 и платформами за пределами экосистемы MetaTrader, выходя за рамки традиционной логики, основанной на графиках. К концу этой статьи у вас будет четкий и пригодный для повторного использования рабочий процесс для получения свечных данных, понимания свечных паттернов, таких как бычье поглощение, и подготовки советника к действиям в зависимости от рыночных условий. Вы также получите хорошее понимание того, как адаптировать этот подход к другим паттернам, таймфреймам и более сложным сценариям автоматизации.
Примечание:
Данная статья написана исключительно в образовательных целях. Все примеры, проекты и демонстрации предназначены для того, чтобы показать, как язык MQL5 работает в реальных сценариях интеграции. Это не является рекомендацией или призывом исполнять реальные ордера либо совершать какие-либо финансовые действия. Основное внимание здесь уделяется обучению, экспериментированию и пониманию того, как работать с API и функцией WebRequest в языке MQL5 в контролируемом учебном контексте.
Запрос и обработка свечных данных
Получение данных по трем последним свечам – это первый этап создания этого советника. Обычно это было бы просто, потому что нужный символ уже находился бы на графике MetaTrader 5. Однако, поскольку данные для этого проекта поступают напрямую из Binance, анализируемый нами актив отсутствует в MetaTrader 5. Поэтому мы не можем опираться на встроенные ценовые ряды MetaTrader 5 или его индикаторные возможности. Чтобы решить эту проблему, мы начинаем с отправки API-запроса к Binance для получения свечных данных. Эта стратегия похожа на ту, что уже показывалась в Части 29 этой серии, где мы обрабатывали свечные данные напрямую из ответа сервера, а не использовали данные графика MetaTrader 5. Здесь идея та же: мы запрашиваем у Binance необработанные свечные данные, а затем вручную обрабатываем их в советнике.
Первое, что делает советник, – отправляет запрос к API, чтобы получить последние свечные данные по выбранной монете на Binance. После получения ответа следующим шагом становится понимание структуры JSON, которую Binance использует для представления свечных данных. Binance возвращает свечи в виде структурированных массивов, где каждая свеча содержит множество значений. Поняв структуру JSON, мы используем различные функции обработки строк в MQL5, чтобы извлечь необходимые данные. Например, эти функции позволяют нам выделять и группировать такие связанные значения, как цена открытия, цена максимума, цена минимума, цена закрытия и время открытия каждой свечи. Определив последнюю закрытую свечу, мы можем быстро оценить, указывают ли данные OHLC на бычье движение.
Важно понимать, что мы не должны отправлять бесконечное количество запросов к API только потому, что это возможно. Слишком частая отправка запросов может привести к временным ограничениям или отказам в ответе, поскольку у Binance действуют строгие ограничения на частоту запросов к API. Поэтому момент отправки запроса так же важен, как и его точность. Наиболее эффективно отправлять API-запрос именно в момент открытия каждой новой 30-минутной свечи, потому что этот советник работает с уже закрытыми 30-минутными свечами. В этот момент предыдущая свеча уже полностью закрыта, а ее данные точны и окончательны. Если бы запрос отправлялся в другое время, это либо приводило бы к неполным данным по свече, либо тратило бы вызовы API, не дающие новой информации.
Согласуя наши API-запросы с моментами открытия 30-минутных свечей, мы минимизируем лишний сетевой трафик, соблюдаем ограничения Binance по частоте запросов и гарантируем, что анализируемые данные всегда будут относиться к полностью закрытой свече. Поскольку советник выполняет анализ только при появлении новой информации, этот метод также обеспечивает его эффективность и предсказуемость.
Пример:
datetime last_bar_time = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- datetime current_m30_time = iTime(_Symbol,PERIOD_M30,0); if(current_m30_time != last_bar_time) { //SEND REQUEST last_bar_time = current_m30_time; } }
Пояснение:
Чтобы отслеживать время открытия последней 30-минутной свечи, с которой работал советник, он сначала объявляет переменную. Поскольку это значение инициализируется нулем, советник может легко определить первую допустимую свечу и понять, требуется ли новый вызов API. Каждый раз, когда для выбранного символа поступает новый рыночный тик, срабатывает основная функция советника. Каждый тик означает небольшое изменение цены, а функция непрерывно отслеживает рынок. Внутри этой функции советник получает время открытия последней 30-минутной свечи. Это дает четкую точку отсчета для начала текущей свечи.
Затем советник сравнивает сохраненное время последней свечи со временем открытия текущей свечи. Если эти два времени различаются, значит, с момента последней проверки началась новая свеча. В этом случае отправляется API-запрос для получения свечных данных. Отправляя запросы только при открытии новой свечи, советник устраняет лишние вызовы и гарантирует, что будет эффективно работать в условиях ограничений API. После отправки API-запроса советник обновляет переменную времени последней свечи, присваивая ей время открытия текущей свечи. Это гарантирует, что советник будет отправлять новый запрос только при начале следующей 30-минутной свечи и что одна и та же свеча не будет обрабатываться более одного раза. Этот метод сохраняет эффективность и точность получения данных.
Аналогия:
Предположим, вы записываете повседневные события в блокнот. Чтобы не делать одну и ту же запись дважды за день, вы записываете информацию только один раз. Чтобы понять, наступил ли новый день или все еще идет тот, который вы уже записали, вы отдельно храните дату последней записи. Каждый рыночный тик в этом случае похож на то, как вы снова и снова смотрите в календарь в течение дня. Вы делали бы новую запись только тогда, когда дата действительно менялась, а не каждый раз, когда смотрели. Эту дату представляет время открытия текущей 30-минутной свечи, которое показывает, когда нужно действовать.
Аналогичным образом, вы записываете детали, когда начинается новый день, а для советника – когда открывается новая свеча, то есть когда отправляется API-запрос новых свечных данных. Чтобы тот же день не был записан повторно, после записи вы обновляете дату последней заметки. Точно так же, как советник ограничивает API-запросы одним запросом на свечу, это гарантирует, что каждый день будет обрабатываться только один раз и записи останутся аккуратными.
Пример:datetime last_bar_time = 0; string method_bar = "GET"; string url_bar = "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=30m&limit=3"; string headers_bar = ""; int time_out_bar = 5000; char data_bar[]; char result_bar[]; string result_headers_bar; string server_result; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- datetime current_m30_time = iTime(_Symbol,PERIOD_M30,0); if(current_m30_time != last_bar_time) { WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar); server_result = CharArrayToString(result_bar); Print(server_result); last_bar_time = current_m30_time; } }
Вывод:

Пояснение:
В первом разделе объявляются переменные, необходимые для вызова API Binance. Задавая методу значение "GET", мы ясно показываем, что запрос предназначен для чтения данных с сервера, а не для передачи обновлений или внесения изменений. Переменная URL содержит адрес эндпоинта Binance, который предоставляет свечные данные. Хотя сам адрес указывает на сервер, путь задает точный ресурс на нем, в данном случае эндпоинт со свечными данными. Дополнительные указания серверу задаются в строке запроса URL. Например, limit указывает серверу, сколько свечей нужно вернуть, interval определяет длительность каждой свечи, а параметр symbol задает торговую пару, для которой требуются данные.
Поскольку запрос не содержит аутентификации и других метаданных, заголовки задаются пустыми. Если ответ не будет получен, значение таймаута определяет, как долго программа будет ждать, прежде чем отменить запрос. Данные запроса, заголовки ответа и необработанный ответ сервера сохраняются в соответствующих массивах. Чтобы упростить анализ и разбор, преобразованный ответ сервера сохраняется в дополнительной строковой переменной. Затем вызывается функция WebRequest с необходимыми параметрами. При этом используются указанные URL, заголовки и таймаут, чтобы отправить GET-запрос на сервер Binance. Функция не только предоставляет информацию о статусе запроса, но и сохраняет необработанный ответ сервера в массиве ответа. Затем байтовые данные преобразуются в строку для удобства чтения. При выводе этой строки отображается полный JSON-ответ со свечными данными.
Аналогия:
Представьте, что вы запрашиваете сведения за предыдущие три дня, позвонив в библиотеку. Адрес сервера здесь представляет само здание библиотеки, а исторические данные – конкретный отдел, к которому вы обращаетесь, подобно пути API. При отправке запроса вы указываете город, нужное количество дней и уровень детализации сведений. Эти указания похожи на добавленные к URL параметры symbol, interval и limit. После этого вы отправляете письмо и ждете ответа. Когда ответ возвращается, у вас уже готов лоток, чтобы его принять. Сотрудники отправляют ответ в виде последовательности закодированных символов, которые нельзя сразу прочитать. Чтобы понять содержание сообщения за предыдущие три дня, вы сначала декодируете его в читаемый формат, а затем читаете.
Аналогичным образом, советник использует заранее заданные URL и параметры запроса, чтобы отправить GET-запрос в Binance, ждет завершения обработки запроса, получает свечные данные в виде необработанных байтов, преобразует их в читаемый текстовый формат и выводит для проверки. С помощью этого подхода советник может получать информацию о последних свечах для выбранной торговой пары.
Извлечение данных OHLC и времени из свечных данных Binance
Здесь мы сосредоточимся на извлечении нужных данных из ответа сервера Binance. Один из важных уроков при работе с API заключается в том, что форматы данных JSON не всегда являются платформенно нейтральными. Из-за этого свечные данные в разных сервисах могут быть организованы по-разному. Как уже было показано ранее в Части 29 этой серии, первым шагом при обработке любого ответа сервера становится понимание точного формата возвращаемых данных. Каждая свеча в данных Binance передается в виде структурированного массива JSON, содержащего несколько значений, включая время открытия, цену открытия, цену максимума, цену минимума, цену закрытия, объем и другие метаданные. Прежде чем принимать торговые решения, мы должны четко определить позиции в ответе, соответствующие значениям OHLC и времени открытия свечи.
После того как структура становится понятной, следующим шагом становится очистка ответа. Это подразумевает удаление лишних символов, присутствующих в формате JSON, но не полезных для вычислений, таких как скобки, запятые и кавычки. После очистки необработанного ответа мы можем начать объединять связанные данные. Например, чтобы рассматривать значения OHLC одной свечи как единое логическое целое, их нужно сгруппировать. Раскладывая извлеченные значения по массивам, мы делаем данные в MQL5 намного удобнее для дальнейшей обработки. Поскольку каждый массив может представлять определенный вид данных, например, время открытия или значения OHLC, мы можем проводить сравнения, определять бычьи и медвежьи свечи и применять логику на основе последней закрытой свечи.
Binance возвращает свечные данные в формате JSON-массива. На верхнем уровне ответ представляет собой массив с несколькими вложенными массивами. Каждый вложенный массив представляет одну свечу. В самом простом виде структура выглядит так:
[ [array 1], [array 2], [array 3] ]
В каждом вложенном массиве хранятся все данные одной свечи. Благодаря фиксированному порядку значений каждая позиция во внутреннем массиве всегда соответствует одному и тому же типу данных.
Пример:
int array_count; string candle_data[]; string first_bar_data; string first_bar_data_array[]; string second_bar_data; string second_bar_data_array[]; string third_bar_data; string third_bar_data_array[];
if(current_m30_time != last_bar_time) { WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar); server_result = CharArrayToString(result_bar); // Print(server_result); array_count = StringSplit(server_result,']', candle_data); first_bar_data = candle_data[0]; StringReplace(first_bar_data,"[[",""); StringReplace(first_bar_data,"\"",""); StringSplit(first_bar_data,',',first_bar_data_array); second_bar_data = candle_data[1]; StringReplace(second_bar_data,",[",""); StringReplace(second_bar_data,"\"",""); StringSplit(second_bar_data,',',second_bar_data_array); third_bar_data = candle_data[2]; StringReplace(third_bar_data,",[",""); StringReplace(third_bar_data,"\"",""); StringSplit(third_bar_data,',',third_bar_data_array); last_bar_time = current_m30_time; }
Пояснение:
Для обработки ответа сервера используются переменные. Значения первых трех свечей сохраняются в разных массивах, подсчитывается количество сегментов, а блоки свечей сохраняются в массиве строк. Ответ разбивается по закрывающей скобке с сохранением части синтаксиса JSON, в результате чего получается по одному элементу на каждую свечу. Для обработки первой свечи извлекается первый элемент массива свечных данных. Открывающие скобки и кавычки – это примеры лишних символов, которые удаляются из этого текста при очистке данных. Затем очищенная строка по запятым разбивается на массив, где каждый индекс соответствует отдельному значению свечи. Чтобы вторая свеча имела ту же структуру, что и первая, и была полностью отделена, она проходит тот же процесс: из соответствующего элемента массива удаляются лишние символы, после чего он разбивается по запятым.
Третья свеча обрабатывается точно так же. Удаляются лишние символы, восстанавливается сам текст, а очищенная строка разбивается на отдельные значения. После этого у каждой свечи есть свой массив с упорядоченными значениями, отражающими ее данные.
Аналогия:
Представьте ответ сервера в виде длинного рулона бумаги с несколькими последовательно напечатанными квитанциями. Каждая квитанция соответствует одной свече, но изначально все они приклеены к одному рулону, поэтому их трудно рассмотреть и использовать. Разрезать рулон на отдельные квитанции – это первый шаг. Именно это и происходит, когда ответ разбивается на несколько частей. Хотя каждый фрагмент теперь соответствует одной свече, на нем все еще остаются лишние метки, рамки и символы.
Затем первую квитанцию очищают от лишней упаковки, например, декоративных рамок или кавычек, оставляя только само содержание. После очистки вы читаете квитанцию строчка за строчкой и заносите каждое значение в отдельный столбец. Это делает содержимое более ясным и упрощает работу с ним. Затем вы выполняете ту же процедуру для второй квитанции. Вы отделяете ее от остальных, убираете лишнее оформление и раскладываете ее содержимое по отдельным полям. Чтобы гарантировать, что каждая квитанция обрабатывается одинаково, с третьей квитанцией вы поступаете точно так же.
К концу этого процесса то, что начиналось как длинный неупорядоченный рулон бумаги, превращается в несколько хорошо организованных документов. Теперь каждая запись содержит отдельные детали, которые можно сравнивать, анализировать и использовать при принятии решений. Именно так из необработанного ответа сервера получаются структурированные свечные данные, которые советник может надежно использовать. Затем извлеченные значения нужно сохранить в разных переменных. Теперь, когда свечные данные уже очищены и организованы в массивы, каждое значение легко занести в отдельную переменную. Хотя значения открытия, максимума, минимума и закрытия сохраняются по отдельности, время открытия можно записать в отдельную переменную.
Пример:
long bar1_time_s; datetime bar1_time; long bar2_time_s; datetime bar2_time; long bar3_time_s; datetime bar3_time; double bar1_open; double bar2_open; double bar3_open; double bar1_high; double bar2_high; double bar3_high; double bar1_low; double bar2_low; double bar3_low; double bar1_close; double bar2_close; double bar3_close;
if(current_m30_time != last_bar_time) { WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar); server_result = CharArrayToString(result_bar); // Print(server_result); array_count = StringSplit(server_result,']', candle_data); first_bar_data = candle_data[0]; StringReplace(first_bar_data,"[[",""); StringReplace(first_bar_data,"\"",""); StringSplit(first_bar_data,',',first_bar_data_array); second_bar_data = candle_data[1]; StringReplace(second_bar_data,",[",""); StringReplace(second_bar_data,"\"",""); StringSplit(second_bar_data,',',second_bar_data_array); third_bar_data = candle_data[2]; StringReplace(third_bar_data,",[",""); StringReplace(third_bar_data,"\"",""); StringSplit(third_bar_data,',',third_bar_data_array); bar1_time_s = (long)StringToInteger(first_bar_data_array[0])/1000; bar1_time = (datetime)bar1_time_s; bar2_time_s = (long)StringToInteger(second_bar_data_array[0])/1000; bar2_time = (datetime)bar2_time_s; bar3_time_s = (long)StringToInteger(third_bar_data_array[0])/1000; bar3_time = (datetime)bar3_time_s; bar1_open = StringToDouble(first_bar_data_array[1]); bar2_open = StringToDouble(second_bar_data_array[1]); bar3_open = StringToDouble(third_bar_data_array[1]); bar1_high = StringToDouble(first_bar_data_array[2]); bar2_high = StringToDouble(second_bar_data_array[2]); bar3_high = StringToDouble(third_bar_data_array[2]); bar1_low = StringToDouble(first_bar_data_array[3]); bar2_low = StringToDouble(second_bar_data_array[3]); bar3_low = StringToDouble(third_bar_data_array[3]); bar1_close = StringToDouble(first_bar_data_array[4]); bar2_close = StringToDouble(second_bar_data_array[4]); bar3_close = StringToDouble(third_bar_data_array[4]); last_bar_time = current_m30_time; }
Пояснение:
В этом фрагменте кода извлеченные свечные данные наконец распределяются по указанным переменным, представляющим информацию о времени и ценах. Перед любым присвоением для каждой свечи объявляется набор переменных для времени открытия и значений OHLC. Для времени каждой свечи объявляются две переменные. Одна – это переменная типа datetime, которая будет хранить преобразованное время в формате, понятном MetaTrader, а другая – целочисленная переменная типа long, используемая для временного хранения исходного значения метки времени. Для каждого ценового значения свечи объявляются отдельные переменные типа double. Предварительное объявление этих переменных гарантирует, что у каждой части информации будет свое четко определенное место в программе, и проясняет структуру данных.
Данные каждой свечи располагаются в массивах в том порядке, который задает Binance. Первый элемент, то есть индекс 0, содержит время открытия свечи. Изначально это значение представлено строкой в миллисекундах. Чтобы работать с этим значением в MQL5 и перевести миллисекунды в секунды, строка преобразуется в целое число и делится на 1000. Это действие переводит метку времени в секунды и убирает миллисекундную точность. После этого преобразования значение можно безопасно привести к типу datetime. Эта процедура выполняется для каждой свечи, чтобы все времена открытия точно соответствовали системе времени MetaTrader.
Следующий элемент массива свечи, расположенный по индексу 1, представляет цену открытия. После преобразования из текста в число типа double это значение помещается в соответствующую переменную цены открытия. Цена максимума свечи представлена значением по индексу 2, которое преобразуется аналогичным образом. Значение цены минимума находится по индексу 3, а цены закрытия – по индексу 4. Чтобы использовать эти значения в численных вычислениях и сравнениях, каждое из них по отдельности преобразуется из строки в double.
Каждый массив свечи следует одной и той же схеме индексации. Время открытия всегда представлено индексом 0, цена открытия – индексом 1, цена максимума – индексом 2, цена минимума – индексом 3, а цена закрытия – индексом 4. Благодаря этой фиксированной структуре советник может точно считывать и присваивать каждое значение без путаницы. К концу этого этапа необработанные текстовые данные, полученные через API Binance, полностью преобразуются в переменные с корректными типами. Ценовые значения теперь хранятся как числовые типы, пригодные для анализа, а значения времени – как объекты datetime, легко интегрируемые с MetaTrader. После этого завершающего преобразования советник может точно оценивать направление свечи, например, является ли последняя закрытая свеча бычьей.
Аналогия:
Представьте, что сервер отвечает тремя конвертами, каждый из которых содержит сводку по одной 30-минутной свече. У всех конвертов одинаковая структура. В первой строке указано время начала свечи, во второй – цена открытия, в третьей – цена максимума, в четвертой – цена минимума, а в пятой – цена закрытия. Представьте, что прежде чем использовать эти сведения, вы кладете каждый конверт на полку. Чтобы к ним было удобно обращаться, каждый конверт нужно открыть, а его содержимое аккуратно разложить по разным полкам. Первая строка указывает время начала свечи, словно маленькая карточка внутри конверта. Оно не совпадает с часами на вашей полке, потому что выражено в миллисекундах. Прежде чем поместить это значение в соответствующий слот, вы преобразуете его в формат, понятный вашей системе.
Затем остальные строки конверта, содержащие значения открытия, максимума, минимума и закрытия, раскладываются по соответствующим полкам. Используя один и тот же порядок действий для каждого из трех конвертов, вы следите за тем, чтобы все было организовано правильно. Так как каждая полка содержит один и тот же тип данных по всем трем свечам, в итоге книжная полка приобретает четкую и единообразную структуру, которая упрощает анализ. После того как данные каждой свечи аккуратно упорядочены, следующим шагом становится сбор сопоставимых видов данных в отдельные массивы. Сложите все карточки с ценой открытия с полок в один ряд, затем все карточки с максимумами – в другой, карточки с минимумами – в третий, а карточки с закрытием – в четвертый. Благодаря такой структуре каждый вид данных легко получать и обрабатывать.
Это упрощает работу с данными, потому что каждый массив содержит один и тот же тип информации для каждой свечи. Например, вместо того чтобы проходить по свечам по отдельности, вы можете просто обратиться к нужному массиву, если хотите быстро проверить последнюю цену открытия или сравнить максимумы трех последних свечей. Такая организация облегчает применение логики, основанной на времени, цене или других характеристиках свечей, и помогает ускорить анализ.
Пример:
if(current_m30_time != last_bar_time) { WebRequest(method_bar, url_bar, headers_bar, time_out_bar, data_bar, result_bar, result_headers_bar); server_result = CharArrayToString(result_bar); // Print(server_result); array_count = StringSplit(server_result,']', candle_data); first_bar_data = candle_data[0]; StringReplace(first_bar_data,"[[",""); StringReplace(first_bar_data,"\"",""); StringSplit(first_bar_data,',',first_bar_data_array); second_bar_data = candle_data[1]; StringReplace(second_bar_data,",[",""); StringReplace(second_bar_data,"\"",""); StringSplit(second_bar_data,',',second_bar_data_array); third_bar_data = candle_data[2]; StringReplace(third_bar_data,",[",""); StringReplace(third_bar_data,"\"",""); StringSplit(third_bar_data,',',third_bar_data_array); bar1_time_s = (long)StringToInteger(first_bar_data_array[0])/1000; bar1_time = (datetime)bar1_time_s; bar2_time_s = (long)StringToInteger(second_bar_data_array[0])/1000; bar2_time = (datetime)bar2_time_s; bar3_time_s = (long)StringToInteger(third_bar_data_array[0])/1000; bar3_time = (datetime)bar3_time_s; bar1_open = StringToDouble(first_bar_data_array[1]); bar2_open = StringToDouble(second_bar_data_array[1]); bar3_open = StringToDouble(third_bar_data_array[1]); bar1_high = StringToDouble(first_bar_data_array[2]); bar2_high = StringToDouble(second_bar_data_array[2]); bar3_high = StringToDouble(third_bar_data_array[2]); bar1_low = StringToDouble(first_bar_data_array[3]); bar2_low = StringToDouble(second_bar_data_array[3]); bar3_low = StringToDouble(third_bar_data_array[3]); bar1_close = StringToDouble(first_bar_data_array[4]); bar2_close = StringToDouble(second_bar_data_array[4]); bar3_close = StringToDouble(third_bar_data_array[4]); datetime OpenTime[3] = {bar1_time, bar2_time, bar3_time}; double OpenPrice[3] = {bar1_open, bar2_open, bar3_open}; double ClosePrice[3] = {bar1_close, bar2_close, bar3_close}; double LowPrice[3] = {bar1_low, bar2_low, bar3_low}; double HighPrice[3] = {bar1_high, bar2_high, bar3_high}; last_bar_time = current_m30_time; }
Пояснение:
Советник группирует однотипные данные, чтобы упорядочить информацию по трем свечам. Поскольку времена открытия самой старой, средней и последней свечей собираются в единую группу, к ним становится проще обращаться, нежели работать отдельно со значениями для каждой свечи. Применяя тот же подход к ценам открытия, советник может быстро сравнивать цены открытия каждой свечи. Цены закрытия также группируются вместе, как и цены минимума и максимума каждой свечи. Организация этих связанных значений значительно упрощает такие расчеты, как анализ тренда, сравнение максимумов и минимумов или поиск бычьих и медвежьих паттернов.
По сути, такая организация создает структурированную таблицу, где время, цена открытия, цена закрытия, цена минимума и цена максимума последовательно располагаются по столбцам, а каждая свеча представлена отдельной строкой. Это упрощает управление данными и закладывает основу для автоматизированной торговой логики в советнике или для анализа свечных паттернов.
Аналогия:
Предположим, у вас есть три коробки, соответствующие трем предыдущим 30-минутным свечам на Binance. В каждой коробке содержится пять элементов информации: время открытия свечи, цена открытия, цена закрытия, цена минимума и цена максимума.
Вы решили упорядочить все по типам, а не носить три разные коробки, в которых вся информация перемешана. Время открытия из всех трех коробок объединяется вместе, и то же самое делается для цен открытия, закрытия, минимума и максимума. Вам больше не нужно просматривать каждую коробку, чтобы узнать цену открытия второй свечи или максимальную цену из трех. Поскольку все хорошо организовано, доступ к данным, их сравнение и анализ становятся быстрыми и простыми.
Пример:if(ClosePrice[0] < OpenPrice[0] && ClosePrice[1] > OpenPrice[1] && ClosePrice[1] > HighPrice[0]) { Print("BULLISH ENGULFING CONFIRMED"); //SEND A BUY ORDER TO BINANCE } else { // NO BULLSH ENGULFING Print("NO BULLISH ENGULFING CONFIRMED"); }
Пояснение:
Это условие, основанное на созданных ранее массивах свечей, предназначено для выявления паттерна бычьего поглощения. В этой конфигурации нас интересуют только две последние закрытые свечи. В частности, индекс 0 обозначает третью свечу, то есть самую старую из трех; индекс 1 – вторую, среднюю свечу; а индекс 2 – текущую, еще формирующуюся свечу, которая из-за своей незавершенности не используется при определении этого паттерна.
Первая часть условия проверяет, является ли предыдущая свеча (индекс 0 – третья свеча при отсчете справа) медвежьей. Это делается путем сравнения цены открытия и цены закрытия свечи. Если цена закрытия ниже цены открытия, это указывает на то, что в течение своего периода свеча двигалась вниз. Вторая часть условия определяет, является ли последняя завершенная свеча (индекс 1, вторая свеча) бычьей. Это подтверждается, когда цена закрытия свечи выше цены открытия, что указывает на восходящее движение.
Последняя часть условия проверяет, полностью ли текущая бычья свеча поглощает предыдущую медвежью свечу. Это подтверждается проверкой того, находится ли закрытие бычьей свечи выше максимума медвежьей свечи. Если это условие выполняется, формируется паттерн бычьего поглощения, поскольку тело последней свечи полностью охватывает тело предыдущей. Если все эти условия выполнены, код сообщает, что паттерн найден, выводя "BULLISH ENGULFING CONFIRMED". В реальном торговом сценарии в этот момент можно было бы отправить ордер на покупку в Binance.
Если какое-либо из условий не выполняется, срабатывает блок else, который выводит "NO BULLISH ENGULFING CONFIRMED", показывая, что действительного паттерна бычьего поглощения нет. Эта логика предлагает простой программный способ выявления паттерна бычьего поглощения с использованием отсортированных свечных данных, при этом текущая, еще формирующаяся свеча (индекс 2), не учитывается, поскольку ее нельзя использовать для подтверждения паттерна.
Автоматизация отправки ордеров через API Binance
Теперь, когда мы знаем, как распознавать паттерн бычьего поглощения, следующим шагом в этой главе становится автоматическое исполнение сделок. Здесь мы сосредоточимся на том, как использовать функцию WebRequest в MQL5 вместе с API для отправки ордеров в Binance. В предыдущей статье для создания подписи использовались только метка времени и секретный ключ. Этого было достаточно, поскольку в строку запроса для получения баланса счета включались только метка времени и сгенерированная подпись. Но на этот раз для отправки ордера требуется больше данных, чем просто метка времени.
При размещении ордеров через API Binance в строку запроса включаются, например, торговый символ, направление ордера, тип ордера, объем и метка времени. Важно понимать, что при создании подписи должен использоваться каждый параметр, входящий в строку запроса. Binance отклонит запрос, если в процессе создания подписи будет отсутствовать какой-либо параметр.
Пример:
string url_t = "https://api.binance.com/api/v3/time"; string headers_t = ""; char result_t[]; string response_headers_t; char data_t[]; string time; string pattern = "{\"serverTime\":"; int pattern_lenght; int end; string server_time = ""; string time_stamp = ""; string symbol; string sQty; string side; string params; string message; string secrete_key;
if(ClosePrice[0] < OpenPrice[0] && ClosePrice[1] > OpenPrice[1] && ClosePrice[1] > HighPrice[0]) { // Print("BULLISH ENGULFING CONFIRMED"); //SEND A BUY ORDER TO BINANCE WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); time = CharArrayToString(result_t); pattern_lenght = StringFind(time,pattern); pattern_lenght += StringLen(pattern); end = StringFind(time,"}",pattern_lenght + 1); server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght); time_stamp = "timestamp=" + server_time; symbol = "DOGEUSDT"; sQty = DoubleToString(6, 8); StringReplace(sQty, ",", "."); side = "BUY"; params = "symbol=" + symbol + "&side=" + side + "&type=MARKET" + ""eOrderQty=" + sQty + "&" + time_stamp; message = params; secrete_key = "AbcdefGhmb91TXxKK0Ct51sjh9312345eIdLkRpYThugopIMe4EZVjaOCYabcdef";
Пояснение:
С помощью первого набора переменных подготавливается запрос к эндпоинту серверного времени Binance. Переменная URL указывает путь API Binance, который возвращает текущее время сервера. Этот эндпоинт имеет решающее значение, поскольку Binance ожидает, что метки времени будут поступать с его собственного сервера, а не из вашей локальной системы. Поскольку для этого запроса аутентификация не требуется, переменная заголовков остается пустой. Необработанный ответ сервера и необработанные данные запроса сохраняются в символьных массивах, а все заголовки, возвращенные Binance, – в переменной заголовков ответа.
Когда функция WebRequest вызывается с методом GET и URL времени сервера, запрос отправляется в Binance. В ответ Binance возвращает сообщение JSON, содержащее текущее серверное время в миллисекундах. Массив результата содержит этот ответ в виде необработанных байтов. Затем для преобразования этих необработанных байтовых данных в читаемую строку используется функция преобразования массива символов в строку. Теперь эта строка содержит полный JSON-ответ от Binance, включая серверную метку времени. Для авторизованных запросов эта метка времени теперь становится первым необходимым параметром.
Затем метка времени извлекается из ответа сервера с помощью нескольких строковых переменных. Ключу JSON, который Binance использует для серверного времени, соответствует заранее заданная строка-шаблон. Затем объявляются дополнительные переменные для отслеживания начала и конца метки времени в сообщении ответа. Извлеченное серверное время и итоговый отформатированный параметр метки времени хранятся в пустых строках.
Программа ищет шаблон, который указывает на начало поля серверного времени в строке ответа. После того как шаблон найден, к позиции добавляется его длина, чтобы извлечение начиналось именно после метки, а не с нее самой. Затем программа ищет закрывающую скобку, которая показывает, что значение серверного времени закончилось. Подстрока, извлекаемая по этим двум позициям, содержит только числовое значение метки времени. Как и в ответе Binance, это значение по-прежнему представлено в миллисекундах. После этого извлеченное значение оформляется как корректный параметр строки запроса: перед ним добавляется строка "timestamp=". Так формируется параметр метки времени, который будет использоваться в запросе к API.
Оставшиеся параметры, необходимые для размещения ордера, задаются следующим набором переменных. Для хранения торгового символа подготавливается строковая переменная. Для количества подготавливается другая строковая переменная. Одна переменная используется для объединения всех аргументов в единую строку запроса, а другая – для направления ордера. В двух дополнительных переменных хранятся итоговое сообщение, которое будет подписано, и секретный ключ, используемый для создания подписи. Переменная symbol задает торговую пару, по которой будет отправлен ордер.
Чтобы соответствовать ожидаемому Binance числовому формату, переменная количества преобразуется в строку с нужным десятичным форматом, а запятая в записи числа заменяется на точку. Переменная side задает направление ордера. Затем все необходимые поля объединяются в единую строку запроса, образуя строку параметров. Она включает количество, тип ордера, направление, символ и метку времени. Как требует спецификация API Binance, каждый аргумент присоединяется знаком "&".
После этого переменной message присваивается вся строка запроса. Это сообщение представляет весь набор параметров, который будет отправлен в Binance. Закрытый API-ключ, используемый для создания подписи, хранится в переменной секретного ключа. Теперь все необходимое для создания подписи уже подготовлено. Сообщение содержит все параметры запроса, а секретный ключ позволяет создать криптографическую подпись. Это критически важно, потому что Binance требует использовать для создания подписи всю строку запроса, а не только метку времени. Запрос не пройдет аутентификацию, если во входных данных для подписи будет отсутствовать какой-либо параметр.
Пример:uchar uMsg[], uSKey[]; int blockSize = 64; uchar ipad[], opad[]; uchar innerData[], innerHash[]; uchar outerData[], finalHash[]; string API_KEY = "abcdefkuoeX7gUvCb8nX0Ros4Jsabcdef3kA5nw8e12345udVZwWYHgOjyabcdef"; string headers = "X-MBX-APIKEY: " + API_KEY + "\r\n"; char result[]; string response_headers; char datas[]; string url; int status; string server_response;
if(ClosePrice[0] < OpenPrice[0] && ClosePrice[1] > OpenPrice[1] && ClosePrice[1] > HighPrice[0]) { // Print("BULLISH ENGULFING CONFIRMED"); //SEND A BUY ORDER TO BINANCE WebRequest("GET", url_t, headers_t, 5000, data_t, result_t, response_headers_t); time = CharArrayToString(result_t); pattern_lenght = StringFind(time,pattern); pattern_lenght += StringLen(pattern); end = StringFind(time,"}",pattern_lenght + 1); server_time = StringSubstr(time,pattern_lenght,end - pattern_lenght); time_stamp = "timestamp=" + server_time; symbol = "DOGEUSDT"; sQty = DoubleToString(6, 8); StringReplace(sQty, ",", "."); side = "BUY"; params = "symbol=" + symbol + "&side=" + side + "&type=MARKET" + ""eOrderQty=" + sQty + "&" + time_stamp; message = params; secrete_key = "AbcdefGhmb91TXxKK0Ct51sjh9312345eIdLkRpYThugopIMe4EZVjaOCYabcdef"; StringToCharArray(message, uMsg, 0, StringLen(message), CP_UTF8); StringToCharArray(secrete_key, uSKey, 0, StringLen(secrete_key), CP_UTF8); ArrayResize(ipad, blockSize); ArrayResize(opad, blockSize); for(int i = 0; i < blockSize; i++) { ipad[i] = uSKey [i] ^ 0x36; opad[i] = uSKey [i] ^ 0x5C; } ArrayCopy(innerData, ipad); ArrayCopy(innerData, uMsg, blockSize); CryptEncode(CRYPT_HASH_SHA256, innerData, uSKey, innerHash); 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; url = "https://api.binance.com/api/v3/order?" + params + "&signature=" + signature; status = WebRequest("POST", url, headers, 5000, datas, result, response_headers); server_response = CharArrayToString(result); Print(server_response); }
Пояснение:
Начальный набор переменных используется для подготовки всего необходимого для итогового запроса к API и аутентификации. Сообщение и секретный ключ сохраняются в двух массивах байтов в виде необработанных байтовых данных. Это необходимо, поскольку криптографическое хеширование работает с байтами, а не со строками. Стандартный размер блока алгоритма SHA256 для операций HMAC составляет 64 байта. Для внешнего паддинг-блока объявляется один массив, а для внутреннего – другой. Процедура HMAC включает несколько ключевых элементов. Для хранения результатов хеширования и промежуточных данных в процессе подписи создаются дополнительные массивы. Благодаря такому разделению шаги HMAC легче отслеживать.
Секретный ключ и строка API-ключа задаются по отдельности. В заголовках запроса ваш аккаунт идентифицируется только по API-ключу. Он не включается в подпись. Строка заголовков формируется в требуемом Binance формате, после чего к ней добавляется символ перевода строки. С этим заголовком будет отправлен запрос на размещение ордера. Затем для обработки ответа сервера объявляются дополнительные переменные. В их число входят переменные для итогового текста ответа сервера, массивы для raw-данных ответа и строки для заголовков ответа. Дополнительно для последующего использования подготавливаются URL, код состояния и итоговая строка ответа.
После этого из строк сообщения и секретного ключа создаются массивы байтов. Этот шаг необходим, поскольку механизм подписи работает на уровне байтов. Чтобы байтовое представление соответствовало формату, ожидаемому Binance, используется кодировка UTF-8. Теперь и секретный ключ, и аргументы строки запроса представлены в виде необработанных байтовых данных. Размеры внутренних и внешних массивов паддинга изменяются так, чтобы они соответствовали размеру блока SHA256. Затем эти массивы заполняются в цикле, который объединяет фиксированные шестнадцатеричные значения с байтами секретного ключа. Одно значение используется для внутреннего паддинг-блока, а другое – для внешнего. Это преобразование гарантирует, что секретный ключ будет безопасно встроен в процесс хеширования; кроме того, оно является частью стандарта HMAC.
Внутренние данные формируются путем копирования внутреннего паддинг-блока, после чего к нему добавляются байты сообщения. Этот объединенный набор данных представляет первый шаг вычисления HMAC. Затем из этих внутренних данных вычисляется внутренний хэш SHA256. В этом внутреннем хэше влияние секретного ключа уже надежно учтено. Затем копируется внешний паддинг-блок, и к нему добавляется результат внутреннего хеширования, образуя внешние данные. В процессе HMAC эта вторая комбинация используется как итоговые входные данные. Итоговый хэш создается путем применения к этим данным еще одного хэша SHA256. Именно этот итоговый хэш и является криптографической подписью, подтверждающей, что запрос создан с использованием соответствующего секретного ключа.
Поскольку итоговый хэш все еще представлен в виде необработанных байтов, его преобразуют в читаемую шестнадцатеричную строку. Каждый байт предварительно представляется как двухсимвольное шестнадцатеричное значение, а затем добавляется в строку. Итоговая строка имеет в точности тот формат подписи, который требуется Binance. Ее вывод может быть полезен для отладки или проверки. Когда подпись готова, она добавляется к строке запроса как дополнительный параметр.
Согласно требованиям Binance, подпись должна передаваться как часть URL запроса, а не в заголовках. Затем эндпоинт ордера Binance, ранее сформированные параметры и полученная подпись объединяются в итоговый URL запроса. С помощью метода WebRequest в Binance отправляется POST-запрос с полным подписанным URL и заголовком аутентификации. После обработки запроса Binance возвращает ответ в виде необработанных байтов.
Заключение
В этой статье рассмотрен полный процесс работы с API Binance в MQL5 – от получения и организации свечных данных до выявления паттерна бычьего поглощения и отправки аутентифицированных торговых ордеров. Мы рассмотрели, как формировать подписанные запросы, корректно обрабатывать параметры и исполнять реальные сделки с помощью функции WebRequest. Этим заключительным шагом – размещением ордеров через API Binance – статья завершает серию материалов об API и доводит до логического конца весь путь от анализа рыночных данных до исполнения сделок в MetaTrader 5.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21061
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Возможности Мастера MQL5, которые вам нужно знать (Часть 71): Использование паттернов MACD и OBV
Торговые инструменты на языке MQL5 (Часть 9): Мастер первого запуска для советников с прокручиваемым руководством
Знакомство с языком MQL5 (Часть 39): Руководство для начинающих по работе с файлами в MQL5 (I)
Искусство работы с логами (Часть 8): Самопереводящиеся записи об ошибках
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования