English Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 32): Система ордеров (I)

Разработка системы репликации (Часть 32): Система ордеров (I)

MetaTrader 5Примеры | 6 марта 2024, 14:21
511 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 31): Проект Expert Advisor - Класс C_Mouse (V)", мы разработали базовую часть для использования мыши в системе репликации/моделирования. Здесь мы не стали показывать проблему с колесом прокрутки мыши, поскольку изначально я не видел необходимости в использовании этой функции. Теперь мы можем приступить к работе над другой частью, которая, без сомнения, значительно сложнее. И то, что на самом деле нам придется реализовать, как в коде, так и в других сопутствующих вещах, без сомнения, это самое сложное во всей системе репликации/моделирования. Без этой части невозможно провести ни одно практическое и простое исследование: речь идет о системе ордеров.

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

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

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

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

Рисунок 01

Рисунок 01: Текущая схема наследования классов советника

Но хотя данная схема очень хорошо работает для того, что было делалось до сих пор, она еще далека от того, что нам действительно нужно. Это не связано с тем, что сложно добавить класс C_Orders в эту схему наследования. На самом деле, этого можно добиться, если сделать класс C_Orders наследником класса C_Study. Но мы не хотим, чтобы это произошло, и причина кроется в очень практичном вопросе, который иногда игнорируется большинством программистов, работающих с ООП (объектно-ориентированным программированием). Данный вопрос известен: это инкапсуляция. Другими словами, мы будем знать только то, что необходимо для выполнения нашей работы. Когда мы создаем иерархию классов, мы не должны позволять некоторым классам знать больше, чем им действительно нужно знать. Мы всегда должны отдавать предпочтение тому виду программированию, где каждый из классов знает только то, что ему действительно необходимо знать для выполнения своей задачи. Поэтому сохранение инкапсуляции и одновременное добавление класса C_Orders в схему, показанную на рисунке 01, практически не оправданно. Поэтому лучшим вариантом решения данной проблемы является удаление класса C_Terminal из блока наследования, как показано на рисунке 01, и передача его в этот же блок в качестве аргумента или параметра, который может быть использован гораздо более подходящим способом. Так что контроль над тем, кто будет получать ту или иную информацию, будет осуществляться кодом советника, и это будет важно в дальнейшем, поскольку мы сможем поддерживать инкапсуляцию информации.

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

Рисунок 02

Рисунок 02: Новая схема наследования

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


Подготавливаем почву

Первое, что нужно сделать, - это создать перечисление внутри класса C_Terminal. Этом можно увидеть в следующем отрывке:

class C_Terminal
{

       protected:
      enum eErrUser {ERR_Unknown, ERR_PointerInvalid};

// ... Código interno ...

};

Данное перечисление позволит нам указывать через переменную _LastError, когда в системе по какой-либо причине возникает ошибка. Сейчас мы определим только эти два вида ошибок.

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

#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Mouse : public C_Terminal
{
   protected:
//+------------------------------------------------------------------+
// ... Fragmento Interno ....
//+------------------------------------------------------------------+
   private :
//+------------------------------------------------------------------+
// ... Fragmento interno ...
      C_Terminal *Terminal;
//+------------------------------------------------------------------+

Чтобы не повторять постоянно код, мы добавили два новых определения. Это позволит настроить многие параметры. Кроме того, была добавлена частная глобальная переменная, чтобы мы могли правильно обращаться к классу C_Terminal. Это в дополнение к тому, что мы больше не будем использовать наследование класса C_Terminal, как видно из кода выше.

Раз мы больше не используем наследование, нужно внести еще два изменения, которые необходимо отметить. Первое видно в конструкторе класса C_Mouse:

C_Mouse(C_Terminal *arg, color corH, color corP, color corN)
	:C_Terminal()
   {
      Terminal = arg;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      ZeroMemory(m_Info);
      m_Info.corLineH  = corH;
      m_Info.corTrendP = corP;
      m_Info.corTrendN = corN;
      m_Info.Study = eStudyNull;
      m_Mem.CrossHair = (bool)ChartGetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL);
      ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_MOUSE_MOVE, true);
      ChartSetInteger(def_InfoTerminal.ID, CHART_CROSSHAIR_TOOL, false);
      def_AcessTerminal.CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
   }

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

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

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

