English Español Português
preview
Разработка системы репликации (Часть 36): Внесение корректировок (II)

Разработка системы репликации (Часть 36): Внесение корректировок (II)

MetaTrader 5Примеры | 24 апреля 2024, 14:54
212 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 35): Внесение корректировок" мы исправили и устранили некоторые моменты, которые заставляли систему работать довольно необычным образом. Хотя эти сделанные исправления улучшили работу пользователей, мы еще не полностью переработали всё, что связано с системой репликации/моделирования.

Дело в том, что, несмотря на улучшение пользовательского опыта, у нас всё еще есть небольшая проблема. И в этой статье мы решим данную задачу, которая, несмотря на относительную простоту (в теории), окажется довольно сложной для правильного решения. Таким образом, мы расширим наши знания о MetaTrader 5 и научимся более детально использовать язык MQL5.


Отображение типа рынка

Когда советник будет запущен на графике, он сообщит о типе счета. Это важно для того, чтобы знать, как должен действовать советник. И хотя это работает очень хорошо, когда система запускается на графике актива на РЕАЛЬНОМ или на DEMO-счете, система не использует то, что управляет системой репликации/моделирования, и сообщает не о типе счета, к которому принадлежит актив, а о типе счета, на котором работает платформа. Данная проблема хоть и небольшая, но доставляет нам некоторые неудобства.

Может показаться, что решение данной проблемы не слишком сложное. Нужно убедиться, что система репликации/моделирования каким-то образом сообщает вам, какой тип счета является правильным. Это, конечно, зависит от используемого актива. На самом деле, эта идея проста. Но посмотрим ее ее на практике — это будет уже другая история. Правда в том, что заставить систему репликации/моделирования сказать нам, какой тип счета использовать, не так-то просто. Но, к счастью, платформа MetaTrader 5 позволяет реализовать решение, которое будет уместным и правдоподобным для реального использования.

Мы реализуем решение таким образом, чтобы на определенных типа х счетов не позволялись определенные действия. Это очень будет важно, когда мы будем создавать системы ордеров. Итак, в чем же суть всего этого? Система репликации/моделирования сможет использовать активы с разных рынков. То есть возможна ситуация работы с теми активами, которые используются на счетах NETTING или HEDGING.

Примечание. Поскольку тип EXCHANGE очень похож на NETTING, мы не будем использоваьт этот тип EXCHANGE. Его будем считать типом NETTING.

Как пользователь системы репликации/моделирования, вы знаете, какой тип рынка, а точнее, какой вид счета, использует актив. Затем мы можем добавить возможность для пользователя сообщать об этом в систему репликации/моделирования. Это самая простая часть, поскольку всё, что нам нужно сделать, - это добавить новую команду в конфигурационный файл актива. Но это не гарантирует, что информация будет доступна тем, кому она действительно нужна. А кто использует эту информацию в нашей системе репликации/моделирования? Эту информацию использует советник, а именно класс C_Manager вместе с классом C_Orders. Это те, кто действительно активно использует эту специфическую информацию. Даже если это не сделано в данный момент, необходимо думать более глобально и обобщенно. Где мы можем использовать что-то из увиденного:  Как построить советник, работающий автоматически

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

Здесь нам действительно нужно остановиться и подумать. Если посмотреть на код, то можно узнать тип счета. Посмотрите на следующий фрагмент:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                
      switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
         case ACCOUNT_MARGIN_MODE_EXCHANGE      : szInfo = "EXCHANGE";           break;
      }
      Print("Detected Account ", szInfo);
   }

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

В этом и заключается недостаток. Теперь об идеях: можно сказать, что советник должен читать конфигурационный файл актива репликации/моделирования, и это решит проблему. В каком-то смысле это было бы уместно, да, и только если для этого использовался один файл, а это не так. Можно подумать: давайте поэтому попросим сервис репликации/моделирования передать информацию о типе счета. Таким образом, советник будет знать, какой тип является правильным. Но здесь есть небольшой момент. В отличие от C/C++, в MQL5 нет определенных видов построения кода. Дело не в том, что вы совсем не способны использовать некоторые виды написания кода для передачи информации между программами. Мы уже видели, что это возможно, и не один, а несколько раз. Однако проблема в том, что наша информация должна содержаться в 8-байтовом блоке. Таким образом, мы сможем использовать глобальные переменные терминала для передачи информации между программами. Не забудьте, что для этого мы будем использовать язык MQL5. Есть и другие способы сделать это, но здесь я хочу использовать возможности платформы и языка MQL5.

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

Проблема заключается в этих булевых значениях. В отличие от C/C++, где каждая булева величина может содержаться в одном бите, в MQL5 каждая булева величина будет занимать восемь битов. Может быть, вы не понимаете, что это значит на самом деле, но целочисленный байт будет использоваться, когда нам нужен только один бит, чтобы сохранить то, что нам нужно. С этим и связана проблема. Помните, что для передачи информации типа ushort потребуется два байта. Структура st_0 на самом деле занимает четыре байта, а не три, как мы ожидали. Если мы добавим в эту структуру st_0 еще четыре, и только четыре булевых числа, то окажемся на пределе восьми байт, которыми мы можем пользоваться.

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

Так что вот намек на улучшение для MQL5. Нечто, казалось бы, тривиальное, но очень полезное в программировании на этом языке.

Давайте теперь вернемся к нашему коду, ведь мы всё равно можем работать с тем, что у нас есть под рукой. Новый код для файла Interprocess.mqh можно найти ниже:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + "_Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + "_ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + "_Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market_" + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
        union u_0
        {
                double  df_Value;       // Value of the terminal global variable...
                ulong   IdGraphic;      // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {
                bool    isPlay;         // Indicates whether we are in Play or Pause mode...
                bool    isWait;         // Tells the user to wait...
                bool    isHedging;      // If true we are in a Hedging account, if false the account is Netting...
                bool    isSync;         // If true indicates that the service is synchronized...
                ushort  iPosShift;      // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};
//+------------------------------------------------------------------+

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

ВАЖНОЕ ЗАМЕЧАНИЕ: Для тех, кто не знает, как выглядел бы этот же код, если бы язык MQL5 использовал подобное моделирование C/C++ для определения булевых структур, чтобы компилятор делал работу по корректировке кода за нас, отметим, что чтение, как и этап написания кода, не пострадали бы никак. Но тот же самый код, который занимает шесть байт, занял бы три, если бы его можно было оформить кодом следующим образом:

union u_Interprocess
{
        union u_0
        {
                double  df_Value;      // Value of the terminal global variable...
                ulong   IdGraphic;     // Contains the Graph ID of the asset...
        }u_Value;
        struct st_0
        {           
    		char  isPlay    : 1// Indicates whether we are in Play or Pause mode...
    		char  isWait    : 1// Tells the user to wait...                
    		char  isHedging : 1;   // If true we are in a Hedging account, if false the account is Netting...                
    		char  isSync    : 1// If true indicates that the service is synchronized...
                ushort  iPosShift;     // Value between 0 and 400...
        }s_Infos;
        datetime ServerTime;
};

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

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

Но если мир дарит нам лимоны, давайте сделаем лимонад. И давайте не будем сетовать на то, что мир не такой, каким мы его ожидаем. Вносим эти изменения в файл Interprocess.mqh. Нам нужно перейти к другому вопросу. Теперь мы изменим файл C_ConfigService.mqh. В данном файле мы сделаем несколько изменений, начиная с кода, показанного ниже:

        private :
                enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
                enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
                struct st001
                {
                        C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
                        int     Line;
                }m_GlPrivate;
                string  m_szPath;
                bool    m_AccountHedging;

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

C_ConfigService()
        :m_szPath(NULL),
         m_ModelLoading(1),
         m_AccountHedging(false)
   {
   }

А то, что нам нужна переменная, которая будет служить памятью, объясняется тем, что нам нужен индикатор Control для создания глобальной переменной терминала, в которую будут поступать уже настроенные данные. Однако эта глобальная терминальная переменная будет появляться только тогда, когда шаблон загрузит индикатор управления на график. И график появится только после того, как сервис репликации/моделирования закончит настройку всей системы. Если бы значение для настройки, указанное пользователем, не хранилось где-нибудь, мы не смогли бы сообщить советнику вид счета, но при тщательном программировании мы можем передать советнику вид счета, настроенный в сервисе репликации/моделирования. Тогда мы будем знать, как работать. Но это при условии, что вы воспользуетесь знаниями которые мы вам дали о том, как создать автоматический советник. И вы уже подумаете о том, как протестировать разработанное вами приложение.

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

inline const bool TypeAccountIsHedging(void) const
   {
      return m_AccountHedging;
   }

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

inline bool Configs(const string szInfo)
   {
      const string szList[] = {
                               "PATH",
                               "POINTSPERTICK",
                               "VALUEPERPOINTS",
                               "VOLUMEMINIMAL",
                               "LOADMODEL",
                               "ACCOUNT"
                              };
      string  szRet[];
      char    cWho;
                                
      if (StringSplit(szInfo, '=', szRet) == 2)
      {
         StringTrimRight(szRet[0]);
         StringTrimLeft(szRet[1]);
         for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
         switch (cWho)
         {
            case 0:
               m_szPath = szRet[1];
               return true;
            case 1:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
               return true;
            case 2:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
               return true;
            case 3:
               CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
               return true;
            case 4:
               m_ModelLoading = StringInit(szRet[1]);
               m_ModelLoading = ((m_ModelLoading < 1) && (m_ModelLoading > 4) ? 1 : m_ModelLoading);
               return true;
            case 5:
               if (szRet[1] == "HEDGING") m_AccountHedging = true;
               else if (szRet[1] == "NETTING") m_AccountHedging = false;
               else
               {
                  Print("Entered account type is not invalid.");                                                          
                  return false;
               }
               return true;                    
         }
         Print("Variable >>", szRet[0], "<< not defined.");
      }else
         Print("Configuration definition >>", szInfo, "<< invalidates.");
                                        
      return false;
   }

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

[Config]
Path = Forex\EURUSD
PointsPerTick = 0.00001
ValuePerPoints = 1.0
VolumeMinimal = 0.01
Account = HEDGING

или нечто очень похожее на то, что показано ниже:

[Config]
Path = Petrobras PN
PointsPerTick = 0.01
ValuePerPoints = 1.0
VolumeMinimal = 100.0
Account = NETTING

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

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = ULONG_MAX;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
      info.s_Infos.isHedging = TypeAccountIsHedging();
      info.s_Infos.isSync = true;
      GlobalVariableSet(def_GlobalVariableReplay, info.u_Value.df_Value);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

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


Информируем советника о типе счета

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

Рисунок 01

Рисунок 01 - Процесс инициализации системы репликации/моделирования.

Почему так важно понимать этот первый рисунок? Причина в том, что класс C_Terminal является общим классом для индикатора управления и советника. Если не разобраться с этим, то не получится понять, как и почему системе удается закрепиться на платформе MetaTrader 5, чтобы следовать тому, что настроил пользователь. Более того, если мы не поймем эту инициализацию, мы можем наделать глупостей, редактируя файл, который будет настраивать репликацию/моделирование. При этом система будет работать не так, как должна. Таким образом, вместо получения опыта, близкого к реальному рынку, мы можем получить ложное представление о том, как мы должны действовать и вести себя, когда будем применять на практике то, что мы изучали в процессе репликации/моделирования. Итак, нудно обязательно понять, что из себя представляет первый рисунок.

  • Все стрелки синего цвета указывают на первый этап инициализации, это происходит в тот самый момент, когда мы, как пользователь, запускаем сервис в MetaTrader 5.
  • После этого мы переходим ко второму этапу, который выполняется, когда сервис создает график и использует шаблон для инициализации советника и индикатора управления. Это показывается желтой стрелкой.
  • Следующий этап, обозначенный ЗЕЛЕНЫМИ стрелками, состоит в создании глобальной переменной терминала с помощью индикатора управления и конфигурацией этой переменной сервисом репликации/моделирования. В этот момент мы видим, что класс C_Terminal отправляет данные индикатору управления, но не классу C_Manager, который в данный момент инициализируется советником.
  • Последний этап (ФИОЛЕТОВЫЕ стрелки) - это настройка из данных, предоставленных пользователем в файле, который указывает как будет работать репликация/моделирование, от информации, необходимой советнику для того, чтобы узнать, какой вид счета используется.

Хотя это может показаться запутанным, мы должны быть предельно внимательны, чтобы класс C_Terminal позволял индикатору Control выполнять свою работу. Но, в то же время, не позволяйте советнику делать что-либо до того, как класс C_Manager будет правильно настроен. И почему я говорю, что управление должно осуществляться в классе C_Terminal, а не в классе C_Manager? Возможно, было бы проще сделать это в классе C_Manager. Почему бы и нет! Причина не просто в том, чтобы выбрать, в каком классе хранить вещи, настоящей причиной является класс C_Orders. Если система управления находится в классе C_Manager, класс C_Orders не сможет получить доступ к нужным нам данным, но тот факт, что мы поместили управление в класс C_Terminal, означает, что даже если впоследствии мы будем использовать другой класс для управления ордерами, он всё равно сможет получить доступ к нужным нам данным. Однако, мы не можем, как показано на рисунке 01, выполнять работу в любом случае; мы должны убедиться, что она выполнена как можно лучше. Иначе вся система может выйти из строя сразу же после запуска. Я знаю, что трудно понять и даже может шокировать, но поверьте, если программирование не будет вестись достаточно строго, система рухнет. Так можно увидеть, как реализована система. Начнем со следующего кода:

class C_Terminal
{
        protected:
                enum eErrUser {ERR_Unknown, ERR_PointerInvalid};
                enum eEvents {ev_Update};
//+------------------------------------------------------------------+
                struct st_Terminal
                {
                        ENUM_SYMBOL_CHART_MODE   ChartMode;
                        ENUM_ACCOUNT_MARGIN_MODE TypeAccount;
                        long    ID;
                        string  szSymbol;
                        int     Width,
                                Height,
                                nDigits;
                        double  PointPerTick,
                                ValuePerPoint,
                                VolumeMinimal,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
                st_Terminal m_Infos;
                struct mem
                {
                        long    Show_Descr,
                                Show_Date;
                        bool    AccountLock;
                }m_Mem;

Данная переменная предоставляет доступ к виду счета средствами, уже реализованными в этой статье. Таким образом, нам не нужно будет внедрять новый код в класс, чтобы получить эту информацию, что очень здорово, но у нас есть и другая переменная. Она будет служить БЛОКИРОВКОЙ, но вскоре вы поймете, зачем и как она будет использоваться. Теперь нам нужно посмотреть на конструктор класса, который показан ниже:

C_Terminal()
   {
      m_Infos.ID = ChartID();
      m_Mem.AccountLock = false;
      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_EVENT_OBJECT_CREATE, 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.ValuePerPoint / m_Infos.PointPerTick;
      m_Infos.ChartMode     = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
      if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
      ResetLastError();
   }

Здесь мы инициализируем переменную lock, чтобы указать классу C_Terminal, что значение вида счета еще не реализовано. Но мы что-то с этим делаем, и это очень важно. Когда система обнаруживает, что актив НЕ ЯВЛЯЕТСЯ АКТИВОМ ВОСПРОИЗВЕДЕНИЯ, класс C_Terminal должен инициализировать данные счета. Если это актив воспроизведения, то данные будут инициализированы не сейчас, а позже в классе C_Manager. Это нужно понимать обязательно, потому что очень скоро это будет иметь огромное значение. Класс C_Manager инициализирует данные, если актив является активом воспроизведения. Если актив не является активом воспроизведения, он инициализируется на этом этапе, но в любом случае будет вызвана процедура, как показано ниже:

inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
   {
      if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
      m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
   }

Данная процедура очень важна. Без него вид используемого счета был бы неизвестен. Поскольку в этом случае предполагается, что используется тот или иной вид. Однако важно не изменять переменную вида счета после ее инициализации. Для этого мы используем вот этот лок. Обратите внимание и я это подчеркиваю: только если переменная lock установлена в false, мы сможем инициализировать ее значение на основе аргумента, переданного процедуре. После этого переменная не может быть изменена до конца выполнения кода. Еще одна деталь заключается в том, что мы ограничимся видами NETTING и HEDGING. Причина в том, что тип EXCHANGE работает так же, как и тип NETTING. Поскольку мы используем систему для торговли, очень близкой к реальному рынку, я не вижу проблемы в том, чтобы ограничиться видами NETTING и HEDGING. На этом мы завершаем кодовую часть класса C_Terminal. Теперь давайте посмотрим, что нужно изменить в классе C_Manager. По сути, в данном классе C_Manager, всё, что нам нужно было сделать, это изменить код конструктора. Теперь он будет выглядеть так:

C_Manager(C_Terminal *arg1, C_Study *arg2, color cPrice, color cStop, color cTake, const ulong magic, const double FinanceStop, const double FinanceTake, uint Leverage, bool IsDayTrade)
         :C_ControlOfTime(arg1, magic)
   {
      string szInfo = "HEDGING";
      u_Interprocess info;
                                
      Terminal = arg1;
      Study = arg2;
      if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
      if (_LastError != ERR_SUCCESS) return;
      m_Infos.FinanceStop     = FinanceStop;
      m_Infos.FinanceTake     = FinanceTake;
      m_Infos.Leverage        = Leverage;
      m_Infos.IsDayTrade      = IsDayTrade;
      m_Infos.AccountHedging  = false;
      m_Objects.corPrice      = cPrice;
      m_Objects.corStop       = cStop;
      m_Objects.corTake       = cTake;
      m_Objects.bCreate       = false;                                                                                        
      if (def_InfoTerminal.szSymbol == def_SymbolReplay)
      {
         do
         {
            while ((!GlobalVariableGet(def_GlobalVariableReplay, info.u_Value.df_Value)) && (!_StopFlag)) Sleep(750);
         }while ((!info.s_Infos.isSync) && (!_StopFlag));
         def_AcessTerminal.SetTypeAccount(info.s_Infos.isHedging ? ACCOUNT_MARGIN_MODE_RETAIL_HEDGING : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
      };
      switch (def_InfoTerminal.TypeAccount)
      {
         case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; break;
         case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING";            break;
      }
      Print("Detected Account ", szInfo);
   }

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

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


Заключение

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

Это может привести к проблемам в будущем. Так что не забывайте обновляться.


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

Прикрепленные файлы |
Files_-_FOREX.zip (3744 KB)
Files_-_BOLSA.zip (1358.28 KB)
Files_-_FUTUROS.zip (11397.55 KB)
Разрабатываем мультивалютный советник (Часть 9): Сбор результатов оптимизации одиночных экземпляров торговой стратегии Разрабатываем мультивалютный советник (Часть 9): Сбор результатов оптимизации одиночных экземпляров торговой стратегии
Наметим основные этапы по разработке нашего советника. Одним из первых будет проведение оптимизации одиночного экземпляра разработанной торговой стратегии. Попробуем собрать в одном месте всю необходимую информацию о проходах тестера при оптимизации.
Разработка системы репликации (Часть 35): Внесение корректировок (I) Разработка системы репликации (Часть 35): Внесение корректировок (I)
Прежде чем мы сможем двигаться дальше, нам нужно исправить несколько моментов. Но это не обязательные исправления, а улучшение в способе управления и использования класса. Причина в том, что сбои происходят из-за какого-то взаимодействия внутри системы. Несмотря на попытки узнать причину некоторых неудач, для их последующего устранения, все эти попытки оказались безуспешными, поскольку некоторые из них не имели смысла. Когда мы используем указатели или рекурсию в C / C++, программа аварийно завершается.
Разработка системы репликации (Часть 37): Прокладываем путь (I) Разработка системы репликации (Часть 37): Прокладываем путь (I)
В этой статье мы начнем делать то, что хотелось сделать гораздо раньше. Однако из-за отсутствия "твердой почвы" я не чувствовал себя уверенно, чтобы представить вопрос публично. Теперь у меня есть основа для того, чтобы делать то, что мы начнем сейчас. Неплохо бы максимально сосредоточиться на понимании содержания этой статьи, и я говорю это не для того, чтобы вы просто это прочитали. Я хочу подчеркнуть, что если вы не поймете данную статью, то можете полностью отказаться от надежды понять содержание следующих статей.
Разработка системы репликации (Часть 34): Система ордеров (III) Разработка системы репликации (Часть 34): Система ордеров (III)
В этой статье мы завершим первый этап конструкции. Несмотря на то, что это выполняется довольно быстро, я расскажу о деталях, которые не обсуждались ранее. Но здесь я объясню некоторые моменты, которые многие не понимают. Например, знаете ли вы, почему вам приходится нажимать клавишу Shift или Ctrl на клавиатуре?