English Español Deutsch 日本語 Português
preview
Торговый инструментарий MQL5 (Часть 1): Разработка EX5-библиотеки для управления позициями

Торговый инструментарий MQL5 (Часть 1): Разработка EX5-библиотеки для управления позициями

MetaTrader 5Примеры | 13 ноября 2024, 11:51
200 0
Kelvin Muturi Muigua
Kelvin Muturi Muigua

Введение

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

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


Что такое библиотеки кода в MQL5?

Библиотеки кода MQL5 представляют собой предварительно написанные функции кода (ex5) или динамически подключаемые библиотеки (DLL), которые мы можем использовать для ускорения разработки советников, индикаторов, скриптов или сервисов для платформы MetaTrader 5. 

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


Типы библиотек кода в MQL5

Будучи MQL5-разработчиком, вы можете создать свою библиотеку кода или набор инструментов несколькими способами:

  • Библиотеки функций (ex5): Эти библиотеки реализуют процедурный стиль разработки, предлагая набор предварительно написанных функций для конкретных задач. Они также обеспечивают дополнительную безопасность за счет инкапсуляции кода. Их можно рассматривать как отдельные инструменты, каждый из которых предназначен для определенной работы.
  • Сторонние DLL C++: Вы можете интегрировать предварительно написанные библиотеки C++ как DLL (Dynamic Linked Libraries, динамически подключаемые библиотеки). Это расширяет возможности MQL5, позволяя использовать внешние функции.

MetaTrader 5 также предлагает дополнительные способы расширить ваш инструментарий:

  • Библиотеки .NET: MetaEditor обеспечивает бесшовную интеграцию с библиотеками .NET посредством "умного" импорта функций, устраняя необходимость в пользовательских обертках.
  • Модуль языка Python: Новый поддерживаемый модуль языка Python позволяет использовать функциональные возможности Python в ваших проектах MQL5.

Если вы хорошо разбираетесь в C++, вы можете создавать собственные DLL-библиотеки, которые легко интегрируются в ваши MQL5-проекты. Вы можете использовать такие инструменты, как Microsoft Visual Studio, для разработки файлов исходного кода C++ (CPP и H), компилировать их в библиотеки DLL, а затем импортировать их в MetaEditor для использования с вашим MQL5-кодом.

Другие ресурсы, похожие на библиотеки MQL5

  • Class/Include(*.mqh): Включаемые файлы MQL5 используют объектно-ориентированное программирование, предлагая готовые классы, инкапсулирующие данные и функционал. Это более сложные инструменты, объединяющие функции/методы и структуры данных. Класс или структуру нельзя экспортировать для создания библиотеки MQL5 (ex5), но можно использовать указатели и ссылки на классы или структуры в функциях библиотеки MQL5.


Для чего может понадобиться MQL5-библиотека (ex5)?

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

Простое повторное использование и модульная конструкция

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

Безопасные функции с инкапсуляцией

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

Простые обновления и долгосрочные преимущества

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


Как создать ex5-библиотеку в MQL5?

Все .ex5-библиотеки начинаются с файлов исходного кода .mq5 с директивой #property, добавленной в начало файла, и одной или нескольких функций, обозначенных как экспортируемые с помощью специального ключевого слова export. Файл исходного кода библиотеки .mq5 преобразуется в файл библиотеки .ex5 после компиляции, надежно инкапсулируя исходный код и делая его готовым к импорту и использованию в других MQL5-проектах.

Создать новую библиотеку MQL5 легко с помощью MetaEditor IDE. Для создания нового файл исходного кода библиотеки (.mq5), который будет содержать функции управления позициями и позже будет скомпилирован в библиотеку .ex5, выполните следующее.

Шаг 1: Откройте MetaEditor IDE и запустите Мастер MQL с помощью команды "Создать".

Мастер MQL5 - новый файл библиотеки

Шаг 2: Выберите "Библиотека" и нажмите "Далее".

Мастер MQL5 - новый файл библиотеки

Шаг 3: В общих параметрах файла библиотеки, укажите папку и имя для вашей новой библиотеки Libraries\Toolkit\PositionsManager и нажмите "Готово" для создания новой библиотеки.

Мастер MQL5 - общие свойства нового файла библиотеки

Библиотека MQL5 обычно хранится в файле с расширением .mq5. Файл содержит исходный код различных функций, написанных для различных конкретных задач. Библиотеки кодов по умолчанию хранятся в папке MQL5\Libraries в каталоге установки MetaTrader 5. Для быстрого доступа к папке Libraries используйте панель Навигатора в MetaEditor.

Папка Libraries в панели Навигатора MetaEditor

Теперь у нас есть файл новой пустой библиотеки MQL5 PositionsManager.mq5, содержащий главные директивы свойств и закомментированную функцию. Не забудьте сохранить новый файл, прежде чем продолжить. Вот как выглядит наш новый файл библиотеки:

//+------------------------------------------------------------------+
//|                                             PositionsManager.mq5 |
//|                          Copyright 2024, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property library
#property copyright "Copyright 2024, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| My function                                                      |
//+------------------------------------------------------------------+
// int MyCalculator(int value,int value2) export
//   {
//    return(value+value2);
//   }
//+------------------------------------------------------------------+

Компоненты файла исходного кода библиотеки MQL5

Файл исходного кода библиотеки (.mq5) состоит из двух основных компонентов:

1. Директива #property: Директива должна быть добавлена в начало файла исходного кода .mq5 библиотеки. Свойство library сообщает компилятору, что данный файл является библиотекой. Указание на это сохраняется в заголовке скомпилированной библиотеки в файле .ex5, сгенерированном при компиляции библиотеки .mq5.

#property library

2. Экспортируемые функции: В основе библиотек MQL5 лежат экспортируемые функции. Это основные компоненты библиотеки, поскольку они отвечают за выполнение всей трудоемкой работы по задачам, которые должна выполнять библиотека. Экспортируемая функция MQL5 похожа на обычную за исключением того, что она объявлена с помощью постмодификатора export, что позволяет импортировать его и использовать в других MQL5-программах после компиляции. Модификатор export дает команду компилятору добавить указанную функцию в таблицу ex5-функций, экспортированных этим библиотечным файлом. Только функции, объявленные с помощью модификатора export доступны и обнаруживаются из других mql5-программ, где их можно вызвать после импорта с помощью специальной директивы #import.

//+------------------------------------------------------------------+
//| Example of an exported function with the export postmodifier     |
//+------------------------------------------------------------------+
int ExportedFunction(int a, int b) export
  {
   return(a + b);
  }
//+------------------------------------------------------------------+

Библиотеки MQL5 не предназначены для стандартной обработки событий, связанных с работой советников, пользовательских индикаторов или скриптов. Это означает, что у них нет таких стандартных функций, как OnInit(), OnDeinit() или OnTick().


Библиотека функций MQL5 для управления позициями

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

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


Глобальные переменные


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

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

//---Global variables
//-------------------------------
//-- Trade operations request and result data structures
MqlTradeRequest tradeRequest;
MqlTradeResult  tradeResult;
//-------------------------------


Функция управления ошибками позиций


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

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

Код Кодовая константа  Описание
Требуемое действие Тип кода 
10004  TRADE_RETCODE_REQUOTE Реквотирование Повторно отправьте запрос на ордер. Код возврата торгового сервера (RETCODE)
10008  TRADE_RETCODE_PLACED Ордер размещен Действие не требуется. Операция успешна. Код возврата торгового сервера (RETCODE)
10009  TRADE_RETCODE_DONE Запрос выполнен Действие не требуется. Операция завершена.
Код возврата торгового сервера (RETCODE)
10013  TRADE_RETCODE_INVALID Неверный запрос Прекратить повторную отправку запроса на инициализацию ордера и обновить данные ордера. Код возврата торгового сервера (RETCODE)
10014  TRADE_RETCODE_INVALID_VOLUME Неверный объем в запросе Прекратить повторную отправку запроса на инициализацию ордера и обновить данные ордера. Код возврата торгового сервера (RETCODE)
10016  TRADE_RETCODE_INVALID_STOPS Неверные стоп-уровне в запросе Прекратить повторную отправку запроса на инициализацию ордера и обновить данные ордера. Код возврата торгового сервера (RETCODE)
10017  TRADE_RETCODE_TRADE_DISABLED Торговля запрещена Прекратить все торговые операции, а также повторную отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10018  TRADE_RETCODE_MARKET_CLOSED Рынок закрыт. Прекратить повторную отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10019  TRADE_RETCODE_NO_MONEY Недостаточно средств для выполнения запроса Прекратить повторную отправку запроса на инициализацию ордера и обновить данные ордера. Код возврата торгового сервера (RETCODE)
10026  TRADE_RETCODE_SERVER_DISABLES_AT Автотрейдинг запрещен сервером Торговля запрещена сервером. Прекратить повторную отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10027  TRADE_RETCODE_CLIENT_DISABLES_AT Автотрейдинг запрещен клиентским терминалом Клиентский терминал отключил торговлю советниками. Прекратить повторную отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10034  TRADE_RETCODE_LIMIT_VOLUME Достигнут лимит на объем ордеров и позиций для данного символа Прекратить повторную отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10011  TRADE_RETCODE_ERROR Request processing error (ошибка обработки запроса) Продолжать отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10012  TRADE_RETCODE_TIMEOUT Request canceled by timeout (запрос отменен по таймауту) Приостановить выполнение на несколько миллисекунд, а затем продолжить отправку запроса на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10015  TRADE_RETCODE_INVALID_PRICE Неверная цена в запросе Обновить цену ордера и повторно отправить запрос на его инициализацию. Код возврата торгового сервера (RETCODE)
10020  TRADE_RETCODE_PRICE_CHANGED Цены изменились Обновить цену ордера и повторно отправить запрос на его инициализацию. Код возврата торгового сервера (RETCODE)
10021  TRADE_RETCODE_PRICE_OFF Отсутствуют котировки для обработки запроса Приостановить выполнение на несколько миллисекунд, а затем повторно отправить запрос на инициализацию ордера. Код возврата торгового сервера (RETCODE)
10024  TRADE_RETCODE_TOO_MANY_REQUESTS Слишком частые запросы Приостановить выполнение на несколько секунд, а затем повторно отправить запрос на инициализацию ордера. Код возврата торгового сервера (RETCODE)
 10031  TRADE_RETCODE_CONNECTION Нет соединения с торговым сервером Приостановить выполнение на несколько миллисекунд, а затем повторно отправить запрос на инициализацию ордера. Код возврата торгового сервера (RETCODE)
 0  ERR_SUCCESS Операция выполнена успешно Прекратить повторную отправку запроса. Ордер успешно отправлен.   Код ошибки выполнения
 4752  ERR_TRADE_DISABLED Торговля советниками запрещена Прекратить повторную отправку запроса на инициализацию ордера. Код ошибки выполнения
 4753  ERR_TRADE_POSITION_NOT_FOUND Позиция не найдена Прекратить повторную отправку запроса на торговую операцию.
Код ошибки выполнения
 4754  ERR_TRADE_ORDER_NOT_FOUND Ордер на найден Прекратить повторную отправку запроса на ордер.
Код ошибки выполнения
 4755  ERR_TRADE_DEAL_NOT_FOUND Сделка не найдена Прекратить повторную отправку запроса на ордер.
Код ошибки выполнения


Создадим нашу первую функцию в библиотеке для обработки ошибок, упомянутых выше. Функция обработки ошибок ErrorAdvisor() будет логического типа, то есть она будет возвращать True или False в зависимости от типа обнаруженной ошибки. Для помощи в обработке данных потребуются два аргумента:

  1. callingFunc (string) - параметр хранит имя или идентификатор функции, которая вызывает ErrorAdvisor().
  2. symbol (string) - параметр хранит имя символа обрабатываемого актива.
  3. tradeServerErrorCode (integer) - параметр хранит тип обнаруженной ошибки.