Другая модификация, которая заслуживает внимания, связана с функцией, которую мы показываем ниже:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      int w = 0;
      static double memPrice = 0;
                                
      C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
      switch (id)
      {
//....

Указанный код должен быть добавлен в советник для обработки событий, о которых сообщает платформа MetaTrader 5. Как видно, если этого не сделать, возникнут проблемы с некоторыми видами событий, что может привести к нарушению положения элемента на графике. На данный момент мы просто удалим код из этого места. Мы даже можем позволить классу C_Mouse вызывать систему обмена сообщениями класса C_Terminal. Но поскольку мы не используем наследование, это оставит код с довольно необычной зависимостью.

Точно так же, как мы сделали в классе C_Mouse, мы сделаем это в классе C_Study. Однако, это будет сделано таким образом, что единственная функция, которая действительно заслуживает внимания, - это конструктор класса, который можно увидеть ниже:

C_Study(C_Terminal *arg, color corH, color corP, color corN)
        :C_Mouse(arg, corH, corP, corN)
   {
      Terminal = arg;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      CreateObjectInfo(2, 110, def_ExpansionBtn1, clrPaleTurquoise);
      CreateObjectInfo(2, 53, def_ExpansionBtn2);
      CreateObjectInfo(58, 53, def_ExpansionBtn3);
   }

Обратите внимание: мы получаем параметр, указывающий на указатель класса C_Terminal, и передаем его классу C_Mouse. Поскольку мы унаследовали его, мы должны правильно его инициализировать, но так или иначе, мы проведем те же проверки, что и в конструкторе класса C_Mouse, чтобы убедиться, что мы используем правильный указатель. Теперь мы должны обратить внимание на один момент: если вы заметили, в обоих конструкторах, как в C_Mouse, так и в C_Study, мы проверяем значение _LastError, чтобы узнать, если что-то не так, как ожидалось. Однако, в зависимости от используемого актива, классу C_Terminal может потребоваться инициализировать свое имя, чтобы советник мог узнать, какой актив находится на графике в данный момент.

Если это случайно произойдет, переменная _LastError будет содержать значение 4301 ( ERR_MARKET_UNKNOWN_SYMBOL ), которое указывает на то, что актив не был обнаружен правильно. Но это будет неправильно, поскольку класс C_Terminal может получить доступ к нужному активу в рамках уже запрограммированного. Чтобы данная ошибка не мешала советнику оставаться на графике, нужно внести небольшое изменение в конструктор класса C_Terminal. Вот оно:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      CurrentSymbol();
      m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
      m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
      ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);
      ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
      m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
      m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
      m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
      m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
      m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
      m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
      m_Infos.AdjustToTrade = m_Infos.PointPerTick / m_Infos.ValuePerPoint;
      ResetLastError();
   }

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


Класс C_Orders

То, что мы видели до этого момента, поможет нам подготовить почву для следующего этапа. Хотя нам потребуется внести еще несколько изменений в класс C_Terminal, некоторые из них мы внесем позже в этой статье. Но давайте перейдем к созданию класса C_Orders, чтобы иметь возможность взаимодействовать с торговым сервером. В данном случае это будет сервер REAL, доступ к которому осуществляется через брокера. Однако мы можем использовать DEMO-аккаунт для тестирования системы. Не рекомендую использовать систему непосредственно на реальном счете.

Код для данного класса начинается следующим образом:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "..\C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_AcessTerminal (*Terminal)
#define def_InfoTerminal def_AcessTerminal.GetInfoTerminal()
//+------------------------------------------------------------------+
class C_Orders
{

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

Далее мы объявляем первые глобальные переменные и внутренние для класса. Этом можно увидеть в следующем отрывке:

   private :
//+------------------------------------------------------------------+
      MqlTradeRequest m_TradeRequest;
      ulong           m_MagicNumber;
      C_Terminal      *Terminal;

Обратите внимание на то, что эти глобальные переменные объявлены как приватные, т.е. к ним нельзя получить доступ за пределами кода класса. Одна деталь, которая заслуживает внимания, - это то, как объявляется переменная, которая будет предоставлять доступ к классу C_Terminal. Учтите, что на самом деле она объявлена как указатель, хотя использование указателей в MQL5 происходит не так, как в C/C++.

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) PrintFormat("Order System - Error Number: %d", _LastError);
      return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
   }

Приведенная выше функция, которая будет частной, служит для «централизации» вызовов. Решение о централизации вызовов обусловлено тем, что в дальнейшем так будет легче адаптировать систему. Это необходимо, чтобы иметь возможность использовать одну и ту же схему как для работы с реальным сервером, так и с сервером, который смоделирован. Эта предыдущая функция была удалена, как и другие, что мы также увидим из серии статей, последняя из которых:  Как построить советник, работающий автоматически (Часть 15): Автоматизация (VII)". В данной статье объясняется, как из ручного советника создается автоматический. Мы возьмем несколько процедур, присутствующих в этой серии, чтобы ускорить работу, которую нам предстоит выполнить здесь. Таким образом, если мы хотим использовать те же концепции, мы сможем протестировать с помощью системы репликации/моделирования автоматический советник, без необходимости использовать тестер стратегий из MetaTrader 5.

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

inline void CommonData(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      double Desloc;
                                
      ZeroMemory(m_TradeRequest);
      m_TradeRequest.magic        = m_MagicNumber;
      m_TradeRequest.symbol       = def_InfoTerminal.szSymbol;
      m_TradeRequest.volume       = NormalizeDouble(def_InfoTerminal.VolumeMinimal + (def_InfoTerminal.VolumeMinimal * (Leverage - 1)), def_InfoTerminal.nDigits);
      m_TradeRequest.price        = NormalizeDouble(Price, def_InfoTerminal.nDigits);
      Desloc = def_AcessTerminal.FinanceToPoints(FinanceStop, Leverage);
      m_TradeRequest.sl           = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), def_InfoTerminal.nDigits);
      Desloc = def_AcessTerminal.FinanceToPoints(FinanceTake, Leverage);
      m_TradeRequest.tp           = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), def_InfoTerminal.nDigits);
      m_TradeRequest.type_time    = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
      m_TradeRequest.stoplimit    = 0;
      m_TradeRequest.expiration   = 0;
      m_TradeRequest.type_filling = ORDER_FILLING_RETURN;
      m_TradeRequest.deviation    = 1000;
      m_TradeRequest.comment      = "Order Generated by Experts Advisor.";
   }

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

Давайте теперь посмотрим на эту функцию. Она присутствует в классе C_Terminal. Ее код можно увидеть ниже:

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

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

m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;

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

C_Orders(C_Terminal *arg, const ulong magic)
         :m_MagicNumber(magic)
   {
      if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   }

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

Итак, мы видим две последние функции, присутствующие в классе на данном этапе развития. Первая из них такая:

ulong ToMarket(const ENUM_ORDER_TYPE type, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      CommonData(type, SymbolInfoDouble(def_InfoTerminal.szSymbol, (type == ORDER_TYPE_BUY ? SYMBOL_ASK : SYMBOL_BID)), FinanceStop, FinanceTake, Leverage, IsDayTrade);
      m_TradeRequest.action   = TRADE_ACTION_DEAL;
      m_TradeRequest.type     = type;

      return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
   };

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

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

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

Это приводит нас к последней функции этого класса C_Orders. Ниже показываем текущий этап развития:

ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
   {
      double  bid, ask;
                                
      bid = SymbolInfoDouble(def_InfoTerminal.szSymbol, (def_InfoTerminal.ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : SYMBOL_BID));
      ask = (def_InfoTerminal.ChartMode == SYMBOL_CHART_MODE_LAST ? bid : SymbolInfoDouble(def_InfoTerminal.szSymbol, SYMBOL_ASK));
      CommonData(type, def_AcessTerminal.AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
      m_TradeRequest.action   = TRADE_ACTION_PENDING;
      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));                                
                                
      return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
   };

