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

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

MetaTrader 5Примеры | 31 января 2025, 09:03
261 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье "Разработка системы репликации (Часть 61): Нажатие кнопки воспроизведения в сервисе (II)", мы проанализировали проблему, с которой мы столкнулись при использовании режима моделирования. Данная проблема возникает не только из-за катастрофического сбоя в разрабатываемом приложении, а также из-за скорости реакции системы, которую она показывала до настоящего момента. Этого времени отклика не хватало приложению для обработки всех полученных им данных. Поэтому нам нужно как-то приспособиться. Да, наш сервис может немного отличаться от идеальной картины реальности, но мы знаем, что на практике такого совершенства не бывает.

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

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


Новая концепция. Новая система

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay      "RePlay"
14. #define def_MaxPosSlider       400
15. #define def_MaxTicksVolume     2000
16. //+------------------------------------------------------------------+
17. union uCast_Double
18. {
19.    double    dValue;
20.    long      _long;                                 // 1 Information
21.    datetime _datetime;                              // 1 Information
22.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
23.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
24.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
25. };
26. //+------------------------------------------------------------------+
27. enum EnumEvents    {
28.          evHideMouse,               //Hide mouse price line
29.          evShowMouse,               //Show mouse price line
30.          evHideBarTime,             //Hide bar time
31.          evShowBarTime,             //Show bar time
32.          evHideDailyVar,            //Hide daily variation
33.          evShowDailyVar,            //Show daily variation
34.          evHidePriceVar,            //Hide instantaneous variation
35.          evShowPriceVar,            //Show instantaneous variation
36.          evSetServerTime,           //Replay/simulation system timer
37.          evCtrlReplayInit,          //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Исходный код файла Defines.mqh

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. class C_Simulation
007. {
008.    private   :
009. //+------------------------------------------------------------------+
010.       int      m_NDigits;
011.       bool     m_IsPriceBID;
012.       double   m_TickSize;
013.       struct st00
014.       {
015.          bool  bHigh, bLow;
016.          int   iMax;
017.       }m_Marks;
018. //+------------------------------------------------------------------+
019. template < typename T >
020. inline T RandomLimit(const T Limit01, const T Limit02)
021.          {
022.             T a = (Limit01 > Limit02 ? Limit01 - Limit02 : Limit02 - Limit01);
023.             return (Limit01 >= Limit02 ? Limit02 : Limit01) + ((T)(((rand() & 32767) / 32737.0) * a));
024.          }
025. //+------------------------------------------------------------------+
026. inline void Simulation_Time(const MqlRates &rate, MqlTick &tick[])
027.          {
028.             for (int c0 = 0, iPos, v0 = (int)(60000 / m_Marks.iMax), v1 = 0, v2 = v0; c0 <= m_Marks.iMax; c0++, v1 = v2, v2 += v0)
029.             {
030.                iPos = RandomLimit(v1, v2);
031.                tick[c0].time = rate.time + (iPos / 1000);
032.                tick[c0].time_msc = iPos % 1000;
033.             }
034.          }
035. //+------------------------------------------------------------------+
036. inline void CorretTime(MqlTick &tick[])
037.          {
038.             for (int c0 = 0; c0 <= m_Marks.iMax; c0++)
039.                tick[c0].time_msc += (tick[c0].time * 1000);
040.          }
041. //+------------------------------------------------------------------+
042. inline int Unique(const double price, const MqlTick &tick[])
043.          {
044.             int iPos = 1;
045.             
046.             do
047.             {
048.                iPos = (m_Marks.iMax > 20 ? RandomLimit(1, m_Marks.iMax - 1) : iPos + 1);
049.             }while ((m_IsPriceBID ? tick[iPos].bid : tick[iPos].last) == price);
050.             
051.             return iPos;
052.          }
053. //+------------------------------------------------------------------+
054. inline void MountPrice(const int iPos, const double price, const int spread, MqlTick &tick[])
055.          {
056.             if (m_IsPriceBID)
057.             {
058.                tick[iPos].bid = NormalizeDouble(price, m_NDigits);
059.                tick[iPos].ask = NormalizeDouble(price + (m_TickSize * spread), m_NDigits);
060.             }else
061.                tick[iPos].last = NormalizeDouble(price, m_NDigits);
062.          }
063. //+------------------------------------------------------------------+
064. inline void Random_Price(const MqlRates &rate, MqlTick &tick[])
065.          {
066.             for (int c0 = 1; c0 < m_Marks.iMax; c0++)
067.             {
068.                MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick);
069.                m_Marks.bHigh = (rate.high == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bHigh;
070.                m_Marks.bLow = (rate.low == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bLow;
071.             }
072.          }
073. //+------------------------------------------------------------------+
074. inline void DistributeVolumeReal(const MqlRates &rate, MqlTick &tick[])
075.          {
076.             for (int c0 = 0; c0 <= m_Marks.iMax; c0++)
077.             {
078.                tick[c0].volume_real = 1.0;
079.                tick[c0].volume = 1;
080.             }
081.             if ((m_Marks.iMax + 1) < rate.tick_volume) for (int c0 = (int)(rate.tick_volume - m_Marks.iMax); c0 > 0; c0--)
082.                tick[RandomLimit(0, m_Marks.iMax - 1)].volume += 1;
083.             for (int c0 = (int)(rate.real_volume - m_Marks.iMax); c0 > 0; c0--)
084.                tick[RandomLimit(0, m_Marks.iMax)].volume_real += 1.0;
085.          }
086. //+------------------------------------------------------------------+
087. inline int RandomWalk(int In, int Out, const double Open, const double Close, double High, double Low, const int Spread, MqlTick &tick[], int iMode, int iDesloc)
088.          {
089.             double vStep, vNext, price, vH = High, vL = Low;
090.             char i0 = 0;
091.             
092.             vNext = vStep = (Out - In) / ((High - Low) / m_TickSize);
093.             for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++)
094.             {
095.                price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -iDesloc : iDesloc));
096.                price = (price > vH ? vH : (price < vL ? vL : price));
097.                MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick);
098.                switch (iMode)
099.                {
100.                   case 1:
101.                      i0 |= (price == High ? 0x01 : 0);
102.                      i0 |= (price == Low ? 0x02 : 0);
103.                      vH = (i0 == 3 ? High : vH);
104.                      vL = (i0 ==3 ? Low : vL);
105.                      break;
106.                   case 0:
107.                      if (price == Close) return c0;
108.                   default:
109.                      break;
110.                }
111.                if (((int)floor(vNext)) >= c1) continue; else if ((++c2) <= 3) continue;
112.                vNext += vStep;
113.                vL = (iMode != 2 ? (Close > vL ? (i0 == 3 ? vL : vL + m_TickSize) : vL) : (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize)));
114.                vH = (iMode != 2 ? (Close > vL ? vH : (i0 == 3 ? vH : vH - m_TickSize)) : (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH)));
115.             }
116.             
117.             return Out;
118.          }
119. //+------------------------------------------------------------------+
120.    public   :
121. //+------------------------------------------------------------------+
122.       C_Simulation(const int nDigits)
123.          {
124.             m_NDigits       = nDigits;
125.             m_IsPriceBID    = (SymbolInfoInteger(def_SymbolReplay, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_BID);
126.             m_TickSize      = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
127.          }
128. //+------------------------------------------------------------------+
129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume = def_MaxTicksVolume)
130.          {
131.             int    i0, i1, i2;
132.             bool   b0;
133.             
134.             m_Marks.iMax   = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume));
135.             m_Marks.iMax   = ((int)rate.tick_volume > m_Marks.iMax ? m_Marks.iMax : (int)rate.tick_volume - 1);
136.             m_Marks.bHigh  = (rate.open == rate.high) || (rate.close == rate.high);
137.             m_Marks.bLow   = (rate.open == rate.low) || (rate.close == rate.low);
138.             Simulation_Time(rate, tick);
139.             MountPrice(0, rate.open, rate.spread, tick);
140.             if (m_Marks.iMax > 10)
141.             {
142.                i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2));
143.                i1 = m_Marks.iMax - i0;
144.                i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0);
145.                i2 = (i2 == 0 ? 1 : i2);
146.                b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low));
147.                i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2);
148.                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);
149.                RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2);
150.                m_Marks.bHigh = m_Marks.bLow = true;
151. 
152.             }else Random_Price(rate, tick);
153.             if (!m_IsPriceBID) DistributeVolumeReal(rate, tick);
154.             if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick);
155.             if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick);
156.             MountPrice(m_Marks.iMax, rate.close, rate.spread, tick);
157.             CorretTime(tick);
158. 
159.             return m_Marks.iMax;
160.          }
161. //+------------------------------------------------------------------+
162. };
163. //+------------------------------------------------------------------+