Если ошибка устранима и не критична, функция ErrorAdvisor() возвращает True. Это указывает вызывающей функции, что ордер еще не выполнен и ей следует повторно отправить запрос на ордер. Если ErrorAdvisor() возвращает False, это означает, что вызывающая функция должна прекратить отправку дальнейших запросов на ордер, поскольку ордер уже был успешно выполнен или возникла критическая неустранимая ошибка.

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

//------------------------------------------------------------------+
// ErrorAdvisor(): Error analysis and processing function.          |
// Returns true if order opening failed and order can be re-sent    |
// Returns false if the error is critical and can not be executed   |
//------------------------------------------------------------------+
bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode)  export
  {
//-- place the function body here
  }

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

//-- save the current runtime error code
   int runtimeErrorCode = GetLastError();

Просканируем и обработаем ошибки возврата торгового сервера (retcode), а также ошибки выполнения, используя вложенные операторы switch. Это удобно, поскольку позволяет быстро определить тип ошибки, вывести описание для пользователя в журнал советника и указать вызывающей функции, как действовать дальше. Я сгруппировал ошибки в две категории:

  1. Ошибки, указывающие на завершение или невыполнение ордера: Эти ошибки будут возвращать False, давая вызывающей функции команду прекратить отправку запросов на ордер.
  2. Ошибки, указывающие на невыполненные ордера: Эти ошибки будут возвращать True, давая вызывающей функции команду повторно отправить запрос на ордер.

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

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

switch(tradeServerErrorCode)//-- check for trade server errors
     {
        //--- Cases to scan different retcodes/server return codes
     }

Внутри оператора switch мы добавим случаи для различных кодов ошибок, возвращаемых торговым сервером. Вот некоторые из них:

Requote (code 10004): Цена изменилась с тех пор, как пользователь попытался открыть ордер. В этом случае нам нужно вывести сообщение в журнал, подождать несколько миллисекунд, используя функцию Sleep(), а затем сообщить вызывающей функции о необходимости повторной попытки открытия ордера.

case 10004:
    Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!");
    Sleep(10);
    return(true);    //--- Exit the function and retry opening the order again

Order Placed Successfully (code 10008): Ордер успешно размещен. Мы можем вывести в журнал сообщение о том, что ордер выполнен успешно, а затем указать вызывающей функции прекратить попытки открыть ордер.

case 10008:
    Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!");
    return(false);    //--- success - order placed ok. exit function

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

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

default:
  switch(runtimeErrorCode)//-- check for runtime errors
    //-- Add cases for different runtime errors here
  }
}

Аналогично ошибкам торгового сервера добавим случаи для различных кодов ошибок выполнения:

No Errors (code 0): С точке зрения программы всё прошло успешно. Мы можем вывести в журнал сообщение об отсутствии ошибок, а затем сообщить вызывающей функции, что ей следует прекратить попытки.

case 0:
    Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!");
    ResetLastError(); //--- reset error cache
    return(false);    //--- Exit the function and stop trying to open order

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

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

default: //--- All other error codes
    Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode);
    ResetLastError(); //--- reset error cache
    return(false);    //--- Exit the function and stop trying to open order
    break;

Ниже представлена полная функция управления ошибками ErrorAdvisor() со всеми завершенными сегментами кода:

bool ErrorAdvisor(string callingFunc, string symbol, int tradeServerErrorCode)  export
  {
//-- save the current runtime error code
   int runtimeErrorCode = GetLastError();

   switch(tradeServerErrorCode)//-- check for trade server errors
     {
      case 10004:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Requote!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again
      case 10008:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Order placed!");
         return(false);    //--- success - order placed ok. exit function

      case 10009:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request completed!");
         return(false);    //--- success - order placed ok. exit function

      case 10011:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request processing error!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10012:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Request canceled by timeout!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      case 10015:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Invalid price in the request!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10020:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Prices changed!");
         Sleep(10);
         return(true);    //--- Exit the function and retry opening the order again

      case 10021:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") There are no quotes to process the request!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      case 10024:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") Too frequent requests!");
         Sleep(1000);
         return(true);    //--- Exit the function and retry opening the order again

      case 10031:
         Print(symbol, " - ", callingFunc, " ->(TradeServer_Code: ", tradeServerErrorCode, ") No connection with the trade server!");
         Sleep(100);
         return(true);    //--- Exit the function and retry opening the order again

      default:
         switch(runtimeErrorCode)//-- check for runtime errors
            case 0:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") The operation completed successfully!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4752:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Trading by Expert Advisors prohibited!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4753:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Position not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4754:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Order not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            case 4755:
               Print(symbol, " - ", callingFunc, " ->(Runtime_Code: ", runtimeErrorCode, ") Deal not found!");
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order

            default: //--- All other error codes
               Print(symbol, " - ", callingFunc, " *OTHER* Error occurred \r\nTrade Server RetCode: ", tradeServerErrorCode, ", Runtime Error Code = ", runtimeErrorCode);
               ResetLastError(); //--- reset error cache
               return(false);    //--- Exit the function and stop trying to open order
               break;
           }
     }
  }


Функция разрешений на торговлю


Функция проверяет, разрешена ли в данный момент торговля в терминале. Учитывается авторизация пользователя, торгового сервера и брокера. Функция вызывается до того, как какие-либо операции по позициям или запросы на ордера будут отправлены для обработки на торговый сервер.

Назовем функцию TradingIsAllowed() и присвоим ей тип возвращаемого значения boolean. Если торговля разрешена и включена, она вернет логическое значение True и логическое значение False, если автоматическая торговля отключена или запрещена. Функция не будет иметь никаких параметров или аргументов и будет содержать следующий сегмент кода:

//+-----------------------------------------------------------------------+
//| TradingIsAllowed() verifies whether auto-trading is currently allowed |                                                                 |
//+-----------------------------------------------------------------------+
bool TradingIsAllowed() export
  {
   if(
      !IsStopped() &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
   )
     {
      return(true);//-- trading is allowed, exit and return true
     }
   return(false);//-- trading is not allowed, exit and return false
  }


Функция записи и отображения деталей ордера


Это простая функция для записи и отображения свойств различных торговых операций или запросов на открытие позиций. Она позволяет пользователю советника простой способ быть в курсе состояния операций советника, отображая информацию на вкладке "Эксперты" советника MetaTrader 5. Вы заметите, что эта функция не экспортируется, и поэтому она доступна только через другие экспортированные функции, которые явно вызывают или выполняют ее. Назовем ее PrintOrderDetails() и указать, что она не будет возвращать никаких данных (что делает ее функцией типа void) и будет принимать одну строковую переменную в качестве входного параметра или аргумента.

//+-----------------------------------------------------------------------+
//| PrintOrderDetails() prints the order details for the EA log           |
//+-----------------------------------------------------------------------+
void PrintOrderDetails(string header)
  {
   string orderDescription;
//-- Print the order details
   orderDescription += "_______________________________________________________________________________________\r\n";
   orderDescription += "--> "  + tradeRequest.symbol + " " + EnumToString(tradeRequest.type) + " " + header +
                       " <--\r\n";
   orderDescription += "Order ticket: " + (string)tradeRequest.order + "\r\n";
   orderDescription += "Volume: " + StringFormat("%G", tradeRequest.volume) + "\r\n";
   orderDescription += "Price: " + StringFormat("%G", tradeRequest.price) + "\r\n";
   orderDescription += "Stop Loss: " + StringFormat("%G", tradeRequest.sl) + "\r\n";
   orderDescription += "Take Profit: " + StringFormat("%G", tradeRequest.tp) + "\r\n";
   orderDescription += "Comment: " + tradeRequest.comment + "\r\n";
   orderDescription += "Magic Number: " + StringFormat("%d", tradeRequest.magic) + "\r\n";
   orderDescription += "Order filling: " + EnumToString(tradeRequest.type_filling)+ "\r\n";
   orderDescription += "Deviation points: " + StringFormat("%G", tradeRequest.deviation) + "\r\n";
   orderDescription += "RETCODE: " + (string)(tradeResult.retcode) + "\r\n";
   orderDescription += "Runtime Code: " + (string)(GetLastError()) + "\r\n";
   orderDescription += "---";
   Print(orderDescription);
  }


Функции для открытия позиций


Сгруппируем эти функции в две категории:

  1. Функция OpenBuyPositions() отвечает за открытие новых позиций на покупку.
  2. Функция OpenSellPositions() отвечает за открытие новых позиций на продажу.

Функция OpenBuyPositions()

Функция имеет тип bool и возвращает значение true при успешном открытии новой сделки на покупку и false, если сделку открыть не удалось. Он принимает шесть параметров или аргументов:

  1. ulong magicNumber - используется для сохранения магического числа советника для упрощения изменения или удаления позиции с простой фильтрацией.
  2. string symbol - сохраняет имя символа или актива, для которого выполняется запрос.
  3. double lotSize - сохраняет объем открываемой позиции на покупку.
  4. int sl - сохраняет значение стоп-лосса в пунктах/пипсах позиции на покупку.
  5. int tp - сохраняет значение тейк-профита в пунктах/пипсах позиции на покупку.
  6. string positionComment - используется для хранения комментария к позиции на покупку.

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

bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
        //--- Function body
  }

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

Если TradingIsAllowed() возвращает false, это означает, что торговля отключена и наш советник не может открывать ордера. В этом случае мы немедленно вернем false из этой функции и выйдем из нее, не открывая новый ордер на покупку.

//-- first check if the ea is allowed to trade
if(!TradingIsAllowed())
  {
   return(false); //--- algo trading is disabled, exit function
  }

Следующим шагом станет подготовка к запросу ордера путем удаления всех оставшихся данных от предыдущих попыток торговли. Для этого воспользуемся функцией ZeroMemory() в двух глобальных структурах данных о сделках, которые мы создали в начале нашего - tradeRequest и tradeResult. Они будут хранить данные ордера на покупку, который мы хотим открыть, и результаты, возвращаемые торговым сервером, соответственно.

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

Теперь давайте зададим параметры открытия позиции на покупку в переменной структуры данных tradeRequest:

  • tradeRequest.type: Установите на ORDER_TYPE_BUY для обозначения ордера на покупку.
  • tradeRequest.action: Установите на TRADE_ACTION_DEAL для указание на открытие новой позиции.
  • tradeRequest.magic: Назначим magicNumber, приведенный здесь в качестве аргумента. Это помогает идентифицировать ордера, открытые нашим советником.
  • tradeRequest.symbol: Назначим символ, указанный здесь в качестве аргумента, указав валютную пару для торговли.
  • tradeRequest.tp и tradeRequest.sl: Пока установим их на 0, стоп-уровнями займемся позже.
  • tradeRequest.comment: Установим positionComment, указанный здесь в качестве аргумента, который можно использовать для добавления текстового комментария к ордеру.
  • tradeRequest.deviation: Параметр разрешает отклонение в два раза больше текущего спреда для торгуемого символа. Это дает платформе определенную гибкость в поиске подходящей цены ордера и ограничивает реквоты ордеров.

//-- initialize the parameters to open a buy position
   tradeRequest.type = ORDER_TYPE_BUY;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

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

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

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

//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   ResetLastError();

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

for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries)
   {
    //--- Place the order request code to open a new buy position
   }

Первой задачей в цикле for будет обновление цены открытия ордера на каждой итерации в случае реквота цены ордера. Мы используем SymbolInfoDouble(symbol, SYMBOL_ASK), чтобы получить текущую цену Ask для символа и назначить ее tradeRequest.price. Это гарантирует, что наш запрос на ордер отражает последнюю рыночную цену.

//--- update order opening price on each iteration
   tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);

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

//-- set the take profit and stop loss on each iteration
   if(tp > 0)
     {
      tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
     }
   if(sl > 0)
     {
      tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
     }

Далее мы отправим ордер на торговый сервер для исполнения. Для этой задачи мы будем использовать функцию OrderSend(), передавая подготовленный tradeRequest и пустую переменную tradeResult в качестве аргументов или параметров. Функция пытается открыть ордер на основе спецификаций, хранящихся в tradeRequest. Результаты, включая коды успеха или ошибок, будут сохранены в переменной tradeResult после завершения выполнения функции.

Оператор if, который мы разместили для функции OrderSend(), позволит нам проверить и подтвердить, был ли запрос на ордер успешным или нет. Если OrderSend() возвращает true, это означает, что запрос на ордер был успешно отправлен. false означает, что запрос не выполнен.

Затем мы вызываем нашу ранее введенную функцию PrintOrderDetails() с сообщением Sent OK для регистрации этой информации в журнале советника.

Также проверим tradeResult.retcode, чтобы подтвердить успешное выполнение ордера. Возвращаем true из функции OpenBuyPosition() в случае успеха и используем break для выхода из цикла. Если OrderSend() возвращает false (запрос на ордер не выполнен), это означает, что возникла проблема. Вызовем PrintOrderDetails() с сообщением Sending Failed, чтобы записать эту информацию. Также выведем сообщение об ошибке, чтобы выделить различные коды ошибок, с которыми мы столкнулись, вернем false из функции OpenBuyPosition(), чтобы проинформировать о неуспехе и используем break для выхода из цикла.

//--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit the function
            break; //--- success - order placed ok. exit the for loop
           }
        }
      else //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit the function
            break; //-- exit the for loop
           }
        }

Ниже представлена OpenBuyPosition() со всеми сегментами кода в правильной последовательности:

//-------------------------------------------------------------------+
// OpenBuyPosition(): Function to open a new buy entry order.        |
//+------------------------------------------------------------------+
bool OpenBuyPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
//-- first check if the ea is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to open a buy position
   tradeRequest.type = ORDER_TYPE_BUY;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

//--- Reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   ResetLastError();

   for(int loop = 0; loop <= 100; loop++) //-- try opening the order untill it is successful (100 max tries)
     {
      //--- update order opening price on each iteration
      tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_ASK);

      //-- set the take profit and stop loss on each iteration
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }

      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, ": CONFIRMED: Successfully openend a ", symbol, " BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit the function
            break; //--- success - order placed ok. exit the for loop
           }
        }
      else //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, ": ", symbol, " ERROR opening a BUY POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit the function
            break; //-- exit the for loop
           }
        }
     }
   return(false);
  }


Функция OpenSellPositios()

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

//-------------------------------------------------------------------+
// OpenSellPosition(): Function to open a new sell entry order.      |
//+------------------------------------------------------------------+
bool OpenSellPosition(ulong magicNumber, string symbol, double lotSize, int sl, int tp, string positionComment) export
  {
//-- first check if the ea is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to open a sell position
   tradeRequest.type = ORDER_TYPE_SELL;
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.magic = magicNumber;
   tradeRequest.symbol = symbol;
   tradeRequest.tp = 0;
   tradeRequest.sl = 0;
   tradeRequest.comment = positionComment;
   tradeRequest.deviation = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * 2;

//-- set and moderate the lot size or volume
   lotSize = MathMax(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN));  //-- Verify that volume is not less than allowed minimum
   lotSize = MathMin(lotSize, SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX));  //-- Verify that volume is not more than allowed maximum
   lotSize = MathFloor(lotSize / SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP)) * SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); //-- Round down to nearest volume step
   tradeRequest.volume = lotSize;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try opening the order (101 max) times untill it is successful
     {
      //--- update order opening price on each iteration
      tradeRequest.price = SymbolInfoDouble(symbol, SYMBOL_BID);

      //-- set the take profit and stop loss on each iteration
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }

      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Print the order details
         PrintOrderDetails("Sent OK");

         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print("CONFIRMED: Successfully openend a ", symbol, " SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
            PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- Print the order details
         PrintOrderDetails("Sending Failed");

         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, symbol, tradeResult.retcode) || IsStopped())
           {
            Print(symbol, " ERROR opening a SELL POSITION at: ", tradeRequest.price, ", Lot\\Vol: ", tradeRequest.volume);
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }


Функция изменения стоп-лосса и тейк-профита позиции


Наша следующая функция в библиотеке будет называться SetSlTpByTicket() и будет отвечать за изменение уровней стоп-лосс (SL) и тейк-профит (TP) для существующей открытой позиции, используя тикет позиции в качестве механизма фильтрации. Он принимает в качестве аргументов номер тикета позиции, желаемый SL в пипсах (пунктах) и желаемый TP в пипсах (пунктах) и пытается обновить SL и TP позиции на торговом сервере. Функция возвращает логическое значение (true или false). Если уровни стоп-лосс и тейк-профит позиции были успешно изменены, функция вернет true, в противном случае - false.

Ниже представлены аргументы функции SetSlTpByTicket():

  1. ulong positionTicket - уникальный идентификатор позиции.
  2. int sl - желаемый уровень SL в пипсах (пунктах) от цены открытия.
  3. int tp - желаемый уровень TP в пипсах (пунктах) от цены открытия.

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

bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export
  {
//-- Function body
  }

Как и в случае с другими нашими функциями, сначала проверим, разрешена ли торговля для нашего советника с помощью функции TradingIsAllowed(). Если торговля отключена, функция завершает работу и возвращает false.

//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

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

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP.");
     }
   else
     {
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

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

//-- create variables to store the calculated tp and sl prices to send to the trade server
   double tpPrice = 0.0, slPrice = 0.0;
   double newTpPrice = 0.0, newSlPrice = 0.0;

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double volume = PositionGetDouble(POSITION_VOLUME);
   double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

Нам также понадобится некоторая информация, специфичная для символа:

//-- Get some information about the positions symbol
   int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places
   int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);

Теперь давайте рассчитаем новые цены стоп-лосс (SL) и тейк-профит (TP) на основе цены открытия позиции, желаемого SL/TP в пипсах (пунктах) и типа позиции (покупка или продажа). Сохраним эти первоначальные расчеты перед их проверкой:

//--Save the non-validated tp and sl prices
   if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices
     {
      newSlPrice = entryPrice - (sl * symbolPoint);
      newTpPrice = entryPrice + (tp * symbolPoint);
     }
   else  //-- SELL POSITION
     {
      newSlPrice = entryPrice + (sl * symbolPoint);
      newTpPrice = entryPrice - (tp * symbolPoint);
     }

Далее печатаем сводку данных о позиции, которые мы собрали ранее:

//-- Print position properties before modification
   string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" +
   " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New Proposed SL: " + (string)newSlPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New Proposed TP: " + (string)newTpPrice + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);

Значения, предоставленные пользователем библиотеки для SL и TP, могут быть непригодны для непосредственного использования функцией OrderSend(). Прежде чем продолжить, нам нужно выполнить простую проверку их значений:

//-- validate the sl and tp to a proper double that can be used in the OrderSend() function
   if(sl == 0)
     {
      slPrice = 0.0;
     }
   if(tp == 0)
     {
      tpPrice = 0.0;
     }

Теперь нам необходимо выполнить более сложную проверку на основе сохраненных ранее данных символа. Сгруппируем логику проверки в две группы: одну для позиций на покупку, а другую — для позиций на продажу. Проверка SL и TP будет основана на текущей цене символа, ограничениях минимального уровня стопа символа и спреде символа.

Если указанная цена TP или SL недействительна и выходит за пределы требуемого диапазона, будет сохранена исходная цена TP или SL, а также будет выведено сообщение с объяснением причины сбоя изменения. После проверки значений SL и TP распечатаем еще одну сводку, чтобы зарегистрировать подтвержденные и проверенные значения:

//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice
   if(positionType == POSITION_TYPE_BUY)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice + (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice - (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP is valid
         tp > 0 &&
         (
            newTpPrice <= entryPrice + (spread * symbolPoint) ||
            newTpPrice <= positionPriceCurrent ||
            (
               newTpPrice - entryPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice >= positionPriceCurrent ||
            entryPrice - newSlPrice < symbolStopLevel * symbolPoint ||
            positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }
   if(positionType == POSITION_TYPE_SELL)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice - (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice + (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP price is valid
         tp > 0 &&
         (
            newTpPrice >= entryPrice - (spread * symbolPoint) ||
            newTpPrice >= positionPriceCurrent ||
            (
               entryPrice - newTpPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice <= positionPriceCurrent ||
            newSlPrice - entryPrice < symbolStopLevel * symbolPoint ||
            newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }

//-- Print verified position properties before modification
   positionProperties = "---\r\n";
   positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New SL: " + (string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New TP: " + (string)tpPrice + "\r\n";
   Print(positionProperties);

Теперь, когда у нас есть проверенные значения SL и TP, пришло время отправить запрос на торговый сервер для их изменения. Мы используем функцию ZeroMemory() для очистки структур tradeRequest и tradeResult, чтобы убедиться, что они не содержат остаточных данных от предыдущих операций. Затем инициализируем структуру tradeRequest со следующей информацией:

  • action - установим TRADE_ACTION_SLTP для изменения стоп-уровней.
  • position - установим positionTicket, чтобы указать позицию, над которой мы работаем.
  • symbol - установим positionSymbol, чтобы определить символ для позиции.
  • sl - установим slPrice, который содержит проверенное значение стоп-лосса.
  • tp - установим tpPrice, который содержит проверенное значение тейк-профита.

Далее вызовем функцию ResetLastError() для удаления всех предыдущих кодов ошибок, сохраненных внутри. Это гарантирует получение нами точных кодов ошибок при отправке ордеров.

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

Мы готовы отправить ордер на торговый сервер. Однако мой опыт подсказывает, что иногда исполнение ордеров может не выполняться из-за временных проблем с сетью или перегрузки сервера. Это значит, что нам нужно найти изящный способ обработки отправки ордера с повторными попытками. Используем цикл for, который повторяется до 101 раза (loop <= 100). Этот механизм повторных попыток помогает справиться с возможными временными ошибками во время исполнения ордеров.

Внутри цикла for используем OrderSend() для отправки запроса на ордер, содержащийся в tradeRequest, и сохраним результат в tradeResult. Если OrderSend() возвращает true, это означает, что цены SL и TP были успешно изменены и запрос на ордер был выполнен без каких-либо проблем.

Также сделаем окончательное подтверждение, проверив tradeResult.retcode на определенные коды (10008 или 10009), которые указывают на успешную модификацию SL/TP для этой позиции. Если коды совпадают, мы печатаем подтверждающее сообщение с такими данными, как номер позиции, символ и коды возврата. Затем используем return(true) для успешного выхода из функции. Оператор break выходит из цикла. Таким образом мы можем быть полностью уверенными в выходе из цикла for и избежать ненужных итераций. Если OrderSend() возвращает false или retcode не совпадает с кодами успешного выполнения, это указывает на ошибку.

//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = tpPrice;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }

Ниже представлена функция SetSlTpByTicket() со всеми сегментами кода в правильной последовательности. Убедитесь, что ваша функция содержит все компоненты кода ниже:

bool SetSlTpByTicket(ulong positionTicket, int sl, int tp) export
  {
//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to set SLTP.");
     }
   else
     {
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

//-- create variables to store the calculated tp and sl prices to send to the trade server
   double tpPrice = 0.0, slPrice = 0.0;
   double newTpPrice = 0.0, newSlPrice = 0.0;

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double volume = PositionGetDouble(POSITION_VOLUME);
   double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Get some information about the positions symbol
   int symbolDigits = (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); //-- Number of symbol decimal places
   int symbolStopLevel = (int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   int spread = (int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);

//--Save the non-validated tp and sl prices
   if(positionType == POSITION_TYPE_BUY) //-- Calculate and store the non-validated sl and tp prices
     {
      newSlPrice = entryPrice - (sl * symbolPoint);
      newTpPrice = entryPrice + (tp * symbolPoint);
     }
   else  //-- SELL POSITION
     {
      newSlPrice = entryPrice + (sl * symbolPoint);
      newTpPrice = entryPrice - (tp * symbolPoint);
     }

//-- Print position properties before modification
   string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " SLTP Modification Details" +
   " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New Proposed SL: " + (string)newSlPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New Proposed TP: " + (string)newTpPrice + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);

//-- validate the sl and tp to a proper double that can be used in the OrderSend() function
   if(sl == 0)
     {
      slPrice = 0.0;
     }
   if(tp == 0)
     {
      tpPrice = 0.0;
     }

//--- Check if the sl and tp are valid in relation to the current price and set the tpPrice
   if(positionType == POSITION_TYPE_BUY)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice + (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice - (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP is valid
         tp > 0 &&
         (
            newTpPrice <= entryPrice + (spread * symbolPoint) ||
            newTpPrice <= positionPriceCurrent ||
            (
               newTpPrice - entryPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent > entryPrice && newTpPrice - positionPriceCurrent < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice >= positionPriceCurrent ||
            entryPrice - newSlPrice < symbolStopLevel * symbolPoint ||
            positionPriceCurrent - newSlPrice < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }
   if(positionType == POSITION_TYPE_SELL)
     {
      //-- calculate the new sl and tp prices
      newTpPrice = 0.0;
      newSlPrice = 0.0;
      if(tp > 0)
        {
         newTpPrice = entryPrice - (tp * symbolPoint);
        }
      if(sl > 0)
        {
         newSlPrice = entryPrice + (sl * symbolPoint);
        }

      //-- save the new sl and tp prices incase they don't change afte validation below
      tpPrice = newTpPrice;
      slPrice = newSlPrice;

      if( //-- Check if specified TP price is valid
         tp > 0 &&
         (
            newTpPrice >= entryPrice - (spread * symbolPoint) ||
            newTpPrice >= positionPriceCurrent ||
            (
               entryPrice - newTpPrice < symbolStopLevel * symbolPoint ||
               (positionPriceCurrent < entryPrice && positionPriceCurrent - newTpPrice < symbolStopLevel * symbolPoint)
            )
         )
      )
        {
         //-- Specified TP price is invalid, don't modify the TP
         Print(
            "Specified proposed ", positionSymbol,
            " TP Price at ", newTpPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent TP at ", StringFormat("%G", currentPositionTpPrice), " will not be changed!"
         );
         tpPrice = currentPositionTpPrice;
        }

      if( //-- Check if specified SL price is valid
         sl > 0 &&
         (
            newSlPrice <= positionPriceCurrent ||
            newSlPrice - entryPrice < symbolStopLevel * symbolPoint ||
            newSlPrice - positionPriceCurrent < symbolStopLevel * symbolPoint
         )
      )
        {
         //-- Specified SL price is invalid, don't modify the SL
         Print(
            "Specified proposed ", positionSymbol,
            " SL Price at ", newSlPrice,
            " is invalid since current ", positionSymbol, " price is at ", positionPriceCurrent,
            "\r\nCurrent SL at ", StringFormat("%G", currentPositionSlPrice), " will not be changed!"
         );
         slPrice = currentPositionSlPrice;
        }
     }

//-- Print verified position properties before modification
   positionProperties = "---\r\n";
   positionProperties += "--> Validated and Confirmed SL and TP: <--\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + ", Price Current: " + StringFormat("%G", positionPriceCurrent) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "   -> New SL: " + (string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "   -> New TP: " + (string)tpPrice + "\r\n";
   Print(positionProperties);

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; //-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = tpPrice;

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try modifying the sl and tp 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            PrintFormat("Successfully modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- Order request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            PrintFormat("ERROR modified SLTP for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________\r\n\r\n");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }


Функция закрытия позиции


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

Сначала мы определяем функцию и указываем, что она будет возвращать логическое значение (true или false) и принимать один параметр в качестве аргумента.

bool ClosePositionByTicket(ulong positionTicket) export
  {
//--- Function body
  }

Затем проверим, разрешено ли советнику торговать.

//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

Далее сбрасываем все предыдущие ошибки с помощью ResetLastError() и выбираем позицию, используя предоставленный номер тикета. Если позиция выбрана, печатаем сообщение, подтверждающее выбор. Если же выбор не удался, печатаем сообщение об ошибке и выходим из функции, возвращая false.

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed.");
     }
   else
     {
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

После успешного выбора позиции сохраняем ее свойства и отображаем их.

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double positionVolume = PositionGetDouble(POSITION_VOLUME);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Print position properties before closing it
   string positionProperties;
   positionProperties += "-- "  + positionSymbol + " " + EnumToString(positionType) + " Details" +
   " -------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n";
   positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n";
   positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "_______________________________________________________________________________________";
   Print(positionProperties);

Затем сбрасываем значения структуры данных tradeRequest и tradeResult, используя функцию ZeroMemory() для очистки в них всех предыдущих данных. Затем инициализируем параметры торгового запроса для закрытия позиции, установив торговое действие на TRADE_ACTION_DEAL для обозначения операции по прекращению торговли, тикета позиции, символа, объема и отклонения цены.

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the trade reqiest parameters to close the position
   tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.volume = positionVolume;
   tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;

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

//--- Set the price and order type of the position being closed
   if(positionType == POSITION_TYPE_BUY)
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
      tradeRequest.type = ORDER_TYPE_SELL;
     }
   else//--- For sell type positions
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
      tradeRequest.type = ORDER_TYPE_BUY;
     }

Наконец, мы сбрасываем все предыдущие ошибки с помощью функции ResetLastError(), а затем пытаемся закрыть позицию, отправив торговый запрос. Воспользуемся циклом for, чтобы попытаться отправить запрос на закрытие позиции несколько раз для гарантированного закрытия позиции даже при слабом интернет-соединении или при возникновении некритических ошибок. Если ордер успешно отправлен и выполнен (коды возврата 10008 или 10009), отображаем сообщение об успешном выполнении и возвращаем true. Если ордер не выполнен, вызываем функцию ErrorAdvisor() для обработки ошибки. Если функция ErrorAdvisor() указывает на критическую ошибку или если советник остановлен, выводим сообщение об ошибке и возвращаем false, чтобы обозначить, что закрытие позиции не удалось.

ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- position closing request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }

Обязательно расположите все сегменты кода в следующей последовательности. Все сегменты кода функции ClosePositionByTicket() расположены в правильном порядке ниже:

bool ClosePositionByTicket(ulong positionTicket) export
  {
//-- first check if the EA is allowed to trade
   if(!TradingIsAllowed())
     {
      return(false); //--- algo trading is disabled, exit function
     }

//--- Confirm and select the position using the provided positionTicket
   ResetLastError(); //--- Reset error cache incase of ticket selection errors
   if(PositionSelectByTicket(positionTicket))
     {
      //---Position selected
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Position with ticket:", positionTicket, " selected and ready to be closed.");
     }
   else
     {
      Print("...........................................................................................");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      return(false); //-- Exit the function
     }

//--- Position ticket selected, save the position properties
   string positionSymbol = PositionGetString(POSITION_SYMBOL);
   double positionVolume = PositionGetDouble(POSITION_VOLUME);
   ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

//-- Print position properties before closing it
   string positionProperties;
   positionProperties += "-- "  + positionSymbol + " " + EnumToString(positionType) + " Details" +
   " -------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", PositionGetDouble(POSITION_VOLUME)) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", PositionGetDouble(POSITION_PRICE_OPEN)) + "\r\n";
   positionProperties += "SL: " + StringFormat("%G", PositionGetDouble(POSITION_SL)) + "\r\n";
   positionProperties += "TP: " + StringFormat("%G", PositionGetDouble(POSITION_TP)) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "_______________________________________________________________________________________";
   Print(positionProperties);

//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);

//-- initialize the trade reqiest parameters to close the position
   tradeRequest.action = TRADE_ACTION_DEAL; //-- Trade operation type for closing a position
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.volume = positionVolume;
   tradeRequest.deviation = SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD) * 2;

//--- Set the price and order type of the position being closed
   if(positionType == POSITION_TYPE_BUY)
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
      tradeRequest.type = ORDER_TYPE_SELL;
     }
   else//--- For sell type positions
     {
      tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
      tradeRequest.type = ORDER_TYPE_BUY;
     }

   ResetLastError(); //--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function

   for(int loop = 0; loop <= 100; loop++) //-- try closing the position 101 times untill the request is successful
     {
      //--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
         //-- Confirm order execution
         if(tradeResult.retcode == 10008 || tradeResult.retcode == 10009)
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("Successfully closed position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
            Print("_______________________________________________________________________________________");
            return(true); //-- exit function
            break; //--- success - order placed ok. exit for loop
           }
        }
      else  //-- position closing request failed
        {
         //-- order not sent or critical error found
         if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
           {
            Print(__FUNCTION__, "_________________________________________________________________________");
            PrintFormat("ERROR closing position #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
            Print("_______________________________________________________________________________________");
            return(false); //-- exit function
            break; //-- exit for loop
           }
        }
     }
   return(false);
  }

Сохраните и скомпилируйте файл исходного кода библиотеки PositionsManager.mq5. Файл новой библиотеки PositionsManager.ex5 появится в папке Libraries\Toolkit\.


Заключение

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


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

Прикрепленные файлы |
Нейросети в трейдинге: Оптимизация Transformer для прогнозирования временных рядов (LSEAttention) Нейросети в трейдинге: Оптимизация Transformer для прогнозирования временных рядов (LSEAttention)
Фреймворк LSEAttention предлагает пути совершенствования архитектуры Transformer, и был разработан специально для долгосрочного прогнозирования многомерных временных рядов. Предложенные авторами метода подходы позволяют решить проблемы энтропийного коллапса и нестабильности обучения, характерные для ванильного Transformer.
Быстрый тестер торговых стратегий на Python с использованием Numba Быстрый тестер торговых стратегий на Python с использованием Numba
В статье реализован быстрый тестер стратегий для моделей машинного обучения с применением Numba. По скорости он превосходит тестер стратегий на чистом Python в 50 раз. Автор рекомендует использовать эту библиотеку для ускорения математических расчетов и особенно там, где используются циклы.
Объемный нейросетевой анализ как ключ к будущим трендам Объемный нейросетевой анализ как ключ к будущим трендам
Статья исследует возможность улучшения прогнозирования цен на основе анализа объема торгов, интегрируя принципы технического анализа с архитектурой LSTM нейронных сетей. Особое внимание уделяется выявлению и интерпретации аномальных объемов, использованию кластеризации и созданию признаков на основе объемов и их определения в контексте машинного обучения.
Алгоритм атомарного орбитального поиска — Atomic Orbital Search (AOS) Алгоритм атомарного орбитального поиска — Atomic Orbital Search (AOS)
В статье рассматривается алгоритм AOS (Atomic Orbital Search), который использует концепции атомной орбитальной модели для моделирования поиска решений. Алгоритм основывается на вероятностных распределениях и динамике взаимодействий в атоме. В статье подробно обсуждаются математические аспекты AOS, включая обновление положений кандидатов решений и механизмы поглощения и выброса энергии. AOS открывает новые горизонты для применения квантовых принципов в вычислительных задачах, предлагая инновационный подход к оптимизации.