Здесь есть несколько вещей, которые очень похожи на функцию исполнения по рыночной цене, например, тот факт, что лимиты задаются в экономических величинах и что мы должны указать, будем ли мы покупать или продавать, используя только одно из этих двух значений. Несмотря на это, можно заметить: то, что мы делаем слегка отличается от подавляющего большинства кодов, присутствующих в советнике. Обычно, когда мы создаем советник, он ориентирован для использование на очень специфическом виде рынка, будь то рынок Форекс или фондовая биржа. Поскольку в MetaTrader 5 мы можем использовать оба вида рынка, нам необходимо провести некую стандартизацию, чтобы облегчить себе жизнь. Разве работа на Форекс не то же самое, что и работа на фондовом рынке? С точки зрения пользователя - ДА, но с точки зрения программирования - НЕТ. Если повнимательнее присмотреться, то можно увидеть, что мы проверяем, какой вид рисования используется в настоящее время. Проведя данный тест, мы сможем выяснить, работает ли наша система с последней ценой или же она основана на значениях BID и ASK. Знать это важно не для того, чтобы устанавливать ордер, а для того, чтобы знать, какой вид ордера использовать. В будущем нам нужно будет внедрить такие ордеры в систему, чтобы имитировать работу торгового сервера. Но на данном этапе всё, что нам нужно знать, - это то, что вид ордера одинаково важен, как и цена, по которой он будет исполнен. Если мы поставим цену в правильное место, но ошибемся с видом ордера, то возникнет проблема, поскольку ордер будет исполнен в другое время, отличающееся от того, что вы ожидали или представляли себе, что он будет на самом деле исполнен сервером.

Очень часто новые пользователи MetaTrader 5 допускают ошибки при заполнении отложенных ордеров. Но не на любом рынке, так как со временем вы привыкнете к рынку и не будете так легко совершать ошибки. Однако, когда мы переходим с одного рынка на другой, всё становится сложнее. Если система рисования имеет вид BID-ASK, способ установки вида ордера отличается от системы рисования LAST. Различия едва заметны, но они есть, и приводят к тому, что ордер не остается отложенным, а исполняется по рыночной цене, даже если предполагалось выставить отложенный ордер.


Заключение

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

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

В следующей статье мы рассмотрим, как заставить данную систему ордеров начать взаимодействовать с торговым сервером. По крайней мере, на физическом уровне, чтобы мы могли использовать советник на ДЕМО или РЕАЛЬНОМ счете. Там мы начнем разбираться с тем, как работают типы ордеров, чтобы иметь возможность войти в смоделированную систему. Если сделать наоборот или поместить смоделированную и реальную системы вместе, то возникшая путаница не позволит всем следовать объяснениям. До встречи в следующей статье!


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

Прикрепленные файлы |
Anexo.zip (130.63 KB)
Разработка MQTT-клиента для MetaTrader 5: методология TDD (Часть 4) Разработка MQTT-клиента для MetaTrader 5: методология TDD (Часть 4)
Статья является четвертой частью серии, описывающей этапы разработки нативного MQL5-клиента для протокола MQTT. В этой части мы рассматриваем свойства MQTT v5.0, их семантику, то, как мы читаем некоторые из них, а также приводим краткий пример того, как свойства можно использовать для расширения протокола.
Разработка системы репликации (Часть 31): Проект советника — класс C_Mouse (V) Разработка системы репликации (Часть 31): Проект советника — класс C_Mouse (V)
Разрабатывать способ установки таймера необходимо таким образом, чтобы во время репликации/моделирования он мог сообщить нам, сколько времени осталось, что может показаться на первый взгляд простым и быстрым решением. Многие просто пытаются приспособиться и использовать ту же систему, что и в случае с торговым сервером. Но есть один момент, который многие не учитывают, когда думают о таком решении: при репликации, и это не говоря уже о моделировании, часы работают по-другому. Всё это усложняет создание подобной системы.
Нейросети — это просто (Часть 80): Генеративно-состязательная модель Трансформера графов (GTGAN) Нейросети — это просто (Часть 80): Генеративно-состязательная модель Трансформера графов (GTGAN)
В данной статье я предлагаю Вам познакомиться с алгоритмом GTGAN, который был представлен в январе 2024 года для решения сложных задач по созданию архитектурного макета с ограничениями на граф.
Разработка системы репликации (Часть 30): Проект советника — класс C_Mouse (IV) Разработка системы репликации (Часть 30): Проект советника — класс C_Mouse (IV)
Сегодня мы изучим технику, которая может очень сильно помочь нам на разных этапах нашей профессиональной жизни в качестве программиста. Вопреки мнению многих, ограничена не сама платформа, а знания человека, который говорит об ограничениях. В данной статье будет рассказано о том, что с помощью здравого смысла и творческого подхода можно сделать платформу MetaTrader 5 гораздо более интересной и универсальной, не прибегая к созданию безумных программ или чего-то подобного, и создать простой, но безопасный и надежный код. Мы будем использовать свою изобретательность, чтобы изменить уже существующий код, не удаляя и не добавляя ни одной строки в исходный код.