English
preview
Возможности Мастера MQL5, которые вам нужно знать (Часть 99): Использование KD-дерева и эхо-сети (ESN) в пользовательском классе управления капиталом

Возможности Мастера MQL5, которые вам нужно знать (Часть 99): Использование KD-дерева и эхо-сети (ESN) в пользовательском классе управления капиталом

MetaTrader 5 — Торговые системы |
83 0
Stephen Njuki
Stephen Njuki

Введение

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

Сегодня мы переходим к рассмотрению схожей, но несколько иной категории рыночных условий. Рыночный режим с высоким системным риском и высокой волатильностью. Мы переходим от последовательного сопоставления паттернов к топологии, в большей степени ориентированной на структуру, и вводим подход, объединяющий анализ близости на основе KD-дерева с рекуррентной динамикой эхо-сетей (ESN). Наш подход предназначен для трейдеров, работающих в условиях хвостового риска, когда необходимо оценить, насколько текущая ценовая динамика близка к той, что наблюдалась во время исторических событий, связанных с ликвидностью. Мы объединяем механизм пространственного отображения с облегчённой сетью на основе резервуарных вычислений. В результате мы получаем защитную систему для адаптивного определения объема позиции с учетом того, когда рыночная ситуация достигает «критического порога».

Ретроспектива в сравнении с перспективой: от микропаттернов к топологии рыночных режимов

В наших предыдущих исследованиях пользовательских методов управления капиталом мы построили достаточно надежные модели с использованием таких алгоритмов, как деревья Фенвика, вейвлетная пороговая обработка и суффиксные автоматы. Эти алгоритмы были объединены с возможностями глубокого обучения, предоставляемыми сетями CNN и LSTM. В основном эти модели были ориентированы на запоминание последовательностей и шумоподавление на уровне микропаттернов. Такой подход может подходить для интерпретации рынков как непрерывного потока перекрывающихся сигналов, требующих высокочастотной фильтрации. Представляя сегодня KD-дерево в сочетании с эхо-сетью (ESN), мы не ставим под сомнение предыдущие подходы, а лишь существенно расширяем набор инструментов управления капиталом.

Можно сказать, что восприятие финансовых рынков во многом субъективно: два трейдера, наблюдая одну и ту же ценовую динамику, могут сделать совершенно разные, но одинаково обоснованные выводы. Хотя в наших предыдущих моделях мы интерпретировали данные последовательно, KD-дерево позволяет рассмотреть их через призму пространства и топологии. Таким образом, в нашем алгоритме вместо вопроса «Какая именно последовательность паттернов только что сформировалась?» мы задаем вопрос «Где мы сейчас находимся на исторической карте рыночной волатильности?» Дополняя KD-дерево эхо-сетью (ESN), которая обрабатывает временную динамику данных с малой задержкой, мы получаем модель, работающую как динамический резервуар, без ресурсоемкого итеративного обучения, характерного для ранее использовавшихся сетей, таких как LSTM. Мы предлагаем не обязательно более эффективный метод, а просто альтернативу. Эти два сетевых подхода также частично пересекаются, что призвано дать разработчику возможность выбрать способ анализа, лучше соответствующий его собственному представлению о структуре рынка.

Предполагаемые рыночные условия:

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

Например, свинг-трейдер, придерживающийся системного подхода, может использовать наш предыдущий класс Wavelet/LSTM в обычных рыночных условиях с возвратом к среднему для точного регулирования объема позиций в лотах. Однако тот же трейдер, иначе оценивая риск в ответственные периоды, например при объявлениях центральных банков или публикации данных по занятости в несельскохозяйственном секторе (NFP), мог бы параллельно запустить другой советник и торговать тем же активом на другом таймфрейме, но уже с использованием класса KD-дерево/ESN. В таких ситуациях приоритет трейдера сместился бы с максимального использования тренда на оценку положения в пространстве рыночных состояний ради защиты от риска. Трейдеру пришлось бы задействовать алгоритмический защитный ограничитель, который отслеживает, не приближается ли точка с текущими координатами волатильности опасно близко к критической границе. Этот класс предназначен для разработчика, который считает, что управление капиталом может быть модульным и адаптироваться не только к рынку, на котором ведется торговля, но и к конкретным порогам риска по мере их возникновения.

Проблема: ограничения линейного масштабирования объема позиции:

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

Если торговая стратегия выявляет отличную точку входа в длинную позицию по сигналам стандартных индикаторов, то логика управления капиталом обычно определяет объем позиции исходя из фиксированного процента риска от доступной свободной маржи. Тем не менее, если рынок в целом стремительно приближается к состоянию с характерными признаками обвала — например, аномальным сужением ценового диапазона, за которым следует резкий скачок ATR (среднего истинного диапазона), — можно утверждать, что такой стандартный объем в лотах уже не оптимален с точки зрения риска. Такой подход фактически не учитывает возможность структурного обвала.

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

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


Описание модели

Чтобы преодолеть ограничения линейного масштабирования риска, мы используем два компонента: KD-дерево («Картограф») и эхо-сеть (ESN). Давайте разберемся в этом.

Модуль 1: «Картограф»:

Представим, что рынок — это огромный трёхмерный ландшафт с холмами и долинами, которые соответственно обозначают ценовые вершины и «зоны обвала»; в таком случае мы могли бы представить себе KD-дерево в роли картографа. Этот алгоритм рассматривает точки данных о ценах не как последовательность, а с точки зрения их геометрии. Используя логарифмические доходности цены и значения ATR (средний истинный диапазон) как координаты в фиксированной системе (x, y), дерево рекурсивно разбивает исторические данные на пространственную сетку. Каждый раз, когда советник готовится открыть сделку, KD-дерево выполняет быстрый поиск, чтобы выделить среди k ближайших соседей те, которые помечены как «обвалы». Когда текущие рыночные условия, теперь представленные координатами, оказываются слишком близки к любому из таких исторических узлов опасности, наша модель сразу же распознает это как структурный паттерн риска независимо от преобладающей ценовой динамики.

Механизм 2: Барабанный бой:

Если KD-дерево дает нам карту, то эхо-сеть (ESN) позволяет почувствовать временной «пульс». Обычно обучение глубоких нейронных сетей требует значительных вычислительных ресурсов, тогда как ESN не нуждается в столь затратном обучении, поскольку основана на резервуарных вычислениях. Чтобы наглядно проиллюстрировать это, представьте себе большую бочку, наполненную жидкостью: если бросить в неё камень (этот камень можно считать нашим входным сигналом), на поверхности воды появятся волны. Эти волны, которые также можно рассматривать как внутренние состояния, будут распространяться по воде, образуя сложные рекурсивные узоры. ESN не пытается изменить устройство бочки; она лишь наблюдает за тем, как эти волны резонируют в фиксированной сети внутренних нейронов.

Интегрированный рабочий процесс

Сила нашей модели заключается в синергии KD-дерева и ESN. Подводя итог сказанному выше, алгоритм KD-дерева позволяет определить «безопасное расстояние», отвечая на вопрос: находимся ли мы в той области топологического пространства, где в прошлом происходили рыночные обвалы? Со своей стороны, ESN, обеспечивающая модуляцию ценового импульса, ставит вопрос: «Отражает ли текущая последовательность доходностей временное явление или устойчивую смену рыночного режима?»

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


Формулы модели

Переход от концептуальной метафоры к рабочему коду обычно начинается с изложения основы модели — ее математического аппарата. KD-дерево и ESN работают по принципам, кардинально отличающимся друг от друга. Одно из них можно рассматривать как геометрическое, а другое — как «динамическое».

Разбиение по измерениям в KD-дереве

Это дерево упорядочивает исторические данные, разбивая двумерное пространство признаков на гиперпрямоугольники; координаты (x, y) в этом пространстве соответствуют логарифмическим доходностям и ATR (среднему истинному диапазону). На каждом уровне глубины d дерева мы меняем ось разбиения, чтобы обеспечить равномерное покрытие пространства. Это задается оператором взятия остатка от деления:

f1

