English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 63): Нажатие кнопки воспроизведения в сервисе (IV)

Разработка системы репликации (Часть 63): Нажатие кнопки воспроизведения в сервисе (IV)

MetaTrader 5Примеры | 4 февраля 2025, 16:20
198 0
Daniel Jose
Daniel Jose

Введение

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

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

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

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


Каким же должно быть минимальное количество фактически созданных тиков?

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

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

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

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

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

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

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

128. //+------------------------------------------------------------------+
129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume)
130.          {
131.             int    i0, i1, i2, dm = 0;
132.             bool   b0;
133.             
134.             m_Marks.iMax   = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume));
135.             dm = (dm == 0 ? ((rate.open == rate.high == rate.low == rate.close) ? 1 : dm) : dm);
136.             dm = (dm == 0 ? (((rate.open == rate.high) && (rate.low == rate.close)) || ((rate.open == rate.low) && (rate.close == rate.high)) ? 2 : dm) : dm);
137.             if ((dm == 0 ? ((rate.open == rate.close == rate.high) || (rate.open == rate.close == rate.low) ? 3 : 4) : dm) == 0) return -1;            
138.             m_Marks.iMax   = (MaxTickVolume <= dm ? dm : MaxTickVolume);
139.             m_Marks.iMax   = (((int)rate.tick_volume > m_Marks.iMax) || ((int)rate.tick_volume < dm) ? m_Marks.iMax : (int)rate.tick_volume - 1);
140.             m_Marks.bHigh    = (rate.open == rate.high) || (rate.close == rate.high);
141.             m_Marks.bLow    = (rate.open == rate.low) || (rate.close == rate.low);
142.             Simulation_Time(rate, tick);
143.             MountPrice(0, rate.open, rate.spread, tick);
144.             if (m_Marks.iMax > 10)
145.             {
146.                i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2));
147.                i1 = m_Marks.iMax - i0;
148.                i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0);
149.                i2 = (i2 == 0 ? 1 : i2);
150.                b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low));
151.                i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2);
152.                RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, 1, i2);
153.                RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2);
154.                m_Marks.bHigh = m_Marks.bLow = true;
155. 
156.             }else Random_Price(rate, tick);
157.             if (!m_IsPriceBID) DistributeVolumeReal(rate, tick);
158.             if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick);
159.             if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick);
160.             MountPrice(m_Marks.iMax, rate.close, rate.spread, tick);
161.             CorretTime(tick);
162. 
163.             return m_Marks.iMax;
164.          }
165. //+------------------------------------------------------------------+

Фрагмент файла C_Simulation.mqh

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

Однако, есть одна важная деталь, которую следует иметь в виду. Обратите внимание, что в строке 138, во время тестирования тернарного оператора, значение MaxTickVolume сравнивается со значением, которое мы только что настроили, чтобы определить минимальное количество тиков. Если MaxTickVolume меньше этого настроенного значения, будет использоваться именно оно, независимо от любой другой информации. Фактически, в строке 139 мы снова проверяем это же условие. Таким образом, если значение rate.tick_volume также окажется меньше настроенного значения, настроенное значение будет иметь приоритет.

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

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

Итак, мы решили нашу первую проблему. Теперь давайте перейдем к следующей теме, чтобы решить вторую.


Устраняем ошибку в настройке тиков

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

01. //+------------------------------------------------------------------+
02.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
03.          {
04.             int      MemNRates,
05.                      MemNTicks,
06.                      nDigits,
07.                      nShift;
08.             datetime dtRet = TimeCurrent();
09.             MqlRates RatesLocal[],
10.                      rate;
11.             MqlTick  TicksLocal[];
12.             bool     bNew;
13.             
14.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
15.             nShift = MemNTicks = m_Ticks.nTicks;
16.             if (!Open(szFileNameCSV)) return 0;
17.             if (!ReadAllsTicks()) return 0;         
18.             rate.time = 0;
19.             nDigits = SetSymbolInfos(); 
20.             ArrayResize(TicksLocal, def_MaxSizeArray);
21.             m_Ticks.bTickReal = true;
22.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
23.             {
24.                if (!BuildBar1Min(c0, rate, bNew)) continue;
25.                if (bNew)
26.                {
27.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
28.                   {
29.                      nShift = MemShift;
30.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
31.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) < 0) return 0;
32.                      ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
33.                      nShift += c1;
34.                      delete pSimulator;
35.                   }
36.                   MemShift = nShift;
37.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
38.                };
39.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
40.             }
41.             ArrayFree(TicksLocal);
42.             if (!ToReplay)
43.             {
44.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
45.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
46.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
47.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
48.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
49.                m_Ticks.nTicks = MemNTicks;
50.                ArrayFree(RatesLocal);
51.             }else m_Ticks.nTicks = nShift;
52.                            
53.             return dtRet;
54.          };
55. //+------------------------------------------------------------------+

Фрагмент файла C_FileTicks.mqh

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

01. //+------------------------------------------------------------------+
02.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
03.          {
04.             int      MemNRates,
05.                      MemNTicks,
06.                      nDigits,
07.                      nShift;
08.             datetime dtRet = TimeCurrent();
09.             MqlRates RatesLocal[],
10.                      rate;
11.             MqlTick  TicksLocal[];
12.             bool     bNew;
13.             
14.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
15.             nShift = MemNTicks = m_Ticks.nTicks;
16.             if (!Open(szFileNameCSV)) return 0;
17.             if (!ReadAllsTicks()) return 0;         
18.             rate.time = 0;
19.             nDigits = SetSymbolInfos(); 
20.             m_Ticks.bTickReal = true;
21.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
22.             {
23.                if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
24.                if (!BuildBar1Min(c0, rate, bNew)) continue;
25.                if (bNew)
26.                {
27.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
28.                   {
29.                      nShift = MemShift;
30.                      ArrayResize(TicksLocal, def_MaxSizeArray);
31.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
32.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
33.                         nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
34.                      delete pSimulator;
35.                      ArrayFree(TicksLocal);
36.                      if (c1 < 0) return 0;
37.                   }
38.                   MemShift = nShift;
39.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
40.                };
41.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
42.             }
43.             if (!ToReplay)
44.             {
45.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
46.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
47.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
48.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
49.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
50.                m_Ticks.nTicks = MemNTicks;
51.                ArrayFree(RatesLocal);
52.             }else m_Ticks.nTicks = nShift;
53.                            
54.             return dtRet;
55.          };
56. //+------------------------------------------------------------------+

Фрагмент файла C_FileTicks.mqh (финальный)

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

Если же вы считаете такую трату времени неприемлемой, смело меняйте порядок событий. В любом случае, в этом исправленном фрагменте, показанном ниже, мы не должны пропускать вызов деструктора класса моделирования в случае возникновения сбоя. Если сравнить фрагменты, то можно заметить, что возврат происходит только в случае неудачи, в строке 36. Но перед этим, в строках 34 и 35, мы вызываем деструктор и освобождаем выделенную память. Именно в таком порядке.

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

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

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_FileBars.mqh"
005. #include "C_Simulation.mqh"
006. //+------------------------------------------------------------------+
007. #define macroRemoveSec(A)   (A - (A % 60))
008. #define def_MaxSizeArray    16777216 // 16 Mbytes
009. //+------------------------------------------------------------------+
010. class C_FileTicks
011. {
012.    protected:
013.       enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
014.       struct stInfoTicks
015.       {
016.          MqlTick     Info[];
017.          MqlRates    Rate[];
018.          int         nTicks,
019.                      nRate;
020.          bool        bTickReal;
021.          ePlotType   ModePlot;
022.       };
023. //+------------------------------------------------------------------+
024. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew)
025.          {
026.             double    dClose = 0;
027.             
028.             switch (m_Ticks.ModePlot)
029.             {
030.                case PRICE_EXCHANGE:
031.                   if (m_Ticks.Info[iArg].last == 0.0) return false;
032.                   dClose = m_Ticks.Info[iArg].last;
033.                   break;
034.                case PRICE_FOREX:
035.                   dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose);
036.                   if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false;
037.                   break;
038.             }
039.             if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time)))
040.             {
041.                rate.time = macroRemoveSec(m_Ticks.Info[iArg].time);
042.                rate.real_volume = 0;
043.                rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0);
044.                rate.open = rate.low = rate.high = rate.close = dClose;
045.             }else
046.             {
047.                rate.close = dClose;
048.                rate.high = (rate.close > rate.high ? rate.close : rate.high);
049.                rate.low = (rate.close < rate.low ? rate.close : rate.low);
050.                rate.real_volume += (long) m_Ticks.Info[iArg].volume_real;
051.                rate.tick_volume += (m_Ticks.bTickReal ? 1 : (int)m_Ticks.Info[iArg].volume);
052.             }
053.             
054.             return true;         
055.          }
056. //+------------------------------------------------------------------+
057.    private   :
058.       int         m_File;
059.       stInfoTicks m_Ticks;
060. //+------------------------------------------------------------------+
061. inline bool Open(const string szFileNameCSV)
062.          {
063.             string szInfo = "";
064.             
065.             if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
066.             {
067.                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File);
068.                if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true;
069.                Print("File ", szFileNameCSV, ".csv not a traded tick file.");
070.             }else
071.                Print("Tick file ", szFileNameCSV,".csv not found...");
072.                
073.             return false;
074.          }
075. //+------------------------------------------------------------------+
076. inline bool ReadAllsTicks(void)
077.          {
078.             string    szInfo;
079.                         
080.             Print("Loading replay ticks. Please wait...");
081.             ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
082.             m_Ticks.ModePlot = PRICE_FOREX;
083.             while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
084.             {
085.                ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
086.                szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
087.                m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19));
088.                m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3));
089.                m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File));
090.                m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File));
091.                m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File));
092.                m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File));
093.                m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File));
094.                m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot);
095.                m_Ticks.nTicks++;
096.             }
097.             FileClose(m_File);
098.             if (m_Ticks.nTicks == (INT_MAX - 2))
099.             {
100.                Print("Too much data in tick file.\nIt is not possible to continue...");
101.                return false;
102.             }
103.             return (!_StopFlag);
104.          }
105. //+------------------------------------------------------------------+
106.       int SetSymbolInfos(void)
107.          {
108.             int iRet;
109.             
110.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5));
111.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX);
112.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID);
113.             
114.             return iRet;
115.          }
116. //+------------------------------------------------------------------+
117.    public   :
118. //+------------------------------------------------------------------+
119.       C_FileTicks()
120.          {
121.             ArrayResize(m_Ticks.Rate, def_BarsDiary);
122.             m_Ticks.nRate = -1;
123.             m_Ticks.nTicks = 0;
124.             m_Ticks.Rate[0].time = 0;
125.          }
126. //+------------------------------------------------------------------+
127.       bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume)
128.          {
129.             C_FileBars    *pFileBars;
130.             C_Simulation  *pSimulator = NULL;
131.             int           iMem = m_Ticks.nTicks,
132.                           iRet = -1;
133.             MqlRates      rate[1];
134.             MqlTick       local[];
135.             bool          bInit = false;
136.             
137.             pFileBars = new C_FileBars(szFileNameCSV);
138.             ArrayResize(local, def_MaxSizeArray);
139.             Print("Converting bars to ticks. Please wait...");
140.             while ((*pFileBars).ReadBar(rate) && (!_StopFlag))
141.             {
142.                if (!bInit)
143.                {
144.                   m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX);
145.                   pSimulator = new C_Simulation(SetSymbolInfos());
146.                   bInit = true;
147.                }
148.                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary);
149.                m_Ticks.Rate[++m_Ticks.nRate] = rate[0];
150.                if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local, MaxTickVolume);
151.                if (iRet < 0) break;
152.                for (int c0 = 0; c0 <= iRet; c0++)
153.                {
154.                   ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
155.                   m_Ticks.Info[m_Ticks.nTicks++] = local[c0];
156.                }
157.             }
158.             ArrayFree(local);
159.             delete pFileBars;
160.             delete pSimulator;
161.             m_Ticks.bTickReal = false;
162.             
163.             return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0));
164.          }
165. //+------------------------------------------------------------------+
166.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
167.          {
168.             int      MemNRates,
169.                      MemNTicks,
170.                      nDigits,
171.                      nShift;
172.             datetime dtRet = TimeCurrent();
173.             MqlRates RatesLocal[],
174.                      rate;
175.             MqlTick  TicksLocal[];
176.             bool     bNew;
177.             
178.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
179.             nShift = MemNTicks = m_Ticks.nTicks;
180.             if (!Open(szFileNameCSV)) return 0;
181.             if (!ReadAllsTicks()) return 0;         
182.             rate.time = 0;
183.             nDigits = SetSymbolInfos(); 
184.             m_Ticks.bTickReal = true;
185.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
186.             {
187.                if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
188.                if (!BuildBar1Min(c0, rate, bNew)) continue;
189.                if (bNew)
190.                {
191.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
192.                   {
193.                      nShift = MemShift;
194.                      ArrayResize(TicksLocal, def_MaxSizeArray);
195.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
196.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
197.                         nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
198.                      delete pSimulator;
199.                      ArrayFree(TicksLocal);
200.                      if (c1 < 0) return 0;
201.                   }
202.                   MemShift = nShift;
203.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
204.                };
205.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
206.             }
207.             if (!ToReplay)
208.             {
209.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
210.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
211.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
212.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
213.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
214.                m_Ticks.nTicks = MemNTicks;
215.                ArrayFree(RatesLocal);
216.             }else m_Ticks.nTicks = nShift;
217.                            
218.             return dtRet;
219.          };
220. //+------------------------------------------------------------------+
221. inline stInfoTicks GetInfoTicks(void) const
222.          {
223.             return m_Ticks;
224.          }
225. //+------------------------------------------------------------------+
226. };
227. //+------------------------------------------------------------------+
228. #undef def_MaxSizeArray
229. //+------------------------------------------------------------------+

Заголовочный файл C_FileTicks.mqh

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


Разрешаем пользователю настройку параметров

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

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

01. [Config]
02. Path = WDO
03. PointsPerTick = 0.5
04. ValuePerPoints = 5.0
05. VolumeMinimal = 1.0
06. Account = NETTING
07. MaxTicksPerBar = 2800
08. 
09. [Bars]
10. WDON22_M1_202206140900_202206141759
11. 
12. [ Ticks -> Bars]
13. 
14. [ Bars -> Ticks ]
15. 
16. [Ticks]
17. WDON22_202206150900_202206151759

Пример файла конфигурации

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

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

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

Теперь давайте рассмотрим код заголовочного файла, отвечающий за выполнение того, что указано в этом файле конфигурации. Речь идет о файле C_ConfigService.mqh, новый код которого полностью показан ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Support\C_FileBars.mqh"
005. #include "Support\C_FileTicks.mqh"
006. #include "Support\C_Array.mqh"
007. //+------------------------------------------------------------------+
008. class C_ConfigService : protected C_FileTicks
009. {
010.    protected:
011. //+------------------------------------------------------------------+
012.    private   :
013.       enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev};
014.       enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
015.       struct st001
016.       {
017.          C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev;
018.          int      Line,
019.                   MaxTickVolume;
020.          bool     AccountHedging;
021.          char     ModelLoading;
022.          string   szPath;
023.       }m_GlPrivate;
024. //+------------------------------------------------------------------+
025. inline void FirstBarNULL(void)
026.          {
027.             MqlRates rate[1];
028.             int c0 = 0;
029.             
030.             for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++);
031.             rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid);
032.             rate[0].open = rate[0].high = rate[0].low = rate[0].close;
033.             rate[0].tick_volume = 0;
034.             rate[0].real_volume = 0;
035.             rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400;
036.             CustomRatesUpdate(def_SymbolReplay, rate);
037.          }
038. //+------------------------------------------------------------------+
039. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
040.          {
041.             string szInfo;
042.             
043.             szInfo = In;
044.             Out = "";
045.             StringToUpper(szInfo);
046.             StringTrimLeft(szInfo);
047.             StringTrimRight(szInfo);
048.             if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
049.             if (StringSubstr(szInfo, 0, 1) != "[")
050.             {
051.                Out = szInfo;
052.                return Transcription_INFO;
053.             }
054.             for (int c0 = 0; c0 < StringLen(szInfo); c0++)
055.                if (StringGetCharacter(szInfo, c0) > ' ')
056.                   StringAdd(Out, StringSubstr(szInfo, c0, 1));               
057.             
058.             return Transcription_DEFINE;
059.          }
060. //+------------------------------------------------------------------+
061. inline bool Configs(const string szInfo)
062.          {
063.             const string szList[] = {
064.                      "PATH",
065.                      "POINTSPERTICK",
066.                      "VALUEPERPOINTS",
067.                      "VOLUMEMINIMAL",
068.                      "LOADMODEL",
069.                      "ACCOUNT",
070.                      "MAXTICKSPERBAR"
071.                                     };
072.             string    szRet[];
073.             char      cWho;
074.             
075.             if (StringSplit(szInfo, '=', szRet) == 2)
076.             {
077.                StringTrimRight(szRet[0]);
078.                StringTrimLeft(szRet[1]);
079.                for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
080.                switch (cWho)
081.                {
082.                   case 0:
083.                      m_GlPrivate.szPath = szRet[1];
084.                      return true;
085.                   case 1:
086.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1]));
087.                      return true;
088.                   case 2:
089.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1]));
090.                      return true;
091.                   case 3:
092.                      CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1]));
093.                      return true;
094.                   case 4:
095.                      m_GlPrivate.ModelLoading = StringInit(szRet[1]);
096.                      m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading);
097.                      return true;
098.                   case 5:
099.                      if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true;
100.                      else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false;
101.                      else
102.                      {
103.                         Print("Entered account type is not invalid.");                        
104.                         return false;
105.                      }
106.                      return true;
107.                   case 6:
108.                      m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1]));
109.                      return true;         
110.                }
111.                Print("Variable >>", szRet[0], "<< not defined.");
112.             }else
113.                Print("Configuration definition >>", szInfo, "<< invalidates.");
114.                
115.             return false;
116.          }
117. //+------------------------------------------------------------------+
118. inline bool WhatDefine(const string szArg, char &cStage)
119.          {
120.             const string szList[] = {
121.                      "[BARS]",
122.                      "[TICKS]",
123.                      "[TICKS->BARS]",
124.                      "[BARS->TICKS]",
125.                      "[CONFIG]"
126.                                     };
127.                                     
128.             cStage = 1;
129.             for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++)
130.                if (szList[c0] == szArg) return true;
131.                
132.             return false;
133.          }
134. //+------------------------------------------------------------------+
135. inline bool CMD_Array(char &cError, eWhatExec e1)
136.          {
137.             bool       bBarsPrev = false;
138.             string     szInfo;
139.             C_FileBars *pFileBars;
140.             C_Array    *ptr = NULL;
141.             
142.             switch (e1)
143.             {
144.                case eTickReplay : ptr = m_GlPrivate.pTicksToReplay; break;
145.                case eTickToBar  : ptr = m_GlPrivate.pTicksToBars;   break;
146.                case eBarToTick  : ptr = m_GlPrivate.pBarsToTicks;   break;
147.                case eBarPrev    : ptr = m_GlPrivate.pBarsToPrev;    break;
148.             }            
149.             if (ptr != NULL)
150.             {
151.                for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++)
152.                {
153.                   if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break;
154.                   switch (e1)
155.                   {
156.                      case eTickReplay:
157.                         if (LoadTicks(szInfo, true, m_GlPrivate.MaxTickVolume) == 0) cError = 4;
158.                         break;
159.                      case eTickToBar :
160.                         if (LoadTicks(szInfo, false, m_GlPrivate.MaxTickVolume) == 0) cError = 5; else bBarsPrev = true;
161.                         break;
162.                      case eBarToTick :
163.                         if (!BarsToTicks(szInfo, m_GlPrivate.MaxTickVolume)) cError = 6;
164.                         break;
165.                      case eBarPrev   :
166.                         pFileBars = new C_FileBars(szInfo);
167.                         if ((*pFileBars).LoadPreView() == 0) cError = 3; else bBarsPrev = true;
168.                         delete pFileBars;
169.                         break;
170.                   }
171.                }
172.                delete ptr;
173.             }
174.             
175.             return bBarsPrev;
176.          }
177. //+------------------------------------------------------------------+
178.    public   :
179. //+------------------------------------------------------------------+
180.       C_ConfigService()
181.          :C_FileTicks()
182.          {
183.             m_GlPrivate.AccountHedging = false;
184.             m_GlPrivate.ModelLoading = 1;
185.             m_GlPrivate.MaxTickVolume = 2000;
186.          }
187. //+------------------------------------------------------------------+
188. inline const bool TypeAccountIsHedging(void) const
189.          {
190.             return m_GlPrivate.AccountHedging;
191.          }
192. //+------------------------------------------------------------------+
193.       bool SetSymbolReplay(const string szFileConfig)
194.          {
195. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo)
196.             int      file;
197.             char     cError,
198.                      cStage;
199.             string   szInfo;
200.             bool     bBarsPrev;
201.                         
202.             if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
203.             {
204.                Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated...");
205.                return false;
206.             }
207.             Print("Loading data for playback. Please wait....");
208.             cError = cStage = 0;
209.             bBarsPrev = false;
210.             m_GlPrivate.Line = 1;
211.             m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL;
212.             while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
213.             {
214.                switch (GetDefinition(FileReadString(file), szInfo))
215.                {
216.                   case Transcription_DEFINE:
217.                      cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
218.                      break;
219.                   case Transcription_INFO:
220.                      if (szInfo != "") switch (cStage)
221.                      {
222.                         case 0:
223.                            cError = 2;
224.                            break;
225.                         case 1:
226.                            if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array();
227.                            (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line);
228.                            break;
229.                         case 2:
230.                            if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array();
231.                            (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line);
232.                            break;
233.                         case 3:
234.                            if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array();
235.                            (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line);
236.                            break;
237.                         case 4:
238.                            if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array();
239.                            (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line);
240.                            break;
241.                         case 5:
242.                            if (!Configs(szInfo)) cError = 7;
243.                            break;
244.                      }
245.                      break;
246.                };
247.                m_GlPrivate.Line += (cError > 0 ? 0 : 1);
248.             }
249.             FileClose(file);
250.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick));
251.             CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay));
252.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev);
253.             bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev);
254.             switch(cError)
255.             {
256.                case 0:
257.                   if (GetInfoTicks().nTicks <= 0)
258.                   {
259.                      Print("There are no ticks to use. Service is being terminated...");
260.                      cError = -1;
261.                   }else if (!bBarsPrev) FirstBarNULL();
262.                   break;
263.                case 1  : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system..."); break;
264.                case 2  : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line);         break;
265.                default : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line);
266.             }
267.                      
268.             return (cError == 0 ? !_StopFlag : false);
269. #undef macroFileName
270.          }
271. //+------------------------------------------------------------------+
272. };
273. //+------------------------------------------------------------------+

Исходный код заголовочного файла C_ConfigService.mqh

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

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

Теперь вы можете задаться вопросом: «Погодите-ка, а почему бы не использовать определение def_MaxTicksVolume, которое существует в файле Defines.mqh?». Причина в том, что такого определения больше не существует. Оно было удалено, поскольку нам больше не нужно. С тех пор, как мы начали работать с файлом конфигурации, мы перестали зависеть от некоторых вещей, поэтому определение def_MaxTicksVolume было удалено из файла Defines.mqh. Если вы решите его оставить там, хорошо. Но не удивляйтесь, если в будущем, когда файл Defines.mqh снова будет упомянут в какой-нибудь статье, вы не найдете там определения def_MaxTicksVolume.

Теперь давайте посмотрим, почему это определение перестало существовать и быть необходимым. Во время реализации в системе возможности, позволяющей имитировать тики в случае обнаружения их избытка в минутном баре, изначально не было определено, где будет предоставляться эта информация. Чтобы избежать смешивания значений в коде, я решил создать общее определение. Так появилось определение def_MaxTicksVolume, которое было помещено в файл Defines.mqh. Принятие такого рода решений является обычным делом при проектировании чего-то, что будет изменяться со временем. Но когда мы добрались до класса C_ConfigService, мне пришла в голову мысль разрешить пользователю настраивать это самое значение, чтобы не приходилось делать новую компиляцию всего кода.

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

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

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

