English 中文 Español Deutsch 日本語 Português
preview
Как построить советник, работающий автоматически (Часть 02): Начинаем писать код

Как построить советник, работающий автоматически (Часть 02): Начинаем писать код

MetaTrader 5Трейдинг | 26 января 2023, 10:14
3 187 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Как построить советник, работающий автоматически (Часть 01): Концепции и структуры, я представил первые шаги, которые необходимо понять, перед тем, как приступать к созданию советника, торгующего автоматически. Там я показал, какие концепции нужно учитывать и какую структуру создавать.

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

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


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

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

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


Создание класса C_Orders

Чтобы создать класс, нам сначала нужно создать файл, который будет содержать код нашего класса. Для этого в окне браузера MetaEditor перейдем к папке "Include" и щелкнем по ней правой кнопкой мыши. Потом выберем "Новый файл" и проследуем по инструкциям на изображении ниже:

Рисунок 1

Рисунок 01 - Добавляем включаемый файл

Рисунок 02

Рисунок 02 - Вот так создается нужный нам файл


После того, как вы пройдете шаги, показанные на рисунках 01 и 02, будет создан нужный файл, который откроется в редакторе MetaEditor. С его содержанием можно ознакомиться ниже. Прежде чем продолжить, я хочу кое-что быстро объяснить. Посмотрите на рисунок 01, на нем вы можете увидеть, что можно создать класс напрямую. Конечно, вы можете удивиться, почему мы этого не делаем, и действительно, это правильный вопрос. Причина в том, что если создать класс, используя пункт с рисунка 01, то класс не создастся полностью с нуля, а уже будет содержать некоторую предопределенную информацию или формат. Для нас же в рамках статьи важно создасть класс с нуля. Давайте вернемся к коду.

#property copyright "Daniel Jose"
#property link      ""
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+

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

Чтобы сделать наш код безопасным и надежным, мы используем инструмент, который MQL5 принес из C++: классы. Если вы не знаете, что такое классы, я вам рекомендую ознакомиться с этой темой. Вам не обязательно почитать непосредственно документацию C++ о классах; на самом деле, можете начать с классов в документации MQL5, что обеспечит вас хорошей отправной точки. Кроме того, содержание документации по MQL5 легче понять, чем всю ту путаницу, которую C++ может вызвать у людей, никогда не слышавших о классах.

В целом можно сказать, что класс — это, безусловно, самый безопасный и эффективный способ изолировать код от других частей кода. Это означает, что класс рассматривается не как код, а как особый тип данных, с помощью которых можно делать гораздо больше, чем просто использовать примитивные типы, такие как integer, double, boolean, среди прочих. Другими словами, для программы класс является многофункциональным инструментом. Ей не нужно знать, как класс создан или что он содержит. Она просто должна знать, как им пользоваться. Подумайте о классе как об электроинструменте —вам не нужно знать, как он был построен или какие компоненты есть в нем. Всё, что вам нужно знать — это как его подключить и использовать, его эксплуатация не должна влиять на его использование. Это простое определение класса.

А теперь, давайте двигаться дальше. Первое, что мы сделаем, — сгенерируем следующие строки:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
class C_Orders
{
        private :
        public  :
};
//+------------------------------------------------------------------+

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

Одним словом: всё, что находится между объявлением слова private и словом public, может быть доступно исключительно внутри класса. Здесь вы сможете использовать глобальные переменные, к которым нельзя получить доступ вне кода класса. Может быть доступно всё, что объявлено после слова public в любом месте кода, независимо от того, является ли оно частью класса или нет. Любой человек может получить доступ к тому, что там находится.

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

#property copyright "Daniel Jose"
#property version   "1.00"
#property link      "https://www.mql5.com/pt/articles/11223"
//+------------------------------------------------------------------+
#include <Generic Auto Trader\C_Orders.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+

На этом этапе мы включаем наш класс в советник с помощью директивы компиляции Include. Когда мы используем эту директиву, компилятор понимает, что с этого момента заголовочный файл C_Orders.mqh, находящийся в каталоге include папки Generic Auto Trader, должен быть включен в систему и потом скомпилирован. Есть несколько хитростей по этому поводу, но я не буду вдаваться в подробности, потому что попытки разобраться в этом оставит менее опытных людей в полной растерянности, но происходит именно то, что я описал.


Определяем первые функции класса C_Orders

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

Поэтому и вам, только начинающим свою карьеру, следует поступать таким же образом, т.е., перед тем, как добавить какие-либо строки кода, вы должны продумать все нужные моменты. Давайте поразмышляем: что нам действительно нужно иметь в классе C_Orders, чтобы проделать как можно меньше работы и максимально использовать возможности платформы MetaTrader 5?

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

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

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

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

class C_Orders
{
        private :
//+------------------------------------------------------------------+
                MqlTradeRequest m_TradeRequest;
                struct st00
                {
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                        bool    PlotLast;
                }m_Infos;

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

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

                C_Orders()
                        {
                                m_Infos.nDigits         = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_Infos.VolMinimal      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_Infos.VolStep         = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_Infos.PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_Infos.ValuePerPoint   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_Infos.AdjustToTrade   = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
                                m_Infos.PlotLast        = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
                        };

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

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

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

                ~C_Orders() { }

Посмотрите на синтаксис. Имя конструктора и деструктора совпадают с именем класса. Однако объявлению деструктора предшествует тильда (~). Кроме того, важно помнить, что как конструктор, так и деструктор не возвращают значения какого-либо типа. Попытка сделать это считается ошибкой и сделает компилирование кода невозможным.

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

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

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

Прежде чем продолжить, хочу обратить ваше внимание на деталь, присутствующую в конструкторе класса. Мы её выделяем ниже:

        m_Infos.PlotLast = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);

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

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

  • По ценам BID, который можно увидеть на рынке ВАЛЮТЫ;
  • По ценам LAST, часто встречаtncz на рынке АКЦИИ.

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

Но зачем эксперту нужно знать, какая система графического представления используется? Причина проста: когда актив использует систему представления BID, то значение цены LAST всегда равно нулю. Это может помешать советнику отправить некоторые виды ордеров на сервер, так как он не знает, какой вид ордера следует отправить. Если советнику всё же удастся отправить ордер, а сервер примет его, то ордер будет выполнен неправильно, поскольку вид ордера заполнился неверно.

Эта проблема возникает при попытке использовать на рынке ФОРЕКС советника, созданного для ФОНДОВОГО рынка. Но верно и обратное: если система графического представления имеет вид LAST, но советник был создан и построен для работы на рынке форекс, где система графиков имеет тип BID, то советник может не отправить ордера в нужный момент, так как цена LAST может меняться, в то время как BID и ASK остаются статичными.

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

По этой причине советник проверяет, на каком виде рынка или графической системы он работает, чтобы его можно было использовать или переносить с рынка ФОРЕКСА на ФОНДОВЫЙ рынок или наоборот без необходимости модификации или перекомпиляции.

Мы уже видели, как важны мелкие нюансы. Простая деталь может поставить под угрозу всё. Теперь посмотрим, как отправить отложенный ордер на торговый сервер.


Отправляем отложенный ордер на сервер

После изучения документации по работе торговой системы на языке MQL5, мы обнаружили функцию OrderSend. Важно помнить, что не обязательно использовать данную функцию напрямую, поскольку можно отправлять ордера на сервер с помощью стандартной библиотеки MetaTrader 5, через класс CTrade. Однако здесь я хочу показать работу этой функции «за кулисами».

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

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

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult TradeCheck;
                                MqlTradeResult      TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

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

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

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

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

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

inline double AdjustPrice(const double value)
                        {
                                return MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick;
                        }

Этот простой расчет скорректирует цену до соответствующего значения, независимо от введенного значения, и сервер примет указанную цену.

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

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

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

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

  • Какой вид операции мы собираемся осуществить (покупка или продажа)?
  • По какой цене мы хотим торговать?
  • Каким объемом мы будем торговать?
  • Какие сроки необходимы для проведения операции?
  • Какой вид ордера мы будем использовать: buy limit, buy stop, sell limit или sell stop (мы не будем использовать sell stop limit или buy stop limit)?
  • На каком рынке мы будем торговать: форекс или фондовый рынок?
  • Какой стоп-лосс мы готовы установить?
  • Какой тейк-профит мы стремимся достичь?
  • Как видите, есть несколько вопросов, и некоторые из них нельзя использовать непосредственно в структуре, требуемой сервером. Поэтому мы создадим абстрагирование, чтобы реализовать более практичный язык или моделирование, чтобы наш советник мог работать без проблем. Мы даем классу самому разбираться со сложностью внесения настроек и исправлений. Таким образом, появляется следующая функция:

                    ulong CreateOrder(const ENUM_ORDER_TYPE type, double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                            {
                                            double  bid, ask, Desloc;                      
    					
                                    	Price = AdjustPrice(Price);
                                            bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                            ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                            ZeroMemory(m_TradeRequest);
                                            m_TradeRequest.action           = TRADE_ACTION_PENDING;
                                            m_TradeRequest.symbol           = _Symbol;
                                            m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                            m_TradeRequest.type             = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                                        (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
                                            m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                            Desloc = FinanceToPoints(FinanceStop, Leverage);
                                            m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                            Desloc = FinanceToPoints(FinanceTake, Leverage);
                                            m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                            m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                            m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                            m_TradeRequest.deviation        = 1000;
                                            m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                                    
                                            return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                    };
    

    Эта простая функция замечательна, потому что она берёт данные, которыми мы хотим торговать, и преобразует их в то, что торговый сервер ожидает получить, независимо от того, работаем ли мы с активом ФОРЕКСА или ФОНДОГО РЫНКА.

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

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

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

    inline double FinanceToPoints(const double Finance, const uint Leverage)
                            {
                                    double volume = m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1));
                                    
                                    return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
                            };
    

    Давайте попробуем разобраться в том, что здесь происходит, так как это может быть немного сложно. Если вы не понимаете эту функцию, пожалуй вам будет трудно понять и то, что происходит, когда класс отправляет ордер на сервер, а сервер устанавливает лимиты Take Profit и Stop Loss на позициях, смещенных от цены входа. Поэтому важно понять, как работает система, чтобы понять эту функцию.

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

    Рисунок 05  Рисунок 06  Рисунок 07


    На рисунках выше показаны выделенные пункты, которые используются при расчете для перевода финансовой стоимости в пунктах. В случае ВАЛЮТ размер и стоимость ТИКЕТА не показаны, но они составляют 1,0 для стоимости тикета и 0,00001 для размера (количества пунктов) за тикет. Не забывайте об этих значениях.

    Теперь давайте рассмотрим следующее: финансовая стоимость - это результат деления торгового объема на количество пунктов, умноженный на стоимость каждого пункта. Например, если предположить, что мы торгуем каким-либо активом с минимальным объемом 100 и плечом 2x, то есть в два раза больше минимального объема. В этом случае объем, которым мы будем торговать, составит 100 x 2, т.е. 200. Пока не надо беспокоиться об этом значении, давайте продолжим. Чтобы узнать стоимость каждого пункта, мы можем проводить следующий расчет:

    Стоимость пункта равна стоимости одного пункта, деленной на количество пунктов. Многие считают, что количество пунктов равно 1, но это ошибка. На изображениях выше видно, что количество пунктов может меняться. Для иностранной валюты оно составляет 0,00001. Поэтому не стоит считать, что то или иное значение является наиболее подходящим, не проверив его. Сделайте так, чтобы программа зафиксировала правильное значение и использовала его. Например, если предположить, что количество пунктов равно 0,01, а значение каждой из них равно 0,01. То в этом случае деление одного на другое даст значение 1. Опять же, данное значение может измениться. Для валют это 0,00001, а для доллара, торгуемого на B3 (Бразильская фондовая биржа) - 10.

    Теперь разберемся с ещё одним моментом. Чтобы было проще, предположим, что пользователь хочет торговать активом с минимальным объемом 100 и с кредитным плечом в 3 раза больше. Размер тикета составляет 0,01, а стоимость тикета - 0,01. Однако, вы хотите принять финансовый риск в размере 250. Сколько пунктов вы должны разместить по отношению к цене входа, чтобы стоимость соответствовала этим 250? Именно это и делает описанная выше процедура. Она рассчитывает стоимость и корректирует её таким образом, чтобы произошел положительный или отрицательный сдвиг в количестве пунктов в зависимости от ситуации, так, чтобы финансовая стоимость была равна 250. В данном случае это нам даст 2,5 или 250 пунктов.

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

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


    Способ тестирования системы ордеров

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

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

    #property copyright "Daniel Jose"
    #property description "This one is an automatic Expert Advisor"
    #property description "for demonstration. To understand how to"
    #property description "develop yours in order to use a particular"
    #property description "operational, see the articles where there"
    #property description "is an explanation of how to proceed."
    #property version   "1.03"
    #property link      "https://www.mql5.com/pt/articles/11223"
    //+------------------------------------------------------------------+
    #include <Generic Auto Trader\C_Orders.mqh>
    //+------------------------------------------------------------------+
    C_Orders *orders;
    ulong m_ticket;
    //+------------------------------------------------------------------+
    input int       user01   = 1;           //Fator de alavancagem
    input int       user02   = 100;         //Take Profit ( FINANCEIRO )
    input int       user03   = 75;          //Stop Loss ( FINANCEIRO )
    input bool      user04   = true;        //Day Trade ?
    input double    user05   = 84.00;       //Preço de entrada...
    //+------------------------------------------------------------------+
    int OnInit()
    {
            orders = new C_Orders();
            
            return INIT_SUCCEEDED;
    }
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
            delete orders;
    }
    //+------------------------------------------------------------------+
    void OnTick()
    {
    }
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
    {
    #define KEY_UP          38
    #define KEY_DOWN        40
    
            switch (id)
            {
                    case CHARTEVENT_KEYDOWN:
                            switch ((int)lparam)
                            {
                                    case KEY_UP:
                                            m_ticket = orders.CreateOrder(ORDER_TYPE_BUY, user05, user03, user02, user01, user04);
                                            break;
                                    case KEY_DOWN:
                                            m_ticket = orders.CreateOrder(ORDER_TYPE_SELL, user05, user03, user02, user01, user04);
                                            break;
                            }
                            break;
            }
    #undef KEY_DOWN
    #undef KEY_UP
    }
    //+------------------------------------------------------------------+
    

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

    1. Укажите цену, по которой должен быть размещен ордер. Помните, что нельзя использовать текущую цену;
    2. Используйте стрелку вверх, если вы думаете, что цена будет расти, или стрелку вниз, если вы думаете, что цена будет падать;
    3. Для этого перейдите на вкладку "Торговля" на панели инструментов. Должен появиться ордер с условиями, указанными советником.

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


    Заключение

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

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

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


    Демонстрационное видео


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

Прикрепленные файлы |
Популяционные алгоритмы оптимизации: Алгоритм оптимизации бактериального поиска пищи (Bacterial Foraging Optimization — BFO) Популяционные алгоритмы оптимизации: Алгоритм оптимизации бактериального поиска пищи (Bacterial Foraging Optimization — BFO)
Основа стратегии поиска пищи бактерией E.coli (кишечная палочка) вдохновила ученых на создание алгоритма оптимизации BFO. Алгоритм содержит оригинальные идеи и перспективные подходы к оптимизации и достоин дальнейшего изучения.
Как построить советник, работающий автоматически (Часть 01): Концепции и структуры Как построить советник, работающий автоматически (Часть 01): Концепции и структуры
Сегодня посмотрим, как создать советник, просто и безопасно работающий в автоматическом режиме.
Рецепты MQL5 — База данных макроэкономических событий Рецепты MQL5 — База данных макроэкономических событий
В статье рассматриваются возможности работы с базами данных на основе движка SQLite. Для удобства и эффективного использования принципов ООП сформирован класс CDatabase. Он в последующем задействуется при создании и управлении базой данных макроэкономических событий. Приводятся примеры использования многих методов класса CDatabase.
Еще раз о системе Мюррея Еще раз о системе Мюррея
Системы графического анализа цен заслуженно популярны у трейдеров. В данной статье я рассказываю о полной системе Мюррея, включающей не только его знаменитые уровни, но и некоторые другие полезные техники оценки текущего положения цены и принятия решения о сделке.