Где:

  • Ось: индекс координаты, который может принимать значение 0 для логарифмической доходности или 1 для ATR (среднего истинного диапазона). Это помогает начертить пространственную границу
  • d: текущая глубина рекурсии в дереве; отсчет начинается с корневого узла, для которого d равно нулю.
  • mod 2: операция взятия остатка от деления на общее число измерений (в нашем случае — 2). Благодаря этому алгоритм чередует плоскости разбиения.


Наша формула гарантирует, что дерево способно сбалансировать пространственное распределение точек данных. Это обеспечивает сложность поиска O(log n) при запросе ближайших исторических точек, соответствующих обвалам цен. Близость текущего состояния рынка P(r, a) к ближайшей точке обвала Q измеряется обычным евклидовым расстоянием:

f2

Где:

  • D: евклидово расстояние между текущим состоянием и историческим узлом, которое служит в модели «показателем близости к опасности».
  • r_P: координата логарифмической доходности, характеризующая текущую ценовую динамику на рынке.
  • r_Q: логарифмическая доходность исторической точки рыночных данных, хранящейся в узле дерева.
  • a_P: координата ATR (среднего истинного диапазона), характеризующая текущую волатильность в реальном времени
  • a_Q: координата ATR (средний истинный диапазон) для точки исторических данных (узел Q)


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

Динамика эхо-сети (ESN)

ESN переносит нас во временную область. Взаимосвязь цены и времени. Наша сеть состоит из резервуара с N нейронами, имеющими фиксированные случайные внутренние связи (W_res) и входные связи (W_in). Вектор внутреннего состояния x(t) обновляется при появлении каждого нового ценового бара на основе предыдущего состояния и текущего входного сигнала u(t).

f3

Где:

  • x(t): обновленный вектор внутреннего состояния или пульсации в резервуаре в момент времени t
  • tanh(): функция активации «гиперболический тангенс», служащая линейным стабилизатором, ограничивающим значения диапазоном [-1, 1]
  • W_in: Матрица входных весов, инициализированная случайным образом.
  • u(t): входной вектор на временном шаге t
  • W_res: Матрица фиксированных рекуррентных весов
  • x(t-1): вектор внутреннего состояния резервуара на предыдущем временном шаге.


Функция гиперболического тангенса (tanh) используется для нелинейного «сжатия», которое удерживает состояние резервуара в стабильном диапазоне и предотвращает «взрыв» сети (по аналогии с проблемой взрыва градиентов).

Преобразование состояния в оценку риска в выходном слое

Наконец, мы преобразуем многомерное состояние резервуара в единственный множитель объема позиции y(t). Для этого мы используем матрицу выходных весов W_out и сигмоидное масштабирование, которое гарантирует, что итоговый коэффициент останется в безопасном, контролируемом диапазоне [0.5; 1.5].

f4

Где:

  • y(t): выходной множитель объема позиции, вычисляемый сетью
  • 0.5: Смещение, задающее нижнюю границу. Гарантирует, что даже самые пессимистичные прогнозы не могут быть ниже 50 %
  • e: число Эйлера (прибл. 2.71828).
  • W_out: Матрица выходных весов.
  • x(t): Итоговый вектор внутреннего состояния резервуара после полной обработки исторических данных.


Наша эхо-сеть (ESN) способна формировать «плавный» коэффициент корректировки, по сути указывая системе снижать рисковую экспозицию, когда временная последовательность доходностей выглядит нестабильной. Благодаря этому у нас есть защитный механизм, способный отсеивать опасные конфигурации волатильности, даже если сами по себе пространственные координаты могли бы показаться безопасными.


Реализация на MQL5

Как и всегда, переход от теории к реализации нашей двухкомпонентной модели в виде работающего пользовательского класса управления капиталом требует тщательной проработки структуры. Создаваемый нами класс «CMoneyKDTreeESN» наследуется от стандартного базового класса «CExpertMoney». Такое наследование играет ключевую роль в обеспечении беспрепятственной интеграции с экосистемой индикаторов и функций для работы с торговыми символами MQL5, а также является необходимым условием для создания пользовательского класса, пригодного для сборки советника в Мастере MQL5. Прежде чем приступить к рассмотрению кода, давайте разберемся в том, чем мы руководствовались при выборе индикаторов для этой модели.

Почему именно ATR и MACD

Нашему пространственному дереву нужны две координаты (x, y), чтобы наносить точки на карту. Координата X — это логарифмическая доходность, рассчитанная по ценам. Мы рассчитываем этот показатель на основе истории цен, чтобы оценить само движение цены. Координата Y, в свою очередь, — это ATR (средний истинный диапазон). Мы выбрали ATR, поскольку абсолютную величину падения цены нельзя адекватно оценить без учета волатильности. Падение на 1 % при низкой волатильности — не более чем медленное снижение стоимости актива; напротив, падение на 1 %, сопровождающееся резким скачком ATR, — признак обвала ликвидности. Таким образом, включение ATR помогает нормализовать оценку падения цены для разных типов активов. Индикатор MACD используется в качестве вектора направления. Если KD-дерево мы используем для построения географической карты, то индикатор MACD (схождение/расхождение скользящих средних) позволяет отобразить «вектор ценового импульса». Разность между основной и сигнальной линиями индикатора MACD (схождение/расхождение скользящих средних) дает чисто математическое представление об истощении или ускорении тренда. Четыре режима работы алгоритма, которые мы рассмотрим ниже, используют эту информацию, чтобы определить, как действовать при приближении к «узлу обвала».

Шаг 1: Построение KD-дерева

Мы реализуем KD-дерево с помощью двух классов: 'CKDNode' — обертка для данных, а 'CKDTreeCrash' — обертка для логики. Наша обертка для данных выглядит следующим образом:

//+------------------------------------------------------------------+
//| KD-Tree Node Class                                               |
//+------------------------------------------------------------------+
class CKDNode
  {
public:
   double            point[2]; // Feature 0: Log Return, Feature 1: ATR
   bool              is_crash;
   CKDNode           *left;
   CKDNode           *right;

                     CKDNode(double f1, double f2, bool crash)
     {
      point[0] = f1;
      point[1] = f2;
      is_crash = crash;
      left = NULL;
      right = NULL;
     }
                    ~CKDNode(void)
     {
      if(CheckPointer(left) != POINTER_INVALID)
         delete left;
      if(CheckPointer(right) != POINTER_INVALID)
         delete right;
     }
  };

Каждый узел содержит двумерные координаты 'point[2]', булев флаг, указывающий, произошел ли в данный исторический момент «обвал» (определяемый по заданному пользователем порогу 'm_crash_threshold'), а также указатели на дочерние узлы в пространственной структуре дерева. Самая важная функция в этом механизме — 'InsertRec': она постепенно строит пространственную карту, добавляя данные точка за точкой.

   //--- Simple recursive insertion
   CKDNode*          InsertRec(CKDNode *node, double f1, double f2, bool crash, unsigned int depth)
     {
      if(node == NULL)
         return new CKDNode(f1, f2, crash);
      unsigned int cd = depth % 2;
      double feature = (cd == 0) ? f1 : f2;
      if(feature < node.point[cd])
         node.left = InsertRec(node.left, f1, f2, crash, depth + 1);
      else
         node.right = InsertRec(node.right, f1, f2, crash, depth + 1);
      return node;
     }

Как видно из приведенного выше листинга, сначала мы проверяем, равен ли указатель на переданный узел null. По замыслу, всякий раз, когда мы доходим до пустого места на нашей карте, мы вставляем новый узел. Мы объявляем целое число без знака cd, которое выступает в роли «пульса» движка. В нем хранится остаток от деления текущей глубины дерева на 2. Когда глубина равна 0, значение 'cd' также равно 0 (мы делим карту по вертикали по значению логарифмической доходности). Если глубина равна 1, то cd будет равен 1, и разбиение будет горизонтальным по значению ATR (средний истинный диапазон).

Именно чередование гиперплоскостей делает это дерево «k-мерным», а не обычным бинарным деревом. Затем мы используем активную ось ('cd'), чтобы определить, где на карте должны находиться данные об исторической ценовой динамике: слева (при меньшем значении) или справа (при большем значении). Этот процесс выполняется рекурсивно до тех пор, пока функция не найдет своё «структурное место». Уровень опасности оценивается с помощью 'SearchRec': мы ищем k ближайших узлов обвала, а затем вызываем 'GetCrashRiskDistance', чтобы получить евклидово расстояние до этих узлов. Когда это расстояние становится небольшим, мы оказываемся на краю исторического обрыва!

Шаг 2: Программирование эхо-сети (ESN)

В то время как KD-дерево перестраивается на каждом новом ценовом баре для поддержания пространственной карты в актуальном состоянии, эхо-сеть (ESN) ('CEchoStateNet') использует фиксированные внутренние веса, инициализированные случайным образом, для создания «резервуара» временной памяти.

   void              InitBaseline()
     {
      //--- Random baseline initialization for reservoir
      MathSrand(1337);
      for(int i = 0; i < m_res_size; i++)
        {
         m_W_in[i] = (double(MathRand()) / 32767.0) * 0.2 - 0.1;
         m_W_out[i] = (double(MathRand()) / 32767.0) * 0.2 - 0.1;
         for(int j = 0; j < m_res_size; j++)
           {
            m_W_res[i * m_res_size + j] = (double(MathRand()) / 32767.0) * 0.4 - 0.2;
           }
        }
     }

Из приведенного выше кода видно, что на веса накладываются жесткие ограничения (0.4–0.2). Такое затухание позволяет резервуару удовлетворять «свойству эхо-состояния». Когда величина весов стремится к бесконечности, внутренние колебания также неограниченно усиливаются, что приводит к проблеме взрыва градиентов. С другой стороны, если веса слишком малы, память сразу же угасает. Основная обработка временной последовательности выполняется в функции Calculate():

   //--- Processes a sequence, returns a risk-modulated multiplier [0.5, 1.5]
   double            Calculate(const double &sequence[])
     {
      double state[];
      double next_state[];
      ArrayResize(state, m_res_size);
      ArrayResize(next_state, m_res_size);
      ArrayInitialize(state, 0.0);
      for(int t = 0; t < ArraySize(sequence); t++)
        {
         double u = sequence[t];
         for(int i = 0; i < m_res_size; i++)
           {
            double res_sum = 0;
            for(int j = 0; j < m_res_size; j++)
               res_sum += m_W_res[i * m_res_size + j] * state[j];
            next_state[i] = MathTanh(m_W_in[i] * u + res_sum);
           }
         ArrayCopy(state, next_state);
        }
      //--- Readout
      double output = 0.0;
      for(int i = 0; i < m_res_size; i++)
         output += m_W_out[i] * state[i];
      //--- Sigmoid scale to [0.5, 1.5]
      return 0.5 + (1.0 / (1.0 + MathExp(-output)));
     }
  };

В приведенном выше коде мы сначала перебираем последовательность логарифмических доходностей в хронологическом порядке, подавая на вход по одной точке данных 'u'. В рамках этого цикла текущее состояние взаимодействует само с собой на основе матрицы рекуррентных весов. Это можно представить как «жидкость», плещущуюся в барабане. Мы присваиваем значение следующего состояния с помощью гиперболической функции, которая ограничивает активацию нейрона в диапазоне от -1 до 1. Это обеспечивает стабильность резервуара, несмотря на рекурсивные вычисления. В завершение мы агрегируем значения выходного слоя и применяем сигмоидную огибающую. Полученное выходное значение лежит в диапазоне от 0 до 1. Таким образом, прибавление 0.5 сдвигает диапазон до [0.5; 1.5], что позволяет безопасно использовать полученное значение.

Шаг 3: 4 режима работы

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

Режим 1: Дивергенция ценового импульса

//+------------------------------------------------------------------+
//| Mode 1: Momentum Divergence                                      |
//| Scales lot sizes based on MACD trends vs Proximity to a Crash.   |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode1_MomentumDivergence(double crash_dist, double atr, double macd_main, double macd_sig)
  {
//--- If price footprint is heavily aligned with historical crash areas
   if(crash_dist < 0.05)
     {
      //--- MACD shows bullish resilience, scale safely
      if(macd_main > macd_sig && macd_main > 0)
         return 1.0;
      //--- Downward momentum + Crash proximity = Extreme cut
      return 0.3;
     }
//--- Low crash risk, momentum carries the size
   return 1.2;
  }

Когда KD-дерево указывает на то, что мы находимся очень близко к историческому обвалу ('dist < 0.05'), мы обращаемся к MACD (схождение/расхождение скользящих средних). Если ценовой импульс по-прежнему имеет выраженную бычью направленность, то при определении объема позиции коэффициент масштабирования остается равным 1.0. сохраняя объем неизменным. Однако если ценовой импульс направлен вниз, сочетание опасной близости в пространстве признаков и медвежьего ценового импульса требует резкого сокращения объема позиции до 30 % от исходного объема. В безопасных ситуациях мы можем немного увеличить объем позиции, применив множитель 1.2.

Режим 2: Всплеск волатильности

//+------------------------------------------------------------------+
//| Mode 2: Volatility Spike                                         |
//| Looks at ATR expansion. Filters fakeouts using KD-Tree Risk.     |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode2_VolatilitySpike(double crash_dist, double atr, double macd_main, double macd_sig)
  {
   double avg_atr_pseudo = atr * 1.5; // Context baseline threshold
//--- Breakout: High ATR but far away from historical crash topologies
   if(atr > avg_atr_pseudo && crash_dist > 0.1)
      return 1.5;
//--- High volatility near crash zones is toxic
   if(atr > avg_atr_pseudo && crash_dist < 0.05)
      return 0.5;
   return 0.8;
  }

Этот режим в значительной степени зависит от ATR (среднего истинного диапазона). Резкий скачок ATR (среднего истинного диапазона) выше значения 'avg_atr_pseudo' может означать как устойчивый пробой, так и опасный обвал. Поэтому мы обратимся к KD-дереву, чтобы определить характер всплеска волатильности. Если расстояние до узла обвала велико, то есть превышает 0.1, это указывает на устойчивый пробой, поэтому мы увеличим объем позиции на 150 % от исходного. Если же это расстояние мало, например менее 0.05, это может предвещать резкий рост волатильности, и мы сократим рисковую экспозицию вдвое.

Режим 3: Истощение тренда

//+------------------------------------------------------------------+
//| Mode 3: Trend Exhaustion                                         |
//| Reduces exposure when MACD Histogram flips near risk nodes.      |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode3_TrendExhaustion(double crash_dist, double atr, double macd_main, double macd_sig)
  {
   double histogram = macd_main - macd_sig;
//--- Bearish histogram reversal happening close to a crash profile
   if(histogram < 0 && crash_dist < 0.08)
      return 0.4;
//--- Bullish histogram expanding with safety distance
   if(histogram > 0 && crash_dist > 0.1)
      return 1.25;
   return 0.9;
  }

В этом режиме учитывается не только направление MACD (схождение/расхождение скользящих средних), но и его «производная» — гистограмма. Когда гистограмма становится отрицательной и мы находимся вблизи зоны риска в пространстве признаков, мы уменьшаем объем позиции до 40 % от базового. Этот механизм служит системой раннего предупреждения о возможном сломе основного тренда.

Режим 4: Консервативное неприятие риска

//+------------------------------------------------------------------+
//| Mode 4: Conservative Risk Aversion                               |
//| Strict algorithmic check relying mainly on KD-tree distance.     |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode4_ConservativeAversion(double crash_dist, double atr, double macd_main, double macd_sig)
  {
//--- In conservative mode, if we are even remotely close to a crash profile, halve the size.
   if(crash_dist < 0.1)
      return 0.5;
//--- Even if safe, only allocate 100% of the calculated lot (no overleveraging)
   return 1.0;
  }

Логика этого режима рассчитана на управляющих портфелями с очень строгими требованиями к риску. Индикаторы не учитываются. Когда KD-дерево обнаруживает близость к точке исторического обвала — при расстоянии менее 0.1, — объем выделяемого капитала немедленно сокращается вдвое. Мы никогда не превышаем 100 % базового уровня риска, то есть наш множитель объема позиции никогда не превышает 1.0.

Шаг 4: Выполнение основного рабочего процесса (`Optimize`)

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

1. Получение данных

Мы используем встроенные функции CopyClose и CopyBuffer для загрузки истории цен и значений индикаторов ATR (средний истинный диапазон) и MACD (схождение/расхождение скользящих средних) в локальные массивы длиной m_history_length.

2. Перестроение дерева

      //--- 2. Build tree and sequence
      for(int i = 0; i < m_history_length; i++)
        {
         log_returns[i] = MathLog(closes[i] / closes[i + 1]);
         m_kdtree.InsertPoint(log_returns[i], atr_buf[i]);
        }

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

3. Пространственный запрос и применение алгоритма

      //--- 3. Assess Current Environment
      double curr_return = log_returns[0];
      double curr_atr = atr_buf[0];
      double curr_macd_main = macd_main_buf[0];
      double curr_macd_sig = macd_sig_buf[0];
      // Distance to nearest Crash
      double crash_dist = m_kdtree.GetCrashRiskDistance(curr_return, curr_atr);
      //--- 4. Apply Selected Iteration Mode (Algorithm)
      double scale_factor = 1.0;
      switch(m_algo_mode)
        {
         case 1:
            scale_factor = Mode1_MomentumDivergence(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
         case 2:
            scale_factor = Mode2_VolatilitySpike(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
         case 3:
            scale_factor = Mode3_TrendExhaustion(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
         case 4:
            scale_factor = Mode4_ConservativeAversion(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
        }
      lot = lot * scale_factor;

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

4. Временная оценка (фильтр ESN)

      //--- 5. Apply the Echo State Network Filter if toggled on
      if(m_use_esn)
        {
         double esn_input[];
         ArrayResize(esn_input, m_history_length);
         for(int i = 0; i < m_history_length; i++)
            esn_input[i] = log_returns[m_history_length - 1 - i] * 100.0; // Chronological order
         double esn_coef = m_esn.Calculate(esn_input);
         lot = lot * esn_coef;
        }

Если трейдер включил нейронную сеть, наша модель меняет порядок элементов массива логарифмических доходностей на обратный, чтобы подать их в ESN в строгом хронологическом порядке — от самых ранних к самым поздним. Входные данные умножаются на 100. чтобы их значения были достаточно велики для заметной активации нейронов резервуара с функцией активации tanh. Полученный коэффициент esn_coef используется для окончательной корректировки исходного объема в лотах с учетом временной динамики.

5. Проверка соответствия требованиям брокера

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


Запуски тестера стратегий

Для проверки эффективности нашей модели с двумя механизмами обработки необходимо провести оптимизацию, а затем — форвардное тестирование. Мы проводим тестирование на валютной паре GBPJPY на 2-часовом таймфрейме в период с 01.01.2025 по 01.05.2026. Оптимизация проводится на данных за 2025 год, а форвардное тестирование — на данных за 2026 год. Поскольку это пользовательский класс управления капиталом, для создания советника в Мастере MQL5 нам также потребуется класс сигналов. Для тестирования мы используем встроенные классы сигналов Envelopes и RSI. Как обычно, мы также проводим два теста: один — только с алгоритмом KD-дерева, а другой — с этим алгоритмом и эхо-сетью (ESN).

Только алгоритм KD-дерева:

r1

Алгоритм KD-дерева и эхо-сеть (ESN):

r2

Основные выводы

Результаты нашего форвардного тестирования действительно подкрепляют предположение о том, что модуль ESN способен работать на данных вне обучающей выборки, не теряя полностью своей эффективности. Добавление этого второго защитного барьера позволило превратить убыток −975 в небольшую прибыль +26, а просадка сократилась с 13.2 % до 9.1 %. Тем не менее профит-фактор 1.02 и доля прибыльных сделок 41.6 % могут свидетельствовать о том, что эта модель лишь держится на плаву и не обеспечивает стабильной альфы.

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


Заключение:

Исследование концептуальной уязвимости — несостоятельности линейного подхода к определению объема позиции в условиях структурных изменений рынка — привело нас к созданию модели с двумя механизмами и оказалось весьма поучительным. Объединение пространственного картирования рисков с помощью KD-дерева с устойчивостью эхо-сети (ESN) во времени дало нам теоретическую основу, способную существенно повысить шансы стратегии на выживание в условиях хвостовых рисков и неопределённости.

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

имяОписание
wz_99.mq5Советник, созданный с помощью Мастера MQL5
MoneyKDTreeESN.mqhПользовательский класс управления капиталом, используемый при создании советника с помощью Мастера MQL5
r1.setНастройки входных параметров для первого тестового прогона
r2.setНастройки входных параметров для второго тестового прогона

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

Прикрепленные файлы |
MQL5.zip (9.77 KB)
Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (формирование и оценка действий) Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (формирование и оценка действий)
Продолжаем торговую адаптацию фреймворка Habi на MQL5 с использованием OpenCL. Разбираем реализацию выходных слоёв Decoder и Critic, передачу общих параметров латентного распределения вместе с пакетом торговых действий и подготовку обучающих сигналов по эталону, историческим результатам и TD-цели. Показываем, как объединяются прямой градиент критика и сигнал, прошедший через декодер и латентную выборку. Это позволяет подготовить компоненты к последующей сборке и обучению торговой модели.
Архитектура машинного обучения в MetaTrader 5 (Часть 18): Исправленный последовательный бутстрэп — клонирование, стирание типа и инструментарий сравнения Архитектура машинного обучения в MetaTrader 5 (Часть 18): Исправленный последовательный бутстрэп — клонирование, стирание типа и инструментарий сравнения
В статье выявляются два дефекта, которые сводят на нет эффект последовательного бутстрэпа при перекрестной проверке: стирание типа SequentiallyBootstrappedBaggingClassifier и несоответствие размерностей на уровне фолда из-за клонирования полного набора информационных интервалов наблюдений. Предлагаемое исправление сохраняет принадлежность классификатора к исходному классу, добавляет функцию _find_seq_bagging() для повторной передачи в CalibratorCV.fit данных t1, ограниченных текущим фолдом, и сбрасывает состояние при каждом разбиении. Новый модуль bootstrap_comparison выводит OOF-метрики и на наблюдениях вне бутстрэп-выборки (OOB), а также данные об использовании памяти, позволяя проверить правильность применения последовательного отбора и количественно оценить его влияние.
Каналы линейной регрессии на MQL5: Построение статистически обоснованных доверительных и прогнозных полос Каналы линейной регрессии на MQL5: Построение статистически обоснованных доверительных и прогнозных полос
В статье реализованы каналы скользящей регрессии методом МНК на языке MQL5 и рассчитываются доверительные и прогнозные полосы с использованием критических значений t-распределения Стьюдента вместо фиксированного множителя стандартного отклонения. В ней объясняется расширение полос по краям окна, обусловленное рычагом наблюдения, полученный результат сравнивается с полосами Боллинджера и каналами Дончиана, а также рассматриваются предпосылки МНК применительно к ценовым данным. Описан способ отрисовки с использованием пяти линий, обеспечивающий надежное отображение в MetaTrader 5.
Отложенная загрузка хендлов индикаторов в MQL5: Менеджер ресурсов для мультитаймфреймовых советников Отложенная загрузка хендлов индикаторов в MQL5: Менеджер ресурсов для мультитаймфреймовых советников
Мультитаймфреймовые торговые советники, которые инициализируют все хэндлы индикаторов в OnInit(), несут фиксированные затраты на запуск, даже если большинство хэндлов никогда не используется. CIndicatorCache использует отложенную загрузку с поиском по составному ключу, методы получения/освобождения AcquireHandle() и ReleaseHandle() с подсчетом ссылок и детерминированный метод FlushAll() для полной очистки кэша. Хэндлы создаются при первом запросе и повторно используются на последующих тиках, что сокращает задержку при запуске, позволяет избежать повторного выделения памяти в куче и предотвращает утечки ресурсов терминала благодаря централизованному владению ресурсами.