Разработка системы репликации - Моделирование рынка (Часть 24): FOREX (V)
Введение
В предыдущей статье "Разработка системы репликации - Моделирование рынка (часть 23): FOREX (IV)", мы рассмотрели реализацию частичной блокировки в системе моделирования. Блокировка была необходимой, поскольку система столкнулась с трудностями при работе с крайне низкими объемами сделок. Данное ограничение проявилось при попытке запустить моделирование на основе построения LAST, когда система рисковала аварийно завершить работу при попытке сгенерировать моделирование. Данная проблема была особенно заметна в те моменты, когда объем сделок, представленный 1-минутным баром, был недостаточен. Чтобы обойти эту проблему, мы сегодня рассмотрим, как адаптировать реализацию, и будем следовать принципам, которые ранее применялись при моделировании на основе построения BID. Этот подход широко используется на валютном рынке, поэтому для нас это уже пятая статья на данную тему. Но в данном случае мы не будем уделять внимание именно валютам, наша цель - улучшить систему моделирования фондового рынка.
Давайте приступим к реализации изменений
Первый шаг - это введение приватной структуры в наш класс. Это связано с наличием данных или информации, общих для обоих режимов моделирования - LAST и BID. Эти общие элементы, по сути являющиеся значениями, будут объединены в единую структуру. Итак, мы определяем следующую структуру:
struct st00 { bool bHigh, bLow; int iMax; }m_Marks;
Хотя с виду это простая структура, она достаточно надежна, чтобы позволить нам улучшить наш код. Благодаря тому, что все общие значения сосредоточены в одном месте, эффективность и организация нашей работы значительно повышаются.
После данной подготовки, мы можем приступить к первой эффективной модификации кода.
inline int Simulation(const MqlRates &rate, MqlTick &tick[]) { m_Marks.iMax = (int) rate.tick_volume - 1; m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); Simulation_Time(rate, tick); if (m_IsPriceBID) Simulation_BID(rate, tick); else Simulation_LAST(rate, tick); else return -1; CorretTime(tick); return m_Marks.iMax; }
Сегодня мы снимем ограничение, которое препятствовало выполнению моделирований, основанных на построении LAST, и введем новую точку входа специально для этого типа моделирования. Обратите внимание на то, что весь механизм работы будет основан на принципах валютного рынка. Основное различие в данной процедуре заключается в разделении моделирований BID и LAST. Однако важно отметить, что методология, используемая при рандомизации времени и его корректировке для совместимости с классом C_Replay, остается идентичной в обоих видах моделирования. Такое единообразие выгодно, поскольку изменения в одном режиме могут принести пользу другому, особенно если это касается управления временем между тиками. Естественно, рассмотренные здесь модификации повлияют и на моделирование, основанное на построении BID. Понять данные изменения довольно просто, поэтому я не буду останавливаться на этих деталях.
После того, как мы вернемся к интересующему нас коду с реализацией вызова функции моделирования построения LAST, мы можем рассмотреть начальные аспекты этого вызова, то есть структуру функции. Мы это показываем в приведенном ниже фрагменте кода:
inline void Simulation_LAST(const MqlRates &rate, MqlTick &tick[]) { if (CheckViability_LAST(rate)) { }else { } DistributeVolumeReal(rate, tick); }
В данном контексте мы будем выполнять два важных действия при работе с построением LAST. Первое действие связано с проверкой возможности использования системы случайного блуждания для моделирования, о чем говорилось в предыдущих статьях. Для тех, кто не изучал данную тему, рекомендую прочитать статью "Разработка системы репликации - Моделирование рынка (Часть 15)": Появление СИМУЛЯТОРА (V) - СЛУЧАЙНОЕ БЛУЖДАНИЕ". Второе действие - это распределение объема торгов между возможными тиками. Данные процедуры всегда были и будут важны при работе с моделированием, основанным на построении LAST.
Прежде чем подробно описать структуру данного каркаса, давайте рассмотрим две важнейшие функции для моделирования на основе построения LAST. Первая функция представлена ниже:
inline bool CheckViability_LAST(const MqlRates &rate) { #define macro_AdjustSafetyFator(A) (A + (A * 1.4)); double v0, v1, v2; v0 = macro_AdjustSafetyFator(rate.high - rate.low); v1 = (rate.open - rate.low); v2 = (rate.high - rate.open); v0 += macro_AdjustSafetyFator(v1 > v2 ? v1 : v2); v1 = (rate.close - rate.low); v2 = (rate.high - rate.close); v0 += macro_AdjustSafetyFator(v1 > v2 ? v1 : v2); return ((int)(v0 / m_TickSize) < rate.tick_volume); #undef macro_AdjustSafetyFator }
Данная функция отвечает за проверку возможности генерации случайного блуждания в доступных пределах. Как нам начинать действовать? Метод заключается в определении количества тиков, доступных для использования, затем информации, полученной из анализируемого бара, и количества тиков, которые необходимо охватить; данный расчет выполняется функцией.
Важно отметить, что мы не будем напрямую использовать полученное значение для определения зоны охвата. Это связано с тем, что если бы мы использовали такой прямой подход, то получившееся случайное блуждание выглядело бы искусственным, с чрезмерно предсказуемыми движениями. Чтобы «смягчить» эту проблему, мы скорректируем расчеты. Данная настройка, реализуемая с помощью макроса, заключается в том, что она определяет на 30% большую область, которая выступает в качестве фактора безопасности для правильного формирования случайного блуждания. Еще один важный аспект - необходимость всегда учитывать в расчетах наибольшее возможное расстояние, так как при рандомизации может потребоваться такое расширение. Таким образом, в процессе расчетов уже учтена данная возможность.
Конечный результат показывает, целесообразно ли использовать метод случайного блуждания или следует прибегнуть к другому, более прямому методу рандомизации. Однако данное решение принимается вызывающей процедурой, но не здесь.
Ниже подробно описываем вторую функцию:
inline void DistributeVolumeReal(const MqlRates &rate, MqlTick &tick[]) { for (int c0 = 0; c0 <= m_Marks.iMax; c0++) tick[c0].volume_real = 1.0; for (int c0 = (int)(rate.real_volume - rate.tick_volume); c0 > 0; c0--) tick[RandomLimit(0, m_Marks.iMax)].volume_real += 1.0; }
Здесь целью является случайное распределение общего объема сделок на 1-минутном баре. Первый цикл выполняет основное распределение, чтобы гарантировать, что каждый тик получит минимальный начальный объем. Второй цикл отвечает за случайное распределение оставшегося объема, чтобы предотвратить его сосредоточение в одном тике. Хотя такая возможность всё еще существует, она значительно снижается благодаря принятой методике распределения.
Данные функции уже были частью изначальной реализации, об этом говорилось в статьях о реализации случайного блуждания. Однако на этот раз мы используем более модульный подход, чтобы максимально повысить возможность повторного использования разработанного кода.
После дополнительных размышлений мы выявили элементы, которые всё еще остаются общими для моделирований BID и LAST. К такому виду элементов относится определение точек входа и выхода, а также возможность определения конечных точек. Учитывая данный момент, мы стремимся повторно использовать ранее разработанные коды. Для этого потребуется изменить код, представленный в предыдущей статье. Предлагаемое изменение выглядит следующим образом:
inline void Mount_BID(const int iPos, const double price, const int spread, MqlTick &tick[]) inline void MountPrice(const int iPos, const double price, const int spread, MqlTick &tick[]) { if (m_IsPriceBID) { tick[iPos].bid = price; tick[iPos].ask = NormalizeDouble(price + (m_TickSize * spread), m_NDigits); }else tick[iPos].last = NormalizeDouble(price, m_NDigits); }
Мы начинаем с замены старого имени функции на новое, а затем вставляем внутренний тест, чтобы определить, будет ли моделирование основано на построении BID или LAST. Таким образом, одна и та же функция может быть адаптирована для генерации тиков на основе BID и LAST в соответствии с данными, наблюдаемыми в файле 1-минутного бара.
Данное изменение также требует внесения двух корректировок. Запомните: Наша цель состоит в том, чтобы интегрировать моделирование BID и LAST в упрощенном виде, чтобы только действительно уникальные аспекты каждого из них обрабатывались соответствующим методом. Другие моменты будут рассматриваться на общей основе. Далее следует изменение кода класса моделирования:
inline void Simulation_BID(const MqlRates &rate, MqlTick &tick[]) { Mount_BID(0, rate.open, rate.spread, tick); for (int c0 = 1; c0 < m_Marks.iMax; c0++) { Mount_BID(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick); MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick); m_Marks.bHigh = (rate.high == tick[c0].bid) || m_Marks.bHigh; m_Marks.bLow = (rate.low == tick[c0].bid) || m_Marks.bLow; } if (!m_Marks.bLow) Mount_BID(Unique(rate.high, tick), rate.low, rate.spread, tick); if (!m_Marks.bHigh) Mount_BID(Unique(rate.low, tick), rate.high, rate.spread, tick); Mount_BID(m_Marks.iMax, rate.close, rate.spread, tick); }
Зачеркнутые фрагменты были удалены, чтобы код продолжал свое правильное функционирование. Однако важно отметить, что для отображения LAST нам нужны те исключенные фрагменты, которые являются общими для системы моделирования. Итак, этот общий код был передан в функцию, описанную ниже:
inline int Simulation(const MqlRates &rate, MqlTick &tick[]) { m_Marks.iMax = (int) rate.tick_volume - 1; m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); Simulation_Time(rate, tick); MountPrice(0, rate.open, rate.spread, tick); if (m_IsPriceBID) Simulation_BID(rate, tick); else Simulation_LAST(rate, tick); if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); CorretTime(tick); return m_Marks.iMax; }
И теперь мы подошли к решающему моменту. Без существенных изменений, только благодаря разумному повторному использованию кода, мы смогли интегрировать оба моделирования. Таким образом, теперь у нас есть моделирование, основанное на построении BID и LAST. Мы автоматически включаем входные, выходные и лимитные значения, если они не были заданы ранее при рандомизированном моделировании. Благодаря этой стратегической корректировке мы существенно расширили рамки нашего моделирования. Кроме того, сложность кода не увеличилась, он остался без значительного увеличения. Если вы используете код, который был представлен до настоящего момента, то вы получите хорошую производительность при моделировании построения BID. А для графика LAST теперь можно вставить бар в график, даже если значение минимума неверно, поскольку зарегистрируется как ноль, несмотря на правильное определение. Данная неточность возникает, потому что для неинициализированных тиков только с определенным временем все значения LAST равны нулю. Это не стало бы проблемой, если не учитывать тот факт, что объем торгов уже распределен. Поэтому настало время пересмотреть нашу функцию присвоения значений цены LAST каждому тику, что позволит провести моделирование с точными данными.
Наша функция моделирования цены LAST остается неизменной. Однако при изучении кода моделирования BID возникает мысль: можно ли воспользоваться этим же кодом для моделирования цены LAST, особенно если количество доступных тиков недостаточно для полного выполнения случайного блуждания? У меня тоже возник этот вопрос. После тщательного анализа мы пришли к выводу, что необходима лишь незначительная модификация функции моделирования BID. Однако, чтобы избежать путаницы в будущем, при внесении изменений в код, нам необходимо будет тщательно спланировать эти изменения уже сейчас. Стоит напомнить, что процедура моделирования BID запускается только что упомянутой функцией. Учитывая, что концепция моделирования LAST аналогична, мы можем поискать способ сохранить предыдущий вызов нетронутым. Таким образом, мы адаптируем процедуру моделирования BID так, чтобы она также могла охватывать моделирование LAST в ситуациях, когда случайное блуждание не применяется.
Некоторые могут сомневаться в таком подходе, но вот как код моделирования для построения BID был адаптирован для включения в него моделирования для построения LAST.
inline void Simulation_BID(const MqlRates &rate, MqlTick &tick[]) inline void Random_Price(const MqlRates &rate, MqlTick &tick[]) { for (int c0 = 1; c0 < m_Marks.iMax; c0++) { MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick); m_Marks.bHigh = (rate.high == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bHigh; m_Marks.bLow = (rate.low == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bLow; m_Marks.bHigh = (rate.high == tick[c0].bid) || m_Marks.bHigh; m_Marks.bLow = (rate.low == tick[c0].bid) || m_Marks.bLow; } }
Чтобы избежать путаницы во время выполнения, мы решили переименовать функцию. Это небольшая цена за преимущество получения более универсального кода. Суть данной адаптации заключается в следующем. Эти два элемента кода заслуживают особого внимания. Тернарный оператор, несмотря на то, что некоторые считают его непонятным, является ценным наследием языка C, который предлагает много полезного. В этих сегментах проверяется вид выполняемого построения, чтобы соответствующим образом настроить лимитную цену. Важно отметить, что независимо от типа построения рандомизация происходит одинаково. Таким образом, нам удалось объединить эти два метода и создать эффективную систему моделирования для BID и LAST.
После внесенных изменений моделирование стало очень похоже на то, о чем шла речь в статье "Разработка системы репликации - Моделирование рынка (Часть 13)": Появление СИМУЛЯТОРА (III)". Однако мы еще не реализовали в системе моделирование с помощью случайного блуждания. Это связано с тем, что на данный момент код был скорректирован в соответствии с представленным вариантом:
inline void Simulation_LAST(const MqlRates &rate, MqlTick &tick[]) { if (CheckViability_LAST(rate)) { }else Random_Price(rate, tick); DistributeVolumeReal(rate, tick); }
Поэтому мы пока не моделируем типичные сценарии, в которых использование случайного блуждания было бы целесообразным и уместным. Однако наша цель - позволить моделированию цены BID при определенных условиях использовать случайное блуждание также, как и моделирование для построения цены LAST. В связи с данным моментом возникает вопрос: "возможно ли это?". И более того, можем ли мы сделать этот подход еще более интересным и устойчивым, чтобы рынки, подобные FOREX, также могли получить выгоду от метода случайного блуждания для моделирования движения цен? Ответ - да, это возможно. Прежде чем реализовать случайное блуждание специально для построения цены LAST, необходимо внести некоторые изменения.
inline bool CheckViability(const MqlRates &rate) { #define macro_AdjustSafetyFator(A) (int)(A + ceil(A * 1.7)) int i0, i1, i2; i0 = macro_AdjustSafetyFator((rate.high - rate.low) / m_TickSize); i1 = (int)((rate.open - rate.low) / m_TickSize); i2 = (int)((rate.high - rate.open) / m_TickSize); i0 += macro_AdjustSafetyFator(i1 > i2 ? i1 : i2); i0 += macro_AdjustSafetyFator((i1 > i2 ? (rate.high - rate.close) : (rate.close - rate.low) / m_TickSize)); return (i0 < rate.tick_volume); #undef macro_AdjustSafetyFator }
Приведенная выше процедура является развитием той, которая ранее использовалась для оценки возможности создания движения случайного блуждания. В связи с техническими деталями и введением расширенных факторов безопасности мы доработали данный подход, чтобы не вводить в заблуждение о возможности выполнения движения. Это изменение оправдано, поскольку процедура проверки теперь не ограничивается только моделированием построения LAST. Он также применяется для оценки применимости случайного блуждания в моделировании построения BID. На первый взгляд, это кажется простым, но тут требуются особые меры предосторожности. Чтобы лучше проиллюстрировать данный момент, обратимся к рисунку 01.
Рисунок 01 - Расчет самого длинного возможного пути
Изученная функция делает именно то, что показано на рисунке 01: она вычисляет самый длинный возможный путь для создания 1-минутного бара. Данная методология была скорректирована с целью оптимизации процесса, чтобы построение BID также приносило пользу. Однако увеличение фактора безопасности с 1,4 до 1,7 создает дополнительные сложности для использования случайного блуждания на некоторых активах. Расчет начинается с определения расстояния между ценой открытия бара и его границей. С этой информацией мы используем самое высокое значение на первом этапе расчета. Другое значение используется для перемещения бара, как показано на рисунке 01. В заключение мы проверим целесообразность использования случайного блуждания.
Вам может показаться, что необходимо внести дополнительные изменения в код класса. И это действительно так. Однако данное изменение будет выполнено таким образом, чтобы обеспечить более гармоничную интеграцию.
inline int Simulation(const MqlRates &rate, MqlTick &tick[]) { m_Marks.iMax = (int) rate.tick_volume - 1; m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); Simulation_Time(rate, tick); MountPrice(0, rate.open, rate.spread, tick); if (CheckViability(rate)) { }else Random_Price(rate, tick); if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); if (m_IsPriceBID) Random_Price(rate, tick); else Simulation_LAST(rate, tick); if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); CorretTime(tick); return m_Marks.iMax; }
Зачеркнутые части мы удалили из кода. Дополнения выделены зеленым. Теперь можно наблюдать случаи, когда 1-минутный бар на рынках, подобных FOREX, может генерировать тики аналогично фондовому рынку, и наоборот. Это позволяет симулятору охватывать широкий диапазон рыночных движений, независимо от объема тиков. Однако важно отметить, что код, отвечающий за создание случайного блуждания, еще не был включен в вышеописанную процедуру. Поэтому мы рассмотрим, как этот код будет реализован для обоих типов построения, сосредоточившись именно на данной функциональности.
Реализация случайного блуждания для цен BID и LAST
Как говорилось выше, класс C_Simulation был разработан для того, чтобы обеспечить единообразную обработку между моделированием построения BID и LAST. Цель заключалась в том, чтобы создать как можно более точное моделирование. Мы подошли к решающему моменту, когда следующим шагом должна стать реализация процедуры, способной работать со случайным блужданием, используя при этом минимальное количество необходимого кода без усложнения процесса. Эта адаптация основана на том, что было подробно описано в статье "Разработка системы репликации - Моделирование рынка (Часть 15)": Появление СИМУЛЯТОРА (V) - СЛУЧАЙНОЕ БЛУЖДАНИЕ". Поэтому я не буду вдаваться в подробности о первоначальной реализации случайного блуждания или о том, как эта появилась эта идея. Тем, кто заинтересован в дальнейшем чтении, я рекомендую ознакомиться с упомянутой статьей. Здесь мы сосредоточимся на том, чтобы адаптировать данный код к новому контексту.
inline int RandomWalk(int In, int Out, const double Open, const double Close, double High, double Low, const int Spread, MqlTick &tick[], int iMode) { double vStep, vNext, price, vH = High, vL = Low; char i0 = 0; vNext = vStep = (Out - In) / ((High - Low) / m_TickSize); for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++) { price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -1 : 1)); price = (price > High ? price - m_TickSize : (price < Low ? price + m_TickSize : price)); MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick); switch (iMode) { case 0: if (price == Close) return c0; else break; case 1: i0 |= (price == High ? 0x01 : 0); i0 |= (price == Low ? 0x02 : 0); vH = (i0 == 3 ? High : vH); vL = (i0 ==3 ? Low : vL); break; default: break; } if (((int)floor(vNext)) >= c1) continue; if ((++c2) <= 3) continue; vNext += vStep; if (iMode != 2) { if (Close > vL) vL = (i0 == 3 ? vL : vL + m_TickSize); else vH = (i0 == 3 ? vH : vH - m_TickSize); }else { vL = (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize)); vH = (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH)); } } return Out; }
Внесенные изменения призваны упростить структуру кода, сохранив при этом его функционирование в неизменном виде. Тут особый интерес представляет то, как предыдущее значение считывается для создания нового, адаптируясь к используемому типу построения, каким бы оно ни было. Такая гибкость очень важна для функциональности симулятора. Для определения значений мы используем уже известную и представленную в этой статье функцию, которая облегчает разработку процесса. Как мы уже говорили, мы не будем подробно описывать особенности функционирования данной процедуры, поскольку она уже была рассмотрена в другой статье.
Теперь давайте рассмотрим, как была построена окончательная процедура. Это наша первая попытка завершить данный этап реализации, одновременно проверяя, достигает ли функция, предназначенная для генерации вызовов моделирования на основе данных о барах, ожидаемой цели. Цель - эффективно охватить моделирование построения BID и LAST. Ниже представляем подробное описание кода функции:
inline int Simulation(const MqlRates &rate, MqlTick &tick[]) { int i0, i1; bool b0 = ((rand() & 1) == 1); m_Marks.iMax = (int) rate.tick_volume - 1; m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); Simulation_Time(rate, tick); MountPrice(0, rate.open, rate.spread, tick); if (CheckViability(rate)) { i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); i1 = m_Marks.iMax - i0; i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0); RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, 1); RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2); m_Marks.bLow = m_Marks.bHigh = true; }else Random_Price(rate, tick); if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); CorretTime(tick); return m_Marks.iMax; }
Подчеркнутый сегмент имеет решающее значение для моделирования случайного блуждания по барам. Хотя данный процесс уже использовался в прошлом, он был интегрирован непосредственно в код генерации. Теперь он перемещен на место, облегчающее его понимание и анализ, что делает его доступным даже для начинающих программистов. Если мы присмотримся, то поймем, что система моделирования оценивает целесообразность использования случайного блуждания. Если это осуществимо, система будет использовать его, если нет - прибегнет к альтернативному методу. Таким образом, мы гарантируем генерацию ценовых движений или ценовых сдвигов при любых обстоятельствах. Это относится как к рынку форекс, так и к фондовому рынку, это не имеет значения. Наша цель - всегда адаптироваться, чтобы обеспечить наилучшее моделирование и, в равной степени, охватить все достижимые ценовые точки, не отклоняясь от того, что показывают бары.
Важно понимать, что в некоторых ситуациях конкретный бар может не подходить для моделирования методом случайного блуждания, в то время как последующий бар может сразу же использовать данный процесс. В результате движения цены могут варьироваться от гармоничных и плавных до более резких и значительных. Такое расхождение не обязательно свидетельствует о сбое в системе моделирования или репликации, а скорее является следствием необходимости быстрого движения цены на данном баре, которое могло не сопровождаться значительным объемом сделок для более плавного моделирования с помощью случайного блуждания. Верно и обратное: высокий объем сделок может позволить применить метод случайного блуждания, что вовсе не означает, что в действительности цена двигалась плавно. В некоторых случаях движение могло быть резким, но плотность торгуемых тиков позволила применить в моделировании случайное блуждание, которое не обязательно отражает реальные рыночные условия на данном конкретном баре.
Может показаться, что мы уже достигли идеального и прекрасного решения, то есть, нашу цель. Но на самом деле всё обстоит иначе: мы еще не дошли до этого. Хотя метод случайного блуждания широко используется, когда количество сделок в 1-минутном баре большое, оно не применимо, когда количество сделок в 1-минутном баре немного меньше необходимого. Хуже того, использование совершенно случайного блуждания для моделирования движения бара, когда расстояние между максимальной и минимальной ценой близко к количеству тиков, приводит к моделированию, которое выглядит причудливо. В таких случаях необходимо пересмотреть модель, о которой говорится в другой статье этого цикла, "Разработка системы репликации - Моделирование рынка (Часть 11)": Появление СИМУЛЯТОРА (I)", где мы предложили систему, которая создает разворот внутри бара.
Идея внедрения такой системы кажется не только уместной, но и возможной. Цель состоит в том, чтобы генерировать реалистичные и допустимые движения и не запускать ценовые значения совершенно случайным образом. Поэтому центральным вопросом теперь является не время, а значение, указанное в цене. Использование функции, которая генерирует значения без какой-либо логики, особенно в ситуациях, когда опытный трейдер определил бы логику в движении цены, является демотивирующим. Однако у этой проблемы тоже есть решение. Она стремится объединить подходы, начиная с 11-й части этой серии до настоящего времени. Хотя данное решение может быть не сразу очевидно для новичков, оно вполне понятно для тех, кто имеет больший опыт в программировании. Таким образом, мы не будем создавать новую функцию моделирования с нуля. Мы будем чередовать менее плавные и более плавные движения, что определяется самим симулятором. Решение о плавности движения будет основано всего на пяти элементах информации: цена открытия, цена закрытия, максимальная цена, минимальная цена и тиковый объем. Это единственные данные, необходимые для такого выбора. Поэтому мы не будем приводить здесь окончательное и финальное решение. Наша цель - показать один из множества возможных способов создания и моделирования движений в рамках 1-минутного бара.
Мы исследовали использование случайного блуждания в различных сценариях и остановились на пути наименьших усилий.
Как уже говорилось выше, необходимо искать метод, который включает в себя определенную логику. Исключительная зависимость от рандомизации не дает удовлетворительных результатов, даже при применении 1-минутного бара и использовании более длительных графиков, например, 10 или 15 минут. В идеале движения должны быть постепенными, чтобы избежать резких мгновенных переходов от одного конца к другому. Таким образом, движение рисуется постепенно, что создает впечатление случайности, хотя на самом деле это результат простых математических расчетов, создающих кажущуюся сложность. Это одна из основ стохастических движений.
Чтобы сделать поток более продуманным и плавным, необходимо устранить некоторые существующие функции и установить правила, которые будут направлять движение управляемым образом. Важно отметить, что мы не должны пытаться насильно направить движение в определенном направлении, но нам нужно определить правила для MetaTrader 5, чтобы он вел процесс так, как посчитает нужным. Для этого сначала нужно модифицировать код процедуры случайного блуждания. Ниже представлен новый код с выделенными поправками:
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) { double vStep, vNext, price, vH = High, vL = Low; char i0 = 0; vNext = vStep = (Out - In) / ((High - Low) / m_TickSize); for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++) { price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -1 : 1)); price = (price > vH ? price - m_TickSize : (price < vL ? price + m_TickSize : price)); price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -iDesloc : iDesloc)); price = (price > vH ? vH : (price < vL ? vL : price)); MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick); switch (iMode) { case 1: i0 |= (price == High ? 0x01 : 0); i0 |= (price == Low ? 0x02 : 0); vH = (i0 == 3 ? High : vH); vL = (i0 ==3 ? Low : vL); break; case 0: if (price == Close) return c0; default: break; } if (((int)floor(vNext)) >= c1) continue; else if ((++c2) <= 3) continue; vNext += vStep; 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))); 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))); if (iMode == 2) { vL = (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize)); vH = (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH)); }else { if (Close > vL) vL = (i0 == 3 ? vL : vL + m_TickSize); else vH = (i0 == 3 ? vH : vH - m_TickSize); } } return Out; }
Изменения включают в себя замену сегментов кода на новые зеленые фрагменты. Хотя изменения могут показаться малозаметными, они обеспечивают гораздо большую гибкость по сравнению с предыдущей версией. Ранее движение было непрерывным, тик за тиком, без разрывов (гэрпов) между ними, что требовало большого объема сделок для плавного моделирования случайного блуждания. Введение гэпов в 1-минутный бар значительно сокращает количество необходимых сделок, позволяя моделировать систему с различными объемами и параметрами 1-минутных баров. Это приводит к графической адаптации движения, генерируемого случайного блуждания, при достижении четырех основных значений: открытия, закрытия, максимума и минимума. Промежуточное поведение определяется случайным блужданием. Однако решающий аспект заключается в функции, вызывающей случайное блуждание. Данная функция подробно описана ниже:
inline int Simulation(const MqlRates &rate, MqlTick &tick[]) { int i0, i1, i2; bool b0; m_Marks.iMax = (int) rate.tick_volume - 1; m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); Simulation_Time(rate, tick); MountPrice(0, rate.open, rate.spread, tick); if (CheckViability(rate)) if (m_Marks.iMax > 10) { i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); i1 = m_Marks.iMax - i0; i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); i2 = (i2 == 0 ? 1 : i2); b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 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); RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); m_Marks.bHigh = m_Marks.bLow = true; }else Random_Price(rate, tick); if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); CorretTime(tick); return m_Marks.iMax; }
Эта функция показывает, что теперь мы больше не будем проверять осуществимость случайного блуждания тем способом, который мы применяли на протяжении всей статьи. Теперь оценка происходит полностью в этой функции. Удивительно, но система попытается выполнить случайное блуждание с минимальным объемом всего в 10 сделок. Для объемов на уровне или ниже этого порога будет использована чистая рандомизация, которая в данном конкретном контексте считается более эффективной, чем случайное блуждание. Инновационным аспектом является создание "разрывов" внутри 1-минутного бара, что обеспечивается специальными расчетами, упомянутыми выше. Однако для правильной работы случайного блуждания важно обеспечить генерацию хотя бы 1 тика.
Однако данный процесс не обходится без трудностей. Для эффективности случайного блуждания необходим дополнительный контроль. Этот дополнительный контроль строится специальным тестом, значение которого можно регулировать по мере необходимости. Если объем сделок превышает 1000 за 1 минуту, система моделирования может выбрать путь, решая случайным образом, к какому максимуму или минимуму идти сначала. С другой стороны, если объем меньше установленного, начальное направление случайного блуждания будет определяться, исходя из близости цены открытия к максимальному или минимальному значению бара.
Данный метод, известный как "путь наименьших усилий", эффективен в ситуациях, когда количество необходимых движений меньше, чем общее расстояние, которое нужно пройти. Это позволяет избежать выбора, который может привести к неоправданно долгим и сложным маршрутам. Из-за такого расчетного подхода некоторые обсуждения и методы, предложенные в этой статье, могут отсутствовать в итоговом приложении. Для иллюстрации эффективности системы представлены два рисунка: график, построенный на основе реальных данных о тиках, и результат моделирования с использованием стратегии наименьших усилий.
Рисунок 02 - График, построенный на основе реальных данных
Рисунок 03 - График, построенный на основе данных, смоделированных системой.
Хотя на первый взгляд графики могут показаться идентичными, но это не так. При более внимательном рассмотрении можно обнаружить различия, например, источник данных, указанный на каждом рисунке, чтобы подчеркнуть разницу между ними. Данное сравнение предлагает пользователям провести эксперимент, используя данные, представленные в приложении, конкретно для актива EURUSD - валютная пара из FOREX. Эта демонстрация показывает, что метод моделирования может быть адаптирован как для построений LAST, так и для построений BID, что позволяет проверить эффективность системы на основе имеющихся данных.
Заключение
Данная статья является важнейшим шагом в подготовке к тому, чтобы сделать систему репликации/моделирования полностью функциональной. В следующей статье мы рассмотрим последние настройки, необходимые перед тем, как перейти к дальнейшему описанию сервиса репликации/моделирования. Данный этап важен для тех, кто стремится понять работоспособность и эффективность системы в условиях тестирования.
Необходимо подчеркнуть важность приложенных файлов: Из-за большого объема данных, особенно реальных тиков для будущих активов, предоставим четыре файла, каждый из которых связан с определенным активом или рынком. Основной файл включает в себя исходный код системы до текущего состояния разработки. Для обеспечения целостности структуры и функциональности системы все файлы должны быть загружены и сохранены в директории, указанной редактором MQL5. Затем систему можно использовать с демонстрационными файлами, повторяя показанные процедуры. Этот процесс соответствует методологии, применяемой в представленных видеоуроках.
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/11189
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования