preview
Сеточный советник на клеточном автомате с онлайн-обучением в MQL5 (Часть III): Живой граф признаков

Сеточный советник на клеточном автомате с онлайн-обучением в MQL5 (Часть III): Живой граф признаков

MetaTrader 5Эксперты |
43 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Обучаемый граф признаков: когда связи между рыночными факторами становятся живыми

В предыдущей части этой серии мы перевели архитектуру Cellular10K из классической сетки в графовую форму. Признаки перестали быть просто массивом feat[50], а клетки клеточного автомата получили не только геометрические соседства, но и логические связи по признакам, стратегиям и дальним small-world-хордам. Это был важный шаг: модель начала мыслить не плоской решёткой, а сетью взаимодействующих агентов.

Но у фиксированного графа остаётся фундаментальное ограничение. Если мы заранее прописали, что momentum связан с trend strength, RSI — с Bollinger, а volatility — с ATR, то мы снова внесли в систему жёсткую гипотезу. Да, она умнее обычной сетки, но всё ещё статична. Рынок же меняется: сегодня объём подтверждает пробой, завтра тот же объём оказывается ловушкой; сегодня RSI работает как разворотный индикатор, завтра он просто показывает силу тренда.

По этой причине третий шаг архитектуры — сделать граф обучаемым, а не фиксированным. Теперь связи между признаками усиливаются, если совместно приводят к прибыльному прогнозу, и ослабляются, если их совместное влияние ведёт к ошибке. В результате система получает не только 10 000 адаптивных клеток, но и живую структуру признакового пространства.


Главная идея: не веса нейросети, а связи между причинами

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

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

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

Для этого вводится матрица связей:

double m_fgraph[CNN_FEATURES * CNN_FEATURES]; // 50 × 50 обучаемых связей feature → feature

Если m_fgraph[0][5] растёт, значит краткосрочный momentum и краткосрочная сила тренда часто совместно давали правильный прогноз. Если m_fgraph[16][22] падает, значит связка RSI14 + Bollinger20 в текущем режиме перестала работать как надёжный разворотный паттерн.


Инициализация графа: мягкая структура вместо случайного хаоса

Обучаемый граф не должен стартовать полностью случайным. Если все связи равны нулю, модель потратит слишком много времени на первичную самоорганизацию. Если связи слишком сильные — она станет догматичной. Поэтому используется мягкая инициализация: локальные связи признаков получают небольшой положительный вес, дальние small-world-связи — ещё меньший.

//+------------------------------------------------------------------+
//| Инициализация обучаемого графа признаков                         |
//+------------------------------------------------------------------+
void InitLearnableFeatureGraph()
  {
   for(int i = 0; i < CNN_FEATURES; i++)
     {
      for(int j = 0; j < CNN_FEATURES; j++)
        {
         double w = 0.0;

         //--- локальная близость признаков: momentum рядом с momentum, RSI рядом с RSI
         if(MathAbs(i - j) == 1)
            w = 0.08;

         //--- дальняя small-world-хорда: признак получает слабую связь с удалённой группой
         int chord = (i * 17 + 11) % CNN_FEATURES;
         if(j == chord)
            w = 0.04;

         //--- самосвязь запрещена, чтобы признак не усиливал сам себя бесконечно
         if(i == j)
            w = 0.0;

         m_fgraph[i * CNN_FEATURES + j] = w;
        }
     }
  }

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


Графовый слой признаков

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

//+------------------------------------------------------------------+
//| Графовый слой признаков (один шаг message passing)               |
//+------------------------------------------------------------------+
void ApplyLearnableFeatureGraph(double &feat[])
  {
   double src[CNN_FEATURES];
   double dst[CNN_FEATURES];

   for(int i = 0; i < CNN_FEATURES; i++)
     {
      src[i] = feat[i];
      dst[i] = feat[i] * 0.72; // собственный голос признака остаётся главным
     }

   for(int i = 0; i < CNN_FEATURES; i++)
     {
      double acc  = 0.0;
      double norm = 0.0;

      for(int j = 0; j < CNN_FEATURES; j++)
        {
         if(i == j)
            continue;

         double w = m_fgraph[i * CNN_FEATURES + j];
         if(MathAbs(w) < 0.0001)
            continue;

         acc  += src[j] * w;
         norm += MathAbs(w);
        }

      if(norm > 0.0001)
         dst[i] += 0.28 * acc / norm;
     }

   for(int i = 0; i < CNN_FEATURES; i++)
      feat[i] = MathTanh(dst[i]);
  }

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

Элемент Роль Зачем нужен
src[i] Исходное значение признака Сохраняет чистый рыночный сигнал до графовой обработки
m_fgraph[i][j] Сила связи признаков Показывает, насколько признак j влияет на признак i
acc / norm Нормированное сообщение соседей Защищает от взрыва масштаба при росте числа связей
MathTanh() Ограничение диапазона Возвращает значения в диапазон [-1,+1]


Как граф обучается после прогноза

Главное правило простое: если два признака были активны в одном направлении и итоговый прогноз оказался правильным — связь между ними усиливается. Если они совместно поддерживали ошибочный прогноз — связь ослабляется.

Для этого перед прогнозом сохраняется снимок признаков:

double m_last_feat[CNN_FEATURES];
double m_last_vote;
bool   m_has_last_graph_sample;

После появления фактического движения цены вызывается обучение графа:

//+------------------------------------------------------------------+
//| Обучение графа признаков по факту движения цены                  |
//+------------------------------------------------------------------+
void TrainFeatureGraph(double actual_move)
  {
   if(!m_has_last_graph_sample)
      return;

   double pred_sign = (m_last_vote > 0.0) ? 1.0 : -1.0;
   double real_sign = (actual_move > 0.0) ? 1.0 : -1.0;
   double reward    = pred_sign * real_sign; // +1 правильно, -1 ошибка

   double lr = 0.006; // скорость обучения графа

   for(int i = 0; i < CNN_FEATURES; i++)
     {
      for(int j = 0; j < CNN_FEATURES; j++)
        {
         if(i == j)
            continue;

         double coact = m_last_feat[i] * m_last_feat[j];
         double delta = lr * reward * coact;

         m_fgraph[i * CNN_FEATURES + j] += delta;

         //--- ограничение силы связи
         if(m_fgraph[i * CNN_FEATURES + j] > 1.0)
            m_fgraph[i * CNN_FEATURES + j] = 1.0;
         if(m_fgraph[i * CNN_FEATURES + j] < -1.0)
            m_fgraph[i * CNN_FEATURES + j] = -1.0;
        }
     }
  }

Это не градиентный спуск в классическом смысле. Здесь нет обратного распространения ошибки (backpropagation), матриц из внешних библиотек и оптимизатора Adam. Это локальное правило усиления/ослабления связей, похожее на рыночную версию правила Хебба (Hebbian learning):

Признаки, которые вместе правильно предсказывают рынок, связываются сильнее. Признаки, которые вместе ошибаются, разъединяются.


Почему связь может стать отрицательной

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

Например, система может обнаружить:

  • RSI в зоне перекупленности обычно разворотный, но при сильном trend strength он становится признаком продолжения;
  • высокий объём при низкой волатильности может быть накоплением, а при высокой волатильности — паникой;
  • momentum без подтверждения диапазоном цены часто даёт ложный пробой.

Отрицательная связь — это не ошибка. Это способ сказать: этот признак в присутствии другого признака нужно читать противоположно.


Защита от переобучения графа

Если граф лишь усиливать после каждой удачи, он быстро станет агрессивным и начнёт переобучаться на последние 20–30 баров. Поэтому в систему добавлены три стабилизатора.

Стабилизатор Реализация Эффект
Ограничение веса w ∈ [-1,+1] Связь не может стать бесконечно сильной
Медленное обучение lr = 0.006 Граф меняется плавно, а не прыгает от бара к бару
Нормировка сообщений acc / norm Сильносвязанные узлы не взрывают масштаб признаков

Дополнительно можно вводить забывание старых связей:

//+------------------------------------------------------------------+
//| Забывание старых связей графа признаков                          |
//+------------------------------------------------------------------+
void DecayFeatureGraph()
  {
   for(int i = 0; i < CNN_FEATURES * CNN_FEATURES; i++)
      m_fgraph[i] *= 0.9995;
  }

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


Интеграция с клеточными автоматами

Обучаемый граф признаков не заменяет клеточный автомат. Он стоит перед ним и подаёт в CA уже контекстно-обогащённые признаки.

//+------------------------------------------------------------------+
//| Прогноз следующего движения через граф и клетки CA               |
//+------------------------------------------------------------------+
double PredictNextMove(string sym)
  {
   double feat[CNN_FEATURES];

   if(!ExtractFeatures(sym, feat))
      return 0.0;

   //--- новый слой: признаки проходят через обучаемый граф
   ApplyLearnableFeatureGraph(feat);

   //--- сохраняем снимок для будущего обучения графа
   for(int i = 0; i < CNN_FEATURES; i++)
      m_last_feat[i] = feat[i];

   //--- дальше работает обычная эволюция 10 000 клеток
   for(int t = 0; t < CNN_CA_TICKS; t++)
      StepCA(feat);

   double vote = WeightedVote();
   m_last_vote = vote;
   m_has_last_graph_sample = true;

   return vote;
  }

Теперь каждая клетка получает не исходные признаки, а признаки после графового согласования. Например, клетка, работающая с feat[0], фактически получает краткосрочный momentum уже с учётом того, как он сейчас связан с трендом, волатильностью, объёмом, диапазоном и временем суток.


Обучаемый граф внутри BPC

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

Модуль Горизонт Что обучает граф
CCellularNeuralNet10K Текущий бар / ближайшее движение Связи признаков, полезные для немедленного направления
CBinaryPredictor10K 10 баров Связи признаков, полезные для среднесрочного направления

Это даёт интересный эффект: один и тот же признак может быть полезен в CA и вреден в BPC. Например, резкий импульс может быть хорошим сигналом на ближайший бар, но плохим сигналом на 10 баров, если после импульса часто происходит откат. Раздельные графы позволяют системе не смешивать эти режимы.


Диагностика: как понять, что граф действительно учится

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

Метрика Норма Опасный сигнал Интерпретация
Graph Density 15–45% >70% Слишком много сильных связей — граф переобучается
Average |W| 0.05–0.35 >0.6 Связи стали слишком жёсткими
Positive / Negative edges 40/60–60/40 90/10 Граф потерял контраст и начал всё усиливать
Top edges stability Смена лидеров при смене режима Один и тот же топ месяцами Граф перестал адаптироваться

Пример функции вывода сильнейших связей:

//+------------------------------------------------------------------+
//| Диагностика: вывод сильнейших связей графа признаков             |
//+------------------------------------------------------------------+
void PrintTopFeatureEdges(int topN = 10)
  {
   for(int k = 0; k < topN; k++)
     {
      double best = 0.0;
      int bi = -1;
      int bj = -1;

      for(int i = 0; i < CNN_FEATURES; i++)
        {
         for(int j = 0; j < CNN_FEATURES; j++)
           {
            if(i == j)
               continue;

            double w = MathAbs(m_fgraph[i * CNN_FEATURES + j]);
            if(w > best)
              {
               best = w;
               bi = i;
               bj = j;
              }
           }
        }

      if(bi >= 0)
         Print("GraphEdge #", k + 1, " F", bi, " -> F", bj,
               " W=", DoubleToString(m_fgraph[bi * CNN_FEATURES + bj], 4));
     }
  }

На трендовом рынке в топе обычно появляются связи между momentum, trend strength, volatility и price level. На возвратном рынке усиливаются RSI, Bollinger, mean reversion и Hurst. Это можно использовать как дополнительную диагностику текущего режима.


Пример интерпретации обученного графа

Допустим, после 500 прогнозов система выводит такие сильные связи:

Связь Вес Интерпретация
feat[0] → feat[5] +0.72 Краткосрочный momentum подтверждается краткосрочной силой тренда
feat[16] → feat[22] −0.48 RSI14 и Bollinger20 в этом режиме конфликтуют; разворотный сигнал ненадёжен
feat[24] → feat[2] +0.51 Объём подтверждает среднесрочный импульс
feat[36] → feat[7] +0.63 Hurst подтверждает трендовый режим

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


Почему это сильнее обычной feature engineering

В классическом подходе разработчик вручную создаёт кросс-признаки:

feat[45] = momentum * volume;
feat[46] = trend / volatility;
feat[47] = rsi * bollinger;

Проблема в том, что такие признаки навсегда фиксируют предположение автора. Обучаемый граф делает то же самое, но динамически и в масштабе всех 50 признаков. Вместо трёх ручных кросс-признаков система получает до 2450 направленных связей.

Подход Количество связей Адаптация Недостаток
Ручные кросс-признаки 3–10 Нет Зависит от гипотез разработчика
Фиксированный граф 50–150 Нет Топология не меняется
Обучаемый граф До 2450 Да, онлайн Нужен контроль переобучения

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


Практические пороги для запуска

После внедрения обучаемого графа первые 200–300 прогнозов следует рассматривать как фазу самоорганизации. В этот период не стоит делать выводы по каждой отдельной сделке. Важнее смотреть на динамику метрик.

  • 0–100 прогнозов: граф только формирует первичные связи; возможна высокая нестабильность.
  • 100–300 прогнозов: появляются устойчивые топ-связи; Network Health должен начать стабилизироваться.
  • 300+ прогнозов: можно оценивать Hit Rate, BPC Precision и плотность графа.

Практическая рекомендация: если после 300 прогнозов Graph Density превышает 70% и Average |W| выше 0.6, а Hit Rate не растёт, граф переобучается. Нужно уменьшить learning rate или усилить decay.

//--- более консервативный режим обучения графа
GRAPH_LR    = 0.003;
GRAPH_DECAY = 0.9990;

Если граф слишком слабый и почти не меняется, можно поднять скорость обучения:

//--- более агрессивный режим для быстрой адаптации
GRAPH_LR    = 0.009;
GRAPH_DECAY = 0.9997;


Что получилось в архитектуре третьей версии

Теперь система состоит из трёх адаптивных уровней:

Уровень Что адаптируется Как обновляется
Обучаемый граф признаков Связи feature → feature Усиливаются после правильных прогнозов, ослабляются после ошибок
Граф клеточных автоматов Состояния 10 000 клеток и их стратегии R1–R12, PnL, соседский консенсус, смена стратегии
Геномы советника Риск, шаг сетки, множитель лота, уровни По результатам торговых циклов

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


Скелет полной логики

//+------------------------------------------------------------------+
//| Обработка нового бара: проверка, обучение, торговля              |
//+------------------------------------------------------------------+
void HandleNewBar()
  {
   //--- 1. Проверяем прошлый прогноз и получаем фактическое движение
   double actual_move = VerifyPrediction();

   //--- 2. Обучаем клетки по факту движения
   g_ca.UpdateStats(TimeHour(TimeCurrent()), actual_move);

   //--- 3. Обучаем граф признаков
   g_ca.TrainFeatureGraph(actual_move);

   //--- 4. BPC проверяет прогноз 10 баров назад и обучает свой граф
   g_bpc.OnNewBar(_Symbol);

   //--- 5. Извлекаем новые признаки и пропускаем их через обучаемый граф
   double vote = g_ca.PredictNextMove(_Symbol);

   //--- 6. Комбинируем CA + BPC
   double effConf = CombineSignals(vote, g_bpc);

   //--- 7. Торговое решение
   if(effConf >= g_cur.min_conf && g_direction == 0)
     {
      int dir = (vote > 0) ? 1 : -1;
      OpenPosition(dir, 0);
     }
  }

Рассмотрим бэктест советника с проверкой на новый бар на ценах открытия, за период 2026.05.01 - 2026.06.01, на EURUSD H1 :


Итоговая статистика:

В первой части была решена проблема фиксированных весов: 10 000 клеток стали живыми агентами, которые обновляются на каждом баре. Во второй части была решена проблема плоской топологии: признаки и клетки были переведены в графовую структуру. В третьей части решается ещё более глубокая проблема — фиксированная структура связей.

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

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

В результате Cellular10K становится трёхуровневой адаптивной архитектурой:

  • обучаемый граф признаков понимает, какие рыночные факторы сейчас связаны;
  • граф клеточных автоматов превращает эти связи в коллективное решение 10 000 агентов;
  • торговый геном переводит решение в параметры сетки, риска и управления позицией.

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

Название файла Описание файла
CellularNeuralNet10K.mqh
Основной модуль CA 10K с обучаемым графом признаков и графом клеточных автоматов
CellularBinaryPredictor10K.mqh
Бинарный предиктор с отдельным обучаемым графом признаков на горизонте 10 баров
CA_Grid_10K.mq5
Пример советника, объединяющего CA, BPC и адаптивные торговые геномы
Прикрепленные файлы |
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Рыночные секреты Ларри Уильямса (Часть 11): Индикатор для обнаружения разворотов Smash Day Рыночные секреты Ларри Уильямса (Часть 11): Индикатор для обнаружения разворотов Smash Day
Мы преобразуем правила разворота Smash Day Ларри Уильямса в пользовательский индикатор MQL5, который отмечает подтвержденные сетапы стрелками. Шаг за шагом в статье показаны привязка буферов, свойства графических построений, заполнение буферов на исторических данных и обновления в реальном времени внутри OnCalculate. Настраиваемое количество предыдущих баров для проверки и наглядное отображение на графике помогают быстро выявлять развороты, соответствующие условиям модели, при этом итоговые торговые решения остаются дискреционными и зависят от контекста.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Моделирование рынка: Position View (V) Моделирование рынка: Position View (V)
Несмотря на изложенное в предыдущей статье, это кажется чем-то простым. Там перед нами стоят различные проблемы и множество задач, которые нужно решить и выполнить. Вы, уважаемый читатель, можете представить, что всё легко и просто. По наивности вы просто принимаете то, что вам предлагают. И это ошибка, которой вам, уважаемый читатель, следует постараться избежать. Хуже простого принятия — непонимание и попытка использовать что-то без реального осознания того, что именно используется. Среди новичков часто встречается этап копирования и вставки кода. Если вы не хотите навсегда остаться на этом этапе, стоит научиться использовать определенные инструменты. Одним из наиболее часто используемых программистами инструментов является документация. Второй инструмент состоит из тестов и лог-файлов. Здесь мы увидим, как это сделать.