Сначала мы добавляем ключ, который будет использоваться. Это делается путем добавления новой строки в список строк, которые служат ключами для параметров конфигурации. Этот массив определен в строке 63. Теперь будьте внимательны, потому что я уже объяснял это ранее, а теперь повторю, чтобы было понятнее. Чтобы добавить новый ключ, его необходимо определить полностью заглавными буквами. Независимо от того, какой ключ используется, он всегда должен быть определен в верхнем регистре. В нашем случае, новый ключ находится в строке 70. Теперь есть второй трюк: обратите внимание, что в строке 75 мы используем функцию из библиотеки MQL5, чтобы разделить значения. В качестве разделителя используется знак равенства. Таким образом, то, что находится перед знаком равенства, считается ключом определения, а то, что находится после знака, — значением, которое следует присвоить этому определению.

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

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

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


Заключительные выводы

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

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

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

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

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

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

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

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Разработка торговой системы на основе стакана цен (Часть I): индикатор Разработка торговой системы на основе стакана цен (Часть I): индикатор
Стакан цен Depth of Market, несомненно, является очень важным элементом для выполнения быстрых сделок, особенно в алгоритмах высокочастотного трейдинга (HFT). В этой серии статей мы рассмотрим этот тип торговых событий, которые можно получить через брокера на многих торгуемых символах. Начнем с индикатора, в котором можно настроить цветовую палитру, положение и размер гистограммы, отображаемой непосредственно на графике. Мы также рассмотрим, как сгенерировать события BookEvent для тестирования индикатора в определенных условиях. Другие возможные темы для будущих статей - это хранение данных ценовых распределений и способы их использования в тестере стратегий.
Возможности Мастера MQL5, которые вам нужно знать (Часть 30): Пакетная нормализация в машинном обучении Возможности Мастера MQL5, которые вам нужно знать (Часть 30): Пакетная нормализация в машинном обучении
Пакетная нормализация — это предварительная обработка данных перед их передачей в алгоритм машинного обучения, например, в нейронную сеть. При этом всегда следует учитывать тип активации, который будет использоваться алгоритмом. Мы рассмотрим различные подходы, которые можно использовать для извлечения выгоды с помощью советника, собранного в Мастере.
Индикатор прогноза волатильности при помощи Python Индикатор прогноза волатильности при помощи Python
Прогнозируем будущую экстремальную волатильность при помощи бинарной классификации. Создаем индикатор прогноза экстремальной волатильности с использованием машинного обучения.
Нейросети в трейдинге: Иерархический двухбашенный трансформер (Окончание) Нейросети в трейдинге: Иерархический двухбашенный трансформер (Окончание)
Мы продолжаем построение модели иерархического двухбашенного трансформера Hidformer, который предназначен для анализа и прогнозирования сложных многомерных временных рядов. В данной статье мы доведем начатую ранее работу до логического завершения с тестированием модели на реальных исторических данных.