Исходный код файла C_Simulation.mqh

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

Я покажу один из основных моментов, чтобы читатель, глядя на код, понимал внесенные изменения. Хотя в некоторых разделах есть и другие небольшие изменения, они не заслуживают особого внимания, поскольку мы их внесли для адаптации процесса к новой методологии. В любом случае, основным изменением в данном случае стало дополнение в строке 16. В оригинальном коде класса данной переменной не было. Прежде, чем мы рассмотрим последствия использования этой переменной в коде класса, давайте посмотрим, где она инициализируется. Если вы думаете, что она инициализируется в конструкторе класса, то вы ошибаетесь. Это не так. Она инициализируется в другом месте, конкретнее, между строками 134 и 135, поэтому следите внимательно за тем, что происходит в этих двух строках. Это будет иметь решающее значение при изменении системы. Строка 129 содержит объявление функции, отвечающей за создание моделирования тика. Однако теперь она включает дополнительный параметр, который точно указывает максимальное количество тиков к моделированию. Помните строку, добавленную в файл Defines.mqh? Одно из мест, где используется это определение, находится именно здесь. Давайте посмотрим, что происходит. Это ключевой момент, если мы захотим что-то изменить, мы должны понимать, как эти изменения повлияют на поведение кода. При выполнении вызова для моделирования тиков необходимо также установить значение, указывающее максимальное количество тиков, которые будут моделироваться. Это значение не является обязательным, поскольку уже имеет значение по умолчанию. Однако, если указанное значение равно или меньше нуля, мы будем считать, что максимальное количество тиков равно единице. Данное значение не является произвольным. И это имеет свои причины, ведь в случае с Forex минимальное возможное значение, которое можно считать тиковым объемом, равно единице. С другой стороны, если указать значение больше, чем определено в заголовочном файле Defines.mqh, класс проигнорирует указанное значение и будет использовать максимальное значение, определенное в системе. Это значение, которое находится в файле Defines.mqh, представляет собой максимальное количество тиков, которое будет моделироваться внутри системы. Однако любое значение в этом диапазоне будет использовано в качестве максимального количества смоделированных тиков. Таким образом, мы сможем настроить его в этих пределах.

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

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

Как уже упоминалось в начале, подобная модификация привела к тому, что все тесты, связанные с максимальным количеством генерируемых тиков, были пересмотрены. Поэтому функции и процедуры данного класса претерпели небольшие изменения. Поскольку они несложные для понимания, я не буду подробно описывать, как они работают. Если у вас есть вопросы, посмотрите статью о случайном блуждании. Об этом можно прочитать в разделе "Разработка системы репликации - Моделирование рынка (Часть 15): Появление СИМУЛЯТОРА (V) - СЛУЧАЙНОЕ БЛУЖДАНИЕ".

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

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


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

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

В отличие от предыдущей темы, решение данной проблемы гораздо сложнее, чем может показаться, и это связано с самой природой того, чем мы будем управлять. Если сервис репликации/моделирования был бы разработан для использования исключительно с определенным типом рынка, проблему было бы проще решить. В таком случае мы могли бы проводить анализ во время загрузки тиков и проверять, не превышает ли сумма в пределах временного окна заданное значение. Если бы такое произошло, мы бы заставили систему отклонять тики в пределах данного окна, что эквивалентно минутному бару. Затем мы заставили симулятор создать движение на основе СЛУЧАЙНОГО БЛУЖДАНИЯ. Таким образом, можно было бы легко ограничить количество тиков в окне. Однако, поскольку во время загрузки тиков мы не знаем, с какими значениями будем иметь дело - BID или LAST, - нам предстоит решить сложную проблему. И сложность заключается именно в том, что мы не знаем, какая система построения графиков - BID или LAST.

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

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

01.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
02.          {
03.             int      MemNRates,
04.                      MemNTicks;
05.             datetime dtRet = TimeCurrent();
06.             MqlRates RatesLocal[],
07.                      rate;
08.             bool     bNew;
09.             
10.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
11.             MemNTicks = m_Ticks.nTicks;
12.             if (!Open(szFileNameCSV)) return 0;
13.             if (!ReadAllsTicks()) return 0;         
14.             rate.time = 0;
15.             for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++)
16.             {
17.                if (!BuildBar1Min(c0, rate, bNew)) continue;
18.                if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
19.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
20.             }
21.             if (!ToReplay)
22.             {
23.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
24.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
25.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
26.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
27.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
28.                m_Ticks.nTicks = MemNTicks;
29.                ArrayFree(RatesLocal);
30.             }else SetSymbolInfos();
31.             m_Ticks.bTickReal = true;
32.                            
33.             return dtRet;
34.          }; 

Фрагмент исходного кода C_FilesTicks.mqh

Не беспокойтесь по поводу нумерации строк. Мы их используем здесь только в качестве ссылки для пояснения; они не соответствуют реальным строкам в файле C_FileTicks.mqh, поскольку данный файл подвергся некоторым изменениям, которые будут рассмотрены позже. Теперь давайте посмотрим, что происходит во время считывания реальных тиков.

Когда запускается функция, в строках 10 и 11 временно сохраняются текущие значения позиций загруженных тиков и представляющих их баров. Если вызывающая программа укажет, что тики должны использоваться не как основа для воспроизведения, а как предыдущие бары, то эти значения будут заменены в строках 27 и 28 соответственно. Таким образом, система останется в целости и сохранности до тех пор, пока не будут получены тики воспроизведения.

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

Теперь пришло время для самого интересного. Мы проанализируем временной интервал в одну минуту. Всё в порядке. В строке 15 мы вводим цикл, целью которого является создание минутных баров, чтобы система могла получить к ним доступ, когда будет нужно. Вот тут-то мы и вмешаемся. Так происходит, потому что в строке 17 выполняется вызов, который их создает. Данная функция подсчитывает количество тиков, использованных для создания движения внутри бара. Теперь обратите внимание: когда проверка в строке 18 покажет состояние true, данные бара будут отображены так же, как если бы бар был считан из файла. Это именно те данные, которые нам нужно передать классу моделирования. Можно проверить это, вернувшись к предыдущей теме и проверив строку 129.

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

Это самая простая часть: протестировать и, если нужно, попросить класс моделирования выполнить СЛУЧАЙНОЕ БЛУЖДАНИЕ, чтобы мы получили движение с нужным количеством тиков. Но теперь начинается самое сложное. Класс моделирования тиков будет генерировать тики, но нам нужно найти самый простой способ модификации уже загруженных тиков. Однако мы удалим только тики, являющиеся частью окна, где количество тиков превышает значение, указанное изнутри или определенное в приложении. В дополнение к данной проблеме необходимо решить еще одну, хотя и более простую. Моделирование нужно запускать только тогда, когда данные будут использоваться в воспроизведении. Но, как я уже говорил, с этой проблемой можно легко справиться, поскольку вызывающая программа сообщает нам о том, будут ли данные использоваться в воспроизведении. Всё, что нам нужно сделать, это проверить значение ToReplay. Это проще простого! Теперь попробуем решить самую сложную задачу - каким-то образом переписать ненужные тики. Для этого придется преобразовать функцию, показанную в предыдущем фрагменте, в другую функцию.

Первая попытка заставить всё работать (а разработка новых функций всегда основана на пробах и ошибках) показана в следующем фрагменте:

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

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

Для этого пришлось добавить строки 6, 7 и 11 - это новые переменные, которые нам действительно помогут. В строке 15 мы инициализируем первую из наших новых переменных, именно для того, чтобы узнать, где находится текущий загруженный счетчик тиков. Также необходимо включить строки 18 и 19, в которых просто инициализируются некоторые значения. Мы также добавили строку 20, в которой выделяется место в памяти для моделирования тиков. Данная область памяти должна освободиться только в строке 41. Мы обнаружили небольшую ошибку, связанную со строкой 31. Мы ее исправим в финальной версии кода. Не волнуйтесь, мы дойдем до этого.

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

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

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

Но подождите минутку. Проверка, выполняемая на строке 25, позволит выполнить описанные верификации только при обнаружении нового бара. Почему получается, что мы смотрим на рассчитанное значение бара, если мы уже находимся на новом баре? Если вы задумались об этом, я вас поздравляю, ведь это говорит о том, что вы действительно понимаете код. Но вы упустили одну важную деталь. Пока не будет выполнена строка 39, мы будем смотреть на бар, который только что закрылся. Строка 39 - это именно та строка, которая заставляет нас переходить на новый бар. Теперь понятно, почему всё нужно делать в определенной последовательности?

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

В строке 29 мы делаем так, чтобы точка сдвига указывала на начало позиций, откуда будут начинаться тики бара, подлежащие замене. Затем мы запускаем систему моделирования в строке 30. И в строке 31 мы запускаем само моделирование. Обратите внимание, что в случае ошибки строка 41 не будет выполнена и выделенная память не будет освобождена. Эту небольшую проблему мы исправим позже, - она никак не влияет на работу платформы в рамке тестов. Если моделирование тика прошло успешно, мы используем вызов в строке 32. Теперь обратите внимание: мы могли бы заменить эту строку 32 циклом for, но поскольку подпрограмма библиотеки MQL5, скорее всего, оптимизирована для быстрого перемещения данных, желательно использовать ее здесь вместо реализации. В строке 33 мы обновляем значение смещения, чтобы оно указывало на новую позицию сразу после передачи данных. И, наконец, в строке 34 мы удаляем симулятор.

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


Заключение

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

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


Видео, демонстрирующее сбой системы

Я скоро покажу, как это исправить, но это будет не сразу, поскольку проблема не критична. Данная проблема связана только с загрузкой или выгрузкой созданных объектов. Здесь у нас есть прекрасная возможность. Если вы действительно хотите показать, что уже хорошо разбираетесь в программировании, можно попробовать исправить ошибку самостоятельно, прежде чем я покажу вам, как это сделать. Вам не нужно показывать мне свои результаты, но попытайтесь, прежде чем читать мое решение. Так вы будете знать, на каком уровне обучения вы находитесь.

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Эволюционный торговый алгоритм обучения с подкреплением и вымиранием убыточных особей (ETARE) Эволюционный торговый алгоритм обучения с подкреплением и вымиранием убыточных особей (ETARE)
Представляем инновационный торговый алгоритм, сочетающий эволюционные алгоритмы с глубоким обучением с подкреплением для торговли на Форекс. Алгоритм использует механизм вымирания неэффективных особей, для оптимизации торговой стратегии.
Нейросети в трейдинге: Иерархический двухбашенный трансформер (Hidformer) Нейросети в трейдинге: Иерархический двухбашенный трансформер (Hidformer)
Предлагаем познакомиться с фреймворком иерархического двухбашенного трансформера (Hidformer), который был разработан для прогнозирования временных рядов и анализа данных. Авторы фреймворка предложили несколько улучшений к архитектуре Transformer, что позволило повысить точность прогнозов и снизить потребление вычислительных ресурсов.
От начального до среднего уровня: Операторы WHILE и DO WHILE От начального до среднего уровня: Операторы WHILE и DO WHILE
В этой статье мы практически и весьма наглядно рассмотрим первый оператор цикла. Несмотря на то, что многие новички испытывают страх, сталкиваясь с необходимостью создания циклов, знание того, как это делать правильно и безопасно, может прийти только с опытом и практикой. Но кто знает, возможно, я смогу уменьшить ваши трудности и страдания, показав основные проблемы и меры предосторожности, которые следует соблюдать при использовании циклов в коде.
Постфактумный анализ торговли: подбираем TrailingStop и новые стопы в тестере стратегий Постфактумный анализ торговли: подбираем TrailingStop и новые стопы в тестере стратегий
Продолжаем тему анализа совершённых сделок в тестере стратегий для улучшения качества торговли. Проверим, как использование различных трейлингов поможет изменить уже полученные результаты торговли.