Español Português
preview
Моделирование рынка: В единстве — сила (II)

Моделирование рынка: В единстве — сила (II)

MetaTrader 5Тестер |
62 0
Daniel Jose
Daniel Jose

Введение

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

В предыдущей статье «Моделирование рынка: В единстве — сила (I)» мы приступили к заключительному этапу разработки системы репликации/моделирования. Там я объяснил директиву, которую мы будем использовать, чтобы все разработанные приложения работали как при подключении к реальному серверу — будь то через демо-счёт или реальный счёт, — так и в системе репликации/моделирования. В этой статье мы смоделируем поведение всего торгового процесса.

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

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


Подготовка к моделированию торгового сервера

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

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

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

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

В предыдущей статье мы уже рассмотрели класс, который будет обеспечивать работу с базой данных. Теперь нам осталось только создать SQL-инструкции, необходимые для создания столбцов. Помните, что я буду использовать максимально простую структуру: всего одну таблицу и никаких проверок для валидации данных. Так мы избежим ненужного затягивания этого заключительного этапа разработки. Я предполагаю, что вы, уважаемый читатель, не будете пытаться вводить в базу данных недопустимые данные. Итак, давайте начнём адаптировать код советника, чтобы он создавал базу данных при использовании системы репликации/моделирования. Чтобы адаптировать советник, нам нужно изменить исходный код в соответствии с приведённым ниже фрагментом.

030. //+------------------------------------------------------------------+
031. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
032. {
033.     int handle;
034.     ulong ul;
035.     
036.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
037.     switch (id)
038.     {
039.         case CHARTEVENT_CHART_CHANGE:
040.             if (Terminal != NULL) break;
041.             else
042.             {            
043.                 Terminal = new C_Terminal(0, 0, user00);
044.                 if (_Symbol != def_SymbolReplay)
045.                 {
046.                     for (int count = PositionsTotal() - 1; count >= 0; count--)
047.                     {
048.                         ul = PositionGetTicket(count);
049.                         if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol)
050.                         {
051.                             ChartIndicatorDelete(0, 0, IntegerToString(ul));
052.                             continue;
053.                         }
054.                         handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
055.                         ChartIndicatorAdd(0, 0, handle);
056.                         IndicatorRelease(handle);
057.                     }
058.                 } else 
059.                 {
060. //... НОВЫЙ КОД ...
061.                 }
062.             }
063.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
064.             EventChartCustom(0, evEA_At_ChartTrade, user00, 0, "");
065.             break;
066.     }
067. }
068. //+------------------------------------------------------------------+
069. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
070. {
071.     if (Terminal == NULL) return;
072.     static ulong ticket = 0;
073.     switch (trans.type)
074.     {
075.         case TRADE_TRANSACTION_HISTORY_ADD:
076.             EventChartCustom(0, evUpdate_Position, trans.position, 0, "");
077.             ticket = (trans.order != trans.position ? trans.position : 0);
078.             break;
079.         case TRADE_TRANSACTION_REQUEST:
080.             if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action)
081.             {
082.                 case TRADE_ACTION_DEAL:
083.                     if (ticket > 0) EventChartCustom(0, evUpdate_Position, ticket, 0, "");
084.                     else
085.                     {
086.                         int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order);
087.                         ChartIndicatorAdd(0, 0, handle);
088.                         IndicatorRelease(handle);
089.                     }
090.                     ticket = 0;
091.                     break;
092.                 case TRADE_ACTION_SLTP:
093.                     EventChartCustom(0, evUpdate_Position, request.position, 0, "");
094.                     break;
095.             }
096.             break;
097.     };
098. }
099. //+------------------------------------------------------------------+
100. void OnDeinit(const int reason)
101. {
102.     ulong ul;
103.     
104.     switch (reason)
105.     {
106.         case REASON_REMOVE:
107.         case REASON_INITFAILED:
108.             EventChartCustom(0, evEA_At_ChartTrade, -1, 0, "");
109.             break;
110.     }
111.     if (Terminal != NULL)
112.     {
113.         if (_Symbol != def_SymbolReplay)
114.         {
115.             for (int count = PositionsTotal() - 1; count >= 0; count--)
116.             {
117.                 ul = PositionGetTicket(count);
118.                 if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
119.                 ChartIndicatorDelete(0, 0, IntegerToString(ul));
120.             }
121.         } else 
122.         {
123. // ... НОВЫЙ КОД ...        
124.         }
125.     }
126.     delete Orders;
127.     delete Terminal;
128.     delete DB;
129. }
130. //+------------------------------------------------------------------+

Фрагмент советника

Обратите внимание, что мы вносим несколько незначительных изменений в код, приведённый в предыдущей статье. А теперь внимание. Есть два места, в которых в советник будет добавлен новый код. Эти два места находятся на строках 60 и 123 фрагмента, чтобы вам было проще понять. В строке 60 мы добавим код, отвечающий за размещение индикатора позиции на графике. Этот код будет эквивалентен коду между строками 46 и 57. В строке 123 мы реализуем код, эквивалентный коду между строками 115 и 119. Таким образом, в советник будет внесено совсем немного изменений. Код обработчика событий OnTradeTransaction, напротив, останется без изменений, как я уже объяснял в предыдущей статье. Тем не менее этот обработчик событий будет широко использоваться в советнике, даже когда мы будем запускать советник в системе репликации/моделирования. Не беспокойтесь. Я подробно объясню, как будет использоваться обработчик событий OnTradeTransaction.

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

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

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

067. //+------------------------------------------------------------------+
068.         ulong SendToPhysicalServer(void)
069.             {
070.                 MqlTradeCheckResult     TradeCheck;
071.                 MqlTradeResult          TradeResult;                
072.                 MqlTradeTransaction     TradeTrans;
073.                                 
074.                 ZeroMemory(TradeCheck);
075.                 ZeroMemory(TradeResult);
076.                 if (_Symbol == def_SymbolReplay)
077.                 {
078.                     TradeTrans.type = TRADE_TRANSACTION_REQUEST;
079.                     m_Base.TradeRequest.symbol = _Symbol;
080.                     m_Base.TradeRequest.action = TRADE_ACTION_DEAL;
081.                     TradeResult.order = 2048;
082.                     TradeResult.retcode = TRADE_RETCODE_DONE;                    
083.                     OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
084.                     TradeTrans.type = TRADE_TRANSACTION_HISTORY_ADD;
085.                     TradeTrans.order = 0;
086.                     TradeTrans.position = TradeResult.order;
087.                     OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
088.                 } else {
089.                     if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
090.                     {
091.                         PrintFormat("Order System - Check Error: %d", GetLastError());
092.                         return 0;
093.                     }
094.                     m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
095.                 }
096.                 if (TradeResult.retcode != TRADE_RETCODE_DONE)
097.                 {
098.                     PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
099.                     return 0;
100.                 };
101.                 
102.                 return TradeResult.order;
103.             }
104. //+------------------------------------------------------------------+

Фрагмент из C_Orders

Именно здесь процесс начинает становиться всё интереснее. Помните, что этот код служит только для проверки того, что мы действительно собираемся реализовать. Обратите внимание, что в строке 72 мы добавили новую структуру, определённую в MQL5. Затем, в строке 76, мы проверяем по имени символа, используем ли мы систему репликации/моделирования. Если мы используем символ, который, вероятно, зарегистрирован на реальном торговом сервере, то переходим к строке 89, и код продолжает работать так, как нам уже известно. Если же мы используем символ системы репликации/моделирования, то будем моделировать ответ сервера. Этот процесс выполняется в два этапа. На первом этапе мы моделируем ответ, соответствующий рыночному ордеру на открытие позиции. После настройки данных мы в первый раз вызываем обработчик события OnTradeTransaction, как показано в строке 83. Затем в строке 87 мы имитируем ещё одно событие, связанное с рыночным ордером. В обоих случаях будет вызван обработчик событий OnTradeTransaction советника, который попытается разместить индикатор позиции на графике.

Этот механизм будет работать. Однако, поскольку у индикатора позиции пока нет источника, из которого он мог бы получать данные о позиции, в итоге он будет удалён с графика. Вы можете добавить в код несколько сообщений, чтобы проследить этот процесс. Я оставляю добавление этих сообщений в качестве упражнения для вас, уважаемый читатель. Так вы сможете понять все этапы процесса: от момента, когда мы используем индикатор Mouse Study для взаимодействия с кнопками покупки и продажи индикатора Chart Trade, через отправку сообщения от индикатора Chart Trade советнику, до того, как советник использует класс C_Orders для моделирования открытия позиции.

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

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

Но подождите минуту. Эта система работает с хеджинговым счётом при подключении к реальному серверу. Почему теперь это не работает, когда мы пытаемся смоделировать сервер? Проблема заключается в порядке, в котором мы отправляем события моделирования. Обратите внимание, что в функции SendToPhysicalServer мы сначала отправляем событие TRADE_TRANSACTION_REQUEST, а затем — событие типа TRADE_TRANSACTION_HISTORY_ADD. Эта последовательность неверна. Я намеренно использовал эту неправильную последовательность, чтобы показать вам, что с этого момента вам нужно будет проявлять максимальную внимательность при реализации каждого шага.

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

Результат можно увидеть на следующем изображении.

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


Проектирование базы данных

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. #include "..\SQL\C_ReplayDataBase.mqh"
006. //+------------------------------------------------------------------+
007. class C_Orders : public C_ReplayDataBase
008. {
                                   .
                                   .
                                   .
164. //+------------------------------------------------------------------+    
165.     public    :
166. //+------------------------------------------------------------------+
167.         C_Orders(const ulong magic)
168.             :C_ReplayDataBase()
169.             {
170.                 m_Base.MagicNumber = magic;
171.                 ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( ticket, type, volume, price, sl, tp, history );");
172.             }
173. //+------------------------------------------------------------------+ 

Фрагмент C_Orders

Обратите внимание, что в строке 5 я добавил заголовочный файл класса C_ReplayDataBase. В строке 7 мы указываем, что класс C_Orders будет публично наследоваться от C_ReplayDataBase. Таким образом, C_Orders получит новую функциональность. Чтобы всё работало правильно, в строке 168 мы инициализируем C_ReplayDataBase в конструкторе C_Orders. Таким образом, нам понадобится меньше кода в советнике, и при этом мы сможем реализовать всё необходимое для моделирования торгового сервера. Хорошо, но нам нужна таблица в базе данных. С помощью инструкции в строке 171 мы создаём таблицу, которую будем использовать в моделировании. Теперь система начинает обретать очертания.

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

041. //+------------------------------------------------------------------+
042.         struct stChartTrade
043.         {
044.             struct stEvent
045.             {
046.                 EnumEvents  ev;
047.                 string      szSymbol,
048.                             szContract;
049.                 bool        IsDayTrade;
050.                 ushort      Leverange;
051.                 double      PointsTake,
052.                             PointsStop;
053.             }Data;
054. //---
055.             bool Decode(const EnumEvents ev, const string sparam)
056.                 {
057.                     string Res[];
058.         
059.                     if (StringSplit(sparam, '?', Res) != 7) return false;
060.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
061.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
062.                     else return false;
063.                     
064.                     return true;
065.                 }
066. //---
067.         }m_ChartTrade;
068. //+------------------------------------------------------------------+
069.         ulong SimulateServer(void)
070.         {
071.             MqlTradeResult        TradeResult;
072.             MqlTradeTransaction   TradeTrans;
073.             
074.             ZeroMemory(TradeResult);
075.             ZeroMemory(TradeTrans);
076.             TradeTrans.type = TRADE_TRANSACTION_HISTORY_ADD;
077.             TradeTrans.order = 0;
078.             TradeTrans.position = TradeResult.order;
079.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
080.             TradeTrans.type = TRADE_TRANSACTION_REQUEST;
081.             m_Base.TradeRequest.symbol = _Symbol;
082.             m_Base.TradeRequest.action = TRADE_ACTION_DEAL;
083.             TradeResult.order = 2048;
084.             TradeResult.retcode = TRADE_RETCODE_DONE;                    
085.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
086.             
087.             return TradeResult.order;
088.         }
089. //+------------------------------------------------------------------+
090.         ulong SendToPhysicalServer(void)
091.         {
092.             MqlTradeCheckResult   TradeCheck;
093.             MqlTradeResult        TradeResult;
094.                                 
095.             ZeroMemory(TradeCheck);
096.             ZeroMemory(TradeResult);
097.             if (_Symbol == def_SymbolReplay)
098.                 return SimulateServer();
099.             if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
100.             {
101.                 PrintFormat("Order System - Check Error: %d", GetLastError());
102.                 return 0;
103.             }
104.             m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
105.             if (TradeResult.retcode != TRADE_RETCODE_DONE)
106.             {
107.                 PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
108.                 return 0;
109.             };
110.             
111.             return TradeResult.order;
112.         }
113. //+------------------------------------------------------------------+    

Фрагмент C_Orders

Обратите внимание, что в этой функции SimulateServer, где мы будем реализовывать всю логику моделирования, порядок событий уже правилен. Таким образом, с помощью индикатора Chart Trade мы можем отправлять несколько рыночных ордеров на покупку или продажу. Это тот же код, который мы видели ранее. В строке 97 мы проверяем, используется ли система репликации/моделирования. Если это так, то в строке 98 мы вызываем симулятор сервера. Отныне мы сосредоточимся исключительно на этой новой функции. Пока не беспокойтесь о том, как всё будет работать в советнике или в индикаторе позиции. Сначала нам нужно наделить наш моделируемый сервер какой-нибудь действительно полезной функциональностью.


Начинаем моделировать сервер

Прежде чем реализовывать то, о чём говорится в названии этого раздела, вы должны понять, что нам предстоит сделать. Моделирование сервера станет одной из самых интересных задач в этой серии статей, поскольку позволит вам понять многие другие аспекты, связанные с реальным сервером. Прежде всего вы должны знать, что запросы, которые мы будем моделировать, уже будут созданы другими функциями и процедурами класса C_Orders и сохранены в структуре m_Base.TradeRequest.

Таким образом, нам останется лишь правильно интерпретировать содержимое этой структуры. Помните, что я не буду проверять, корректны ли данные. Проверка корректности данных была бы равносильна вызову OrderCheck функции MQL5. Поэтому старайтесь не вводить некорректные данные в m_Base.TradeRequest, так как система может вернуть вам ошибочные результаты.

По сути, нам потребуется смоделировать два типа запросов: TRADE_ACTION_DEAL и TRADE_ACTION_SLTP, поскольку только они используются в коде C_Orders. Поэтому было важно сначала разработать всю систему, подключённую к реальному серверу. Таким образом, на данном этапе мы не будем тратить время на реализацию ненужных запросов. С помощью этих двух типов запросов мы сможем покупать и продавать по рынку, а также закрывать позицию или изменять уровни тейк-профита и стоп-лосса.

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

Чтобы начать моделирование, мы начнём с самой простой операции: реализуем моделирование запроса TRADE_ACTION_SLTP. Выбор именно этой операции обусловлен тем, что запрос полностью не зависит от типа счёта, который моделируется, или, точнее, от символа, с которым мы работаем в системе репликации/моделирования. По сути, у запроса TRADE_ACTION_SLTP только одна цель: изменить значение стоп-лосса или тейк-профита. Вот и всё. Поскольку сам запрос указывает, какие данные следует использовать, нам остаётся лишь обновить базу данных. Тут нет ничего сложного. Однако здесь мы не будем моделировать все сообщения, отправляемые сервером. Мы максимально упростим всё. Если вы действительно хотите смоделировать все сообщения, то можете реализовать их полное моделирование. Но, поскольку цель этих статей — обучение, мы реализуем моделирование максимально простым способом. Итак, новый код показан в следующем фрагменте.

068. //+------------------------------------------------------------------+
069.         ulong SimulateServer(void)
070.         {
071.             MqlTradeResult        TradeResult;
072.             MqlTradeTransaction   TradeTrans;
073.             bool                  bResult = false;
074.             
075.             ZeroMemory(TradeResult);
076.             ZeroMemory(TradeTrans);
077.             
078.             switch (m_Base.TradeRequest.action)
079.             {
080.                 case TRADE_ACTION_SLTP:
081.                     bResult = ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 
082.                                                                         m_Base.TradeRequest.sl,
083.                                                                         m_Base.TradeRequest.tp,
084.                                                                         m_Base.TradeRequest.position));
085.                     TradeTrans.type = TRADE_TRANSACTION_REQUEST;
086.                     TradeResult.retcode = TRADE_RETCODE_DONE;
087.                     if (bResult)
088.                     {
089.                         OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
090.                         return m_Base.TradeRequest.position;
091.                     }
092.                     break;
093.                 case TRADE_ACTION_DEAL:
094.                     break;
095.             }
096.             TradeResult.retcode = TRADE_RETCODE_INVALID;
097.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
098.             return 0;                
099.         }
100. //+------------------------------------------------------------------+

Фрагмент C_Orders

Обратите внимание, что всё очень просто и понятно. В строке 80 указана ветка обработки, которую мы будем отрабатывать. В строке 81 мы выполняем операцию с базой данных. Если операция выполнена успешно, то в строке 89 мы моделируем в MetaTrader 5 эффект операции, выполненной в базе данных, как ответ сервера. А если операция не удалась из-за какого-либо сбоя в базе данных, то в строке 97 мы моделируем тот же тип ответа со стороны сервера.

Однако нужно помнить одну вещь. Хотя приведённый выше код в очень сжатом виде моделирует то, что сделал бы сервер, на данный момент класс C_Orders не сможет сформировать запрос TRADE_ACTION_SLTP. Класс C_Orders по-прежнему подключён к реальному торговому серверу. Помните, что приведённые выше тесты моделируют открытие или закрытие позиции. Чтобы правильно обработать запрос TRADE_ACTION_SLTP, нам потребуется изменить процедуру ModifyValueSLTP класса C_Orders. Итак, прежде чем продолжить, давайте посмотрим код процедуры ModifyValueSLTP. Это показано в следующем фрагменте.

129. //+------------------------------------------------------------------+    
130.         void ModifyValueSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
131.         {
132.             ZeroMemory(m_Base.TradeRequest);            
133.             MqlTradeRequest TradeRequest[1];
134. 
135.             if ((sl < 0) || (tp < 0)) if (!PositionSelectByTicket(ticket)) return;
136.             m_Base.TradeRequest.magic      = m_Base.MagicNumber;
137.             m_Base.TradeRequest.action     = TRADE_ACTION_SLTP;
138.             m_Base.TradeRequest.symbol     = symbol;
139.             m_Base.TradeRequest.position   = ticket;
140.             m_Base.TradeRequest.sl         = (sl < 0 ? PositionGetDouble(POSITION_SL) : sl);
141.             m_Base.TradeRequest.tp         = (tp < 0 ? PositionGetDouble(POSITION_TP) : tp);
142.                 
143.             TradeRequest[0] = m_Base.TradeRequest;
144.             ArrayPrint(TradeRequest);
145.                 
146.             SendToPhysicalServer();
147.         }
148. //+------------------------------------------------------------------+    

Фрагмент класса C_Orders

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "..\Defines.mqh"
05. #include "..\SQL\C_ReplayDataBase.mqh"
06. //+------------------------------------------------------------------+
07. class C_InServer : public C_ReplayDataBase
08. {
09.     private   :
10.         bool    m_IsReplay;
11.     public    :
12. //+------------------------------------------------------------------+
13.         C_InServer()
14.             :C_ReplayDataBase(),
15.             m_IsReplay(_Symbol == def_SymbolReplay)
16.         {
17.             ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( ticket, type, volume, price, sl, tp, history );");
18.         }
19. //+------------------------------------------------------------------+
20.     inline const double _PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE arg)
21.     {
22.         if (!m_IsReplay)
23.             return PositionGetDouble(arg);
24.         
25.         return 0;
26.     }
27. //+------------------------------------------------------------------+
28.     inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg)
29.     {
30.         if (!m_IsReplay)
31.             return PositionGetInteger(arg);
32.             
33.         return 0;
34.     }
35. //+------------------------------------------------------------------+
36.     inline const bool _PositionSelectByTicket(ulong arg)
37.     {
38.         if (!m_IsReplay)
39.             return PositionSelectByTicket(arg);
40.             
41.         return false;
42.     }
43. //+------------------------------------------------------------------+
44.     inline const string _PositionGetString(ENUM_POSITION_PROPERTY_STRING arg)
45.     {
46.         if (!m_IsReplay)
47.             return PositionGetString(arg);
48.             
49.         return "";
50.     }
51. //+------------------------------------------------------------------+
52.     inline const string _PositionGetSymbol(int arg)
53.     {
54.         return (m_IsReplay ? def_SymbolReplay : PositionGetSymbol(arg));
55.     }
56. //+------------------------------------------------------------------+
57.     inline const int _PositionsTotal(void)
58.     {
59.         if (!m_IsReplay)
60.             return PositionsTotal();
61.             
62.         return 0;
63.     }
64. //+------------------------------------------------------------------+
65. };
66. //+------------------------------------------------------------------+

C_InServer

Теперь мы решим сразу две проблемы. Решив эти вопросы в классе C_Orders, мы одновременно решим те же проблемы и в индикаторе позиции. Поэтому нам нужно обновить код класса C_Orders, как показано ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_InServer.mqh"
005. //+------------------------------------------------------------------+
006. class C_Orders : public C_InServer
007. {
008.     protected:
009. //+------------------------------------------------------------------+
010. inline const ulong GetMagicNumber(void) const { return m_Base.MagicNumber; }
011. //+------------------------------------------------------------------+
012.         bool ClosePosition(const ulong ticket)
013.         {
014.             bool     IsBuy;
015.             string szContract;
016.                 
017.             if    (!_PositionSelectByTicket(ticket)) return false;
018.             IsBuy = _PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
019.             szContract = _PositionGetString(POSITION_SYMBOL);
020.             ZeroMemory(m_Base.TradeRequest);
021.             m_Base.TradeRequest.action      = TRADE_ACTION_DEAL;
022.             m_Base.TradeRequest.type        = (IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
023.             m_Base.TradeRequest.price       = NormalizeDouble(SymbolInfoDouble(szContract, (IsBuy ? SYMBOL_BID : SYMBOL_ASK)), (int)SymbolInfoInteger(szContract, SYMBOL_DIGITS));
024.             m_Base.TradeRequest.position    = ticket;
025.             m_Base.TradeRequest.symbol      = szContract;
026.             m_Base.TradeRequest.volume      = _PositionGetDouble(POSITION_VOLUME);
027.             m_Base.TradeRequest.deviation   = 1000;
028.                 
029.             return SendToPhysicalServer() != 0;
030.         };
031. //+------------------------------------------------------------------+    
032.     private    :
033. //+------------------------------------------------------------------+
034.         struct stBase
035.         {
036.             MqlTradeRequest TradeRequest;
037.             ulong           MagicNumber;
038.             bool            bTrash;
039.         }m_Base;
040. //+------------------------------------------------------------------+
041.         struct stChartTrade
042.         {
043.             struct stEvent
044.             {
045.                 EnumEvents  ev;
046.                 string      szSymbol,
047.                             szContract;
048.                 bool        IsDayTrade;
049.                 ushort      Leverange;
050.                 double      PointsTake,
051.                             PointsStop;
052.             }Data;
053. //---
054.             bool Decode(const EnumEvents ev, const string sparam)
055.                 {
056.                     string Res[];
057.         
058.                     if (StringSplit(sparam, '?', Res) != 7) return false;
059.                     stEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])};
060.                     if ((ev == loc.ev) && (loc.szSymbol == _Symbol)) Data = loc;
061.                     else return false;
062.                     
063.                     return true;
064.                 }
065. //---
066.         }m_ChartTrade;
067. //+------------------------------------------------------------------+
068.         ulong SimulateServer(void)
069.         {
070.             MqlTradeResult        TradeResult;
071.             MqlTradeTransaction   TradeTrans;
072.             bool bResult = false;
073.             
074.             ZeroMemory(TradeResult);
075.             ZeroMemory(TradeTrans);
076.             
077.             switch (m_Base.TradeRequest.action)
078.             {
079.                 case TRADE_ACTION_SLTP:
080.                     bResult = ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 
081.                                                                         m_Base.TradeRequest.sl,
082.                                                                         m_Base.TradeRequest.tp,
083.                                                                         m_Base.TradeRequest.position));
084.                     TradeTrans.type = TRADE_TRANSACTION_REQUEST;
085.                     TradeResult.retcode = TRADE_RETCODE_DONE;
086.                     if (bResult)
087.                     {
088.                         OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
089.                         return m_Base.TradeRequest.position;
090.                     }
091.                     break;
092.                 case TRADE_ACTION_DEAL:
093.                     break;
094.             }
095.             TradeResult.retcode = TRADE_RETCODE_INVALID;
096.             OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult);
097.             return 0;                
098.         }
099. //+------------------------------------------------------------------+
100.         ulong SendToPhysicalServer(void)
101.         {
102.             MqlTradeCheckResult   TradeCheck;
103.             MqlTradeResult        TradeResult;
104.                                 
105.             ZeroMemory(TradeCheck);
106.             ZeroMemory(TradeResult);
107.             if (_Symbol == def_SymbolReplay)
108.                 return SimulateServer();
109.             if (!OrderCheck(m_Base.TradeRequest, TradeCheck))
110.             {
111.                 PrintFormat("Order System - Check Error: %d", GetLastError());
112.                 return 0;
113.             }
114.             m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult);
115.             if (TradeResult.retcode != TRADE_RETCODE_DONE)
116.             {
117.                 PrintFormat("Order System - Send Error: %d", TradeResult.retcode);
118.                 return 0;
119.             };
120.             
121.             return TradeResult.order;
122.         }
123. //+------------------------------------------------------------------+    
124.         ulong ToMarket(const ENUM_ORDER_TYPE type)
125.         {
126.             double price     = SymbolInfoDouble(m_ChartTrade.Data.szContract, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID));
127.             double vol       = SymbolInfoDouble(m_ChartTrade.Data.szContract, SYMBOL_VOLUME_STEP);
128.             uchar  nDigit    = (uchar)SymbolInfoInteger(m_ChartTrade.Data.szContract, SYMBOL_DIGITS);
129.             
130.             ZeroMemory(m_Base.TradeRequest);
131.             m_Base.TradeRequest.magic         = m_Base.MagicNumber;
132.             m_Base.TradeRequest.symbol        = m_ChartTrade.Data.szContract;
133.             m_Base.TradeRequest.price         = NormalizeDouble(price, nDigit);
134.             m_Base.TradeRequest.action        = TRADE_ACTION_DEAL;
135.             m_Base.TradeRequest.sl            = NormalizeDouble(m_ChartTrade.Data.PointsStop == 0 ? 0 : price + (m_ChartTrade.Data.PointsStop * (type == ORDER_TYPE_BUY ? -1 : 1)), nDigit);
136.             m_Base.TradeRequest.tp            = NormalizeDouble(m_ChartTrade.Data.PointsTake == 0 ? 0 : price + (m_ChartTrade.Data.PointsTake * (type == ORDER_TYPE_BUY ? 1 : -1)), nDigit);
137.             m_Base.TradeRequest.volume        = NormalizeDouble(vol + (vol * (m_ChartTrade.Data.Leverange - 1)), nDigit);
138.             m_Base.TradeRequest.type          = type;
139.             m_Base.TradeRequest.type_time     = (m_ChartTrade.Data.IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
140.             m_Base.TradeRequest.stoplimit     = 0;
141.             m_Base.TradeRequest.expiration    = 0;
142.             m_Base.TradeRequest.type_filling  = ORDER_FILLING_RETURN;
143.             m_Base.TradeRequest.deviation     = 1000;
144.             m_Base.TradeRequest.comment       = "Order Generated by Experts Advisor.";
145. 
146.             MqlTradeRequest TradeRequest[1];
147. 
148.             TradeRequest[0] = m_Base.TradeRequest;
149.             ArrayPrint(TradeRequest);
150. 
151.             return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? SendToPhysicalServer() : 0);
152.         };
153. //+------------------------------------------------------------------+
154.         void CloseAllsPosition(void)
155.         {
156.             for (int count = _PositionsTotal() - 1; count >= 0; count--)
157.             {
158.                 if (_PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue;
159.                 if (_PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue;
160.                 ClosePosition(_PositionGetInteger(POSITION_TICKET));
161.             }
162.         };
163. //+------------------------------------------------------------------+    
164.         void ModifyValueSLTP(const ulong ticket, const string symbol, const double sl, const double tp)
165.         {
166.             ZeroMemory(m_Base.TradeRequest);            
167.             MqlTradeRequest TradeRequest[1];
168. 
169.             if ((sl < 0) || (tp < 0)) if (!_PositionSelectByTicket(ticket)) return;
170.             m_Base.TradeRequest.magic      = m_Base.MagicNumber;
171.             m_Base.TradeRequest.action     = TRADE_ACTION_SLTP;
172.             m_Base.TradeRequest.symbol     = symbol;
173.             m_Base.TradeRequest.position   = ticket;
174.             m_Base.TradeRequest.sl         = (sl < 0 ? _PositionGetDouble(POSITION_SL) : sl);
175.             m_Base.TradeRequest.tp         = (tp < 0 ? _PositionGetDouble(POSITION_TP) : tp);
176.                 
177.             TradeRequest[0] = m_Base.TradeRequest;
178.             ArrayPrint(TradeRequest);
179.                 
180.             SendToPhysicalServer();
181.         }
182. //+------------------------------------------------------------------+    
183.     public    :
184. //+------------------------------------------------------------------+
185.         C_Orders(const ulong magic)
186.             :C_InServer()
187.         {
188.             m_Base.MagicNumber = magic;
189.         }
190. //+------------------------------------------------------------------+    
191.         void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
192.         {
193.             switch (id)
194.             {
195.                 case CHARTEVENT_CUSTOM + evChartTradeBuy     :
196.                 case CHARTEVENT_CUSTOM + evChartTradeSell    :
197.                 case CHARTEVENT_CUSTOM + evChartTradeCloseAll:
198.                     if (m_ChartTrade.Decode((EnumEvents)(id - CHARTEVENT_CUSTOM), sparam)) switch (m_ChartTrade.Data.ev)
199.                     {
200.                         case evChartTradeBuy:
201.                             ToMarket(ORDER_TYPE_BUY);
202.                             break;
203.                         case evChartTradeSell:
204.                             ToMarket(ORDER_TYPE_SELL);
205.                             break;
206.                         case evChartTradeCloseAll:
207.                             CloseAllsPosition();
208.                             break;
209.                     }
210.                     break;
211.                 case CHARTEVENT_CUSTOM + evMsgClosePositionEA:
212.                     ClosePosition((ulong)(lparam));
213.                     break;
214.                 case CHARTEVENT_CUSTOM + evMsgCloseTakeProfit:
215.                     ModifyValueSLTP((ulong)(lparam), sparam, dparam, 0);
216.                     break;
217.                 case CHARTEVENT_CUSTOM + evMsgCloseStopLoss:
218.                     ModifyValueSLTP((ulong)(lparam), sparam, 0, dparam);
219.                     break;
220.                 case CHARTEVENT_CUSTOM + evMsgNewTakeProfit:
221.                     ModifyValueSLTP((ulong)(lparam), sparam, -1, dparam);
222.                     break;
223.                 case CHARTEVENT_CUSTOM + evMsgNewStopLoss:
224.                     ModifyValueSLTP((ulong)(lparam), sparam, dparam, -1);
225.                     break;
226.             }
227.         }
228. //+------------------------------------------------------------------+    
229. };
230. //+------------------------------------------------------------------+

C_Orders

Я знаю, что то, что я делаю, и то, как я это делаю, может показаться настоящим безумием. Однако я хочу показать, что никакой магической коробки не существует: мы можем создать и реализовать всё, что только можем себе представить. Всё зависит от правильного понимания того, чего мы хотим добиться. Именно поэтому программировать так интересно. Нам всегда приходится решать какие-то проблемы. Внимательно следите за кодом, потому что я не буду подробно объяснять, где были внесены изменения. После этих изменений класс C_Orders наконец-то был обновлён. Теперь вернёмся к коду заголовочного файла C_InServer.mqh, чтобы реализовать части, которые будут использоваться в системе репликации/моделирования. Таким образом, мы сможем смоделировать торговый сервер, по крайней мере для системы рыночных ордеров, что и является нашей текущей целью.


Моделирование торгового сервера

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

56. //+------------------------------------------------------------------+
57.     inline const int _PositionsTotal(void)
58.     {
59. #define macro_ERROR { Print("Error in accessing the database..."); return 0; }
60.         
61.         struct stLocal
62.         {
63.             int value;
64.         }Info;
65.         
66.         if (!m_IsReplay)
67.             return PositionsTotal();
68. 
69.         if (!ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay AS tb WHERE tb.history = 0;")) macro_ERROR
70.         if (!GetRegisterOfRequest(Info)) macro_ERROR
71.             
72.         return Info.value;
73.         
74. #undef macro_ERROR
75.     }
76. //+------------------------------------------------------------------+

Фрагмент C_InServer

Посмотрите, что я делаю в этом фрагменте. В строке 59 я определяю макрос, который будет использоваться только здесь. Каждая функция класса C_InServer будет следовать очень похожему шаблону, с небольшими отличиями в зависимости от типа возвращаемого значения. Однако макрос используется только локально, поэтому в строке 74 мы его удаляем. Теперь обратите внимание на строку 61, где мы создаём структуру. Мы будем использовать эту структуру для доступа к базе данных и определения того, сколько позиций остаётся открытыми, тем самым имитируя функцию PositionsTotal из библиотеки MQL5.

Для подсчёта мы используем строку 69. Если всё выполняется корректно, в строке 70 мы пытаемся получить возвращаемое SQL-запросом значение. Если эта операция также завершается без проблем, в строке 72 мы возвращаем результат вызывающему коду. Таким образом, вызывающий код получит соответствующее значение. С точки зрения вызывающего кода реальный и симулируемый серверы будут практически идентичны, поскольку база данных будет предоставлять данные, которые мы обычно получали бы от сервера.

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Defines.mqh"
005. #include "..\SQL\C_ReplayDataBase.mqh"
006. //+------------------------------------------------------------------+
007. class C_InServer : public C_ReplayDataBase
008. {
009.     private    :
010.         bool    m_IsReplay;
011.         struct stLocal
012.         {
013.             ulong   ticket;
014.             int     type;
015.             double  volume,
016.                     price,
017.                     sl,
018.                     tp;
019.         }m_Info;
020.     public    :
021. //+------------------------------------------------------------------+
022.         C_InServer()
023.             :C_ReplayDataBase(),
024.             m_IsReplay(_Symbol == def_SymbolReplay)
025.         {
026.             ZeroMemory(m_Info);
027.             ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( ticket, type, volume, price, sl, tp, history );");
028.         }
029. //+------------------------------------------------------------------+
030.     inline const double _PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE arg)
031.     {    
032.         if (!m_IsReplay)
033.             return PositionGetDouble(arg);
034.             
035.         switch (arg)
036.         {
037.             case POSITION_VOLUME        : return m_Info.volume;
038.             case POSITION_PRICE_OPEN    : return m_Info.price;
039.             case POSITION_SL            : return m_Info.sl;
040.             case POSITION_TP            : return m_Info.tp;
041.             case POSITION_PRICE_CURRENT : break;
042.             case POSITION_SWAP          : break;
043.             case POSITION_PROFIT        : break;
044.         }
045.         
046.         return 0;
047.     }
048. //+------------------------------------------------------------------+
049.     inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg)
050.     {
051.         if (!m_IsReplay)
052.             return PositionGetInteger(arg);
053.             
054.         switch (arg)
055.         {
056.             case POSITION_TICKET            : return (long) m_Info.ticket;
057.             case POSITION_TIME              : break;
058.             case POSITION_TIME_MSC          : break;
059.             case POSITION_TIME_UPDATE       : break;
060.             case POSITION_TIME_UPDATE_MSC   : break;
061.             case POSITION_TYPE              : return (long) m_Info.type;
062.             case POSITION_MAGIC             : break;
063.             case POSITION_IDENTIFIER        : break;
064.         }
065.         
066.         return 0;
067.     }
068. //+------------------------------------------------------------------+
069.     inline const bool _PositionSelectByTicket(ulong arg)
070.     {
071.         bool ret;
072.         
073.         if (!m_IsReplay)
074.             return PositionSelectByTicket(arg);
075. 
076.         ZeroMemory(m_Info);        
077.         if (!ExecRequestOfData(StringFormat("SELECT * FROM tb_Replay AS tb WHERE (tb.history = 0) AND (tb.ticket = %d);", arg)))
078.         {
079.              Print("Error in accessing the database...");
080.              return false;
081.          }
082.         
083.         ret = GetRegisterOfRequest(m_Info);
084. 
085.         return ret;
086.     }
087. //+------------------------------------------------------------------+
088.     inline const string _PositionGetString(ENUM_POSITION_PROPERTY_STRING arg)
089.     {
090.         if (!m_IsReplay)
091.             return PositionGetString(arg);
092.             
093.         switch (arg)
094.         {
095.             case POSITION_SYMBOL     : return def_SymbolReplay;
096.             case POSITION_COMMENT    : break;
097.             case POSITION_EXTERNAL_ID: break;
098.         }
099.         
100.         return "";
101.     }
102. //+------------------------------------------------------------------+
103.     inline const string _PositionGetSymbol(int arg)
104.     {
105.         return (!m_IsReplay ? PositionGetSymbol(arg) : (_PositionGetTicket(arg) > 0 ? def_SymbolReplay : ""));
106.     }
107. //+------------------------------------------------------------------+
108.     inline const int _PositionsTotal(void)
109.     {
110. #define macro_ERROR { Print("Error in accessing the database..."); return 0; }
111.         
112.         struct stLocal
113.         {
114.             int value;
115.         }Info;
116.         
117.         if (!m_IsReplay)
118.             return PositionsTotal();
119. 
120.         if (!ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay AS tb WHERE tb.history = 0;")) macro_ERROR
121.         if (!GetRegisterOfRequest(Info)) macro_ERROR
122.             
123.         return Info.value;
124.         
125. #undef macro_ERROR
126.     }
127. //+------------------------------------------------------------------+
128. };
129. //+------------------------------------------------------------------+

C_InServer

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

Гораздо быстрее получить данные один раз и, пока они не изменятся, извлекать их непосредственно из памяти. Примерами данных, которые изменяются нечасто, являются объём позиции, а также уровни тейк-профита и стоп-лосса. Однако показанная выше система хранит только один блок. Хотя я не могу с уверенностью сказать, сколько блоков хранит MetaTrader 5, полагаю, что он должен хранить несколько, поскольку мы можем держать открытые позиции по разным символам. В нашем случае мы будем использовать только один блок: тот, который соответствует символу, заданному для системы репликации/моделирования. Поэтому я не вижу никаких проблем в использовании одного блока памяти. Кроме того, перед выполнением любой операции даже советники должны проверять, остаётся ли сохранённый тикет позиции действительным.


Заключительные замечания

В этой статье я показал, как можно начать моделирование торгового сервера. До сих пор приложение, разрабатываемое в рамках этой серии статей, было сосредоточено исключительно на моделировании графической части. Однако, чтобы получить более полную систему, в которой мы сможем протестировать советника в сервисе репликации/моделирования, нам также необходимо смоделировать торговый сервер. Как вы, наверное, заметили, симуляция будет включать только самые необходимые элементы. Тем не менее вы, уважаемый читатель, сможете дополнить недостающие части. Поскольку эти дополнительные компоненты не влияют на то, что я хочу показать, у нас уже более чем достаточно, чтобы продолжить в следующей статье. Поэтому спокойно изучите этот материал и разберитесь, как всё это на самом деле работает. Увидимся в следующей статье этой серии, которая уже приближается к своему «грандиозному финалу».


Файл Описание
Experts\Expert Advisor.mq5
Показывает взаимодействие между Chart Trade и советником. Для взаимодействия необходим индикатор Mouse Study.
Indicators\Chart Trade.mq5 Создаёт окно, в котором настраивается отправляемый ордер. Для взаимодействия необходим индикатор Mouse Study.
Indicators\Market Replay.mq5 Создаёт элементы управления, необходимые для взаимодействия с сервисом репликации/моделирования. Для взаимодействия необходим индикатор Mouse Study.
Indicators\Mouse Study.mq5 Обеспечивает взаимодействие между графическими элементами управления и пользователем. Это необходимо как для использования системы репликации/моделирования, так и для торговли на реальном рынке.
Indicators\Order Indicator.mq5 Отвечает за отображение рыночных ордеров и позволяет взаимодействовать с ними и управлять ими.
Indicators\Position View.mq5 Отвечает за отображение рыночных позиций и позволяет взаимодействовать с ними и управлять ими.
Services\Market Replay.mq5 Создаёт и поддерживает сервис репликации/моделирования рынка. Это основной файл всей системы.

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

Прикрепленные файлы |
Anexo.zip (779.24 KB)
Разработка инструментария для анализа Price Action (Часть 73): Построение системы торговых сигналов для гэпов выходного дня в MQL5 Разработка инструментария для анализа Price Action (Часть 73): Построение системы торговых сигналов для гэпов выходного дня в MQL5
Мы расширяем инструментарий для работы с гэпами выходного дня индикатором, который преобразует структуру гэпа в торговые сигналы. Когда цена подтверждает возврат обратно в гэп, индикатор отображает стрелки Buy и Sell, устанавливает тейк-профит на противоположной границе и размещает стоп-лосс по экстремумам текущей недели. Он работает без перерисовки, восстанавливает исторические сигналы, обновляется в реальном времени и предоставляет буферы, пригодные для использования советником, с метками входа и уровнями TP/SL.
Причинный граф как признак машинного обучения Причинный граф как признак машинного обучения
В статье условный граф причинности Грейнджера по 10 валютным парам и трём таймфреймам преобразуется в числовые признаки для CatBoost и MQL5. Абляция сравнивает модели price-only, graph-only и price+graph: графовые признаки почти не работают самостоятельно, но в сочетании с ценой улучшают контекст, режимную фильтрацию и риск. На форварде совместная модель показала более высокий Profit Factor и меньшую просадку.
Возможности Мастера MQL5, которые вам нужно знать (Часть 97): Использование выпуклой оболочки и миниатюрной GRU-сети в пользовательском классе трейлинг-стопа Возможности Мастера MQL5, которые вам нужно знать (Часть 97): Использование выпуклой оболочки и миниатюрной GRU-сети в пользовательском классе трейлинг-стопа
В этой статье рассматривается пользовательский класс Мастера MQL5 для трейлинг-стопов. Пользовательский класс CTrailingConvexHullGRU создается путем объединения алгоритма выпуклой оболочки и GRU-сети. Как и в других статьях серии, разрабатывается модель, которую можно тестировать с советниками, собранными Мастером MQL5, и настраивать с помощью различных классов управления капиталом и сигналов входа. В тестировании используются классы сигналов Envelopes и RSI.
От начального до среднего уровня: Перегрузка операторов (III) От начального до среднего уровня: Перегрузка операторов (III)
В этой статье мы рассмотрим, как реализовать перегрузку как логических операторов, так и операторов сравнения. Для этого требуется определённая осторожность и немалая доля внимания. Даже небольшая оплошность при реализации перегрузки этих операторов может привести к тому, что весь код окажется совершенно непригодным для использования. Если в перегрузке возникнут какие-либо проблемы, всю базу данных, созданную на основе результатов, сгенерированных кодом, придётся либо полностью отбросить, либо, как минимум, целиком перепроверить.