English 中文 Español Deutsch 日本語 Português
preview
Теория категорий в MQL5 (Часть 21): Естественные преобразования с помощью LDA

Теория категорий в MQL5 (Часть 21): Естественные преобразования с помощью LDA

MetaTrader 5Торговые системы | 1 февраля 2024, 10:49
495 0
Stephen Njuki
Stephen Njuki

Введение

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

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

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

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

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

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


Общие сведения

Как мы помним, естественные преобразования — это разница между целевыми объектами двух функторов. Использование этой разницы было подчеркнуто в этой серии статей с помощью квадрата естественности, коммутативной композиции объектов в категории кодомена функторов. Описание можно найти в статье 18. Мы также рассмотрели индукцию квадрата естественности в статье 19.

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

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

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


Описание наборов данных

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

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

Эти два набора данных, имеющих естественное преобразование, могут быть определены несколькими способами, которые уже перечислены во введении. В этой статье мы будем использовать метод линейного дискриминантного анализа (linear discriminant analysis, LDA). Я уже показывал его применение с мастером MQL5 и библиотекой Alglib в этой статье, но, думаю, будет полезным повторить наиболее важные моменты.

Более конкретное определение можно найти здесь, но в широком смысле LDA — это классификатор. Если мы посмотрим на любой типичный набор обучающих данных, он всегда имеет независимые переменные (значения, которые, как предполагается, влияют на конечный результат) и переменную(ые) классификатора, которые служат "конечным результатом". С помощью LDA у нас есть возможность классифицировать этот конечный результат до n классов, где n — натуральное число. Этот алгоритм, разработанный сэром Рональдом Фишером, выводит вектор весов, который помогает определить главный центроид каждого классификатора, а также может оценить положение неизвестного центроида (новой или неизвестной точки данных). С помощью этой информации можно просто сравнить положение неизвестного центроида с положением известного, чтобы узнать, к какому он ближе и, следовательно, какова его классификация. Для иллюстрации "вектор весов" можно рассматривать как уравнение линии, разделяющей точки, имеющие только два классификатора. Если классификаторов три, то это уравнение плоскости. Если один, то это координаты на числовой прямой. В любом сценарии вы можете провести различие между набором обучающих данных, присвоив каждой точке данных набор координат.


Естественная трансформация для прогнозирования временных рядов

Итак, как мы упоминали в предыдущих статьях 18 и 19, при рассмотрении квадратов естественности обычно все, что имеет значение, имеют объекты кодомена, и поэтому, показывая, как два разных набора данных ценовых точек и скользящих средних могут иметь естественное преобразование для этой статьи, мы ничего не сказали о категории источника функтора или объекте(ах). Они не критичны. Однако если у нас есть несколько объектов в исходной категории, можно ожидать, что будет несколько пропорциональных экземпляров как простого, так и составного набора данных. Я уже указывал на это, когда мы рассматривали индукцию квадрата естественности в статье 19.

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

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


Применение естественного преобразования для прогнозирования

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

//+------------------------------------------------------------------+
//| Refresh function to update objects.                              |
//+------------------------------------------------------------------+
void CSignalCT::Refresh(int DataPoints=1)
   {
      m_time.Refresh(-1);
      m_close.Refresh(-1);
      
      for(int v=0;v<DataPoints;v++)
      {
         m_e_s.Let(); m_e_s.Cardinality(2);
         m_e_c.Let(); m_e_c.Cardinality(m_independent+1);
         
         m_e_s.Set(0,TimeToString(m_time.GetData(v)));
         m_e_c.Set(0,TimeToString(m_time.GetData(v)));
      
         double _s_unit=0.0;
         //set independent variables..
         for(int vv=0;vv<m_independent;vv++)
         {
            double _c_unit=m_close.GetData(StartIndex()+v+vv+m_independent);
            
            m_e_c.Set(vv+1,DoubleToString(_c_unit));
         }
         
         m_o_c.Set(0,m_e_c);
         
         //get dependent variable, the MA..
         for(int vv=v;vv<v+m_independent;vv++)
         {
            _s_unit+=m_close.GetData(StartIndex()+vv);
         }
         
         _s_unit/=m_independent;
         
         m_e_s.Set(1,DoubleToString(_s_unit));
         
         m_o_s.Set(0,m_e_s);
      }
   }


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

//+------------------------------------------------------------------+
//| Get Direction function from implied naturality square.           |
//+------------------------------------------------------------------+
double CSignalCT::GetDirection()
   {
      double _da=0.0;
      
      int _info=0;
      CMatrixDouble _w,_xy,_z;
      _xy.Resize(m_data,m_independent+1);
      
      double _point=0.00001;
      if(StringFind(m_symbol.Name(),"JPY")>=0){ _point=0.001; }
      
      for(int v=0;v<m_data;v++)
      {
         Refresh(v+1);
         
         ...
         
         //training classification
         _xy.Set(v,m_independent,(fabs(_ma-_lag_ma)<=m_regularizer*_point?1:(_ma-_lag_ma>0.0?2:0)));
      }
      
      m_lda.FisherLDAN(_xy,m_data,m_independent,__CLASSES,_info,_w);
      
      if(_info>0)
      {
         double _centroids[__CLASSES],_unknown_centroid=0.0; ArrayInitialize(_centroids,0.0);
         
         _z.Resize(1,m_independent+1);
         
         m_o_c.Get(0,m_e_c);
         
         for(int vv=0;vv<m_independent;vv++)
         {
            string _c="";
            m_e_c.Get(vv+1,_c);
            
            double _c_value=StringToDouble(_c);
            _z.Set(0,vv,_c_value);
         }
         
         for(int v=0;v<m_data;v++)
         {
            for(int vv=0;vv<m_independent;vv++)
            {
               _centroids[int(_xy[v][m_independent])]+= (_w[0][vv]*_xy[v][vv]);
            }
         }
         
         // best vector is the first 
         for(int vv=0;vv<m_independent;vv++){ _unknown_centroid+= (_w[0][vv]*_z[0][vv]); }
         
         
... 
      }
      else
      {
         
... 
      }
      
      return(_da);
   }

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

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

Затем на каждом новом баре мы обновляем значения этих элементов и, следовательно, их объектов с помощью функции обновления. Это включает в себя назначение независимых переменных, то есть просто назначение ряда цен, количество которых равно длине периода входного скользящего среднего. Итак, мы передаем эти цены составному элементу и объектам. Мы используем 3 классификатора для нашего LDA: 2 для бычьего рынка, 1 для флетового рынка и 0 для медвежьего рынка. Таким образом, каждой точке обучающих данных присваивается классификация на основе разницы между текущим скользящим средним (на основе индекса в обучающем наборе) и запаздывающим скользящим средним. Оба средних значения берутся на одинаковой длине, которая уже упоминалась в качестве входных данных, и задержка также равна этой длине.

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

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

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

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

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


Практическое применение

В качестве примера проведем тестирование пары GBPUSD с начала этого года по первое июня. Отчет представлен ниже:

r1


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

r2


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


Заключение

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

Что касается темы статьи, естественные преобразования довольно хороши для обработки различных структур данных не только в случаях, когда набор данных развивается из-за потребностей бизнеса или анализа, но, возможно, в случаях, когда необходимо сравнение и размеры (количество столбцов) обоих наборов данных не одинаковы. Эта функция, безусловно, пригодится в ряде дисциплин.


Ссылки

Ссылки, как всегда, в основном на статьи из Википедии.

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


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

Прикрепленные файлы |
ct_21.mqh (31.39 KB)
SignalCT_21_r2.mqh (11.52 KB)
Разработка MQTT-клиента для MetaTrader 5: методология TDD (Часть 3) Разработка MQTT-клиента для MetaTrader 5: методология TDD (Часть 3)
Статья является третьей частью серии, описывающей этапы разработки нативного MQL5-клиента для протокола MQTT. В этой части мы подробно описываем применение принципа разработки через тестирование для реализации обмена пакетами CONNECT/CONNACK. В конце этого шага наш клиент ДОЛЖЕН уметь вести себя соответствующим образом при работе с любыми возможными результатами сервера при попытке подключения.
Трейлинг-стоп в трейдинге Трейлинг-стоп в трейдинге
В этой статье мы рассмотрим использование трейлинг-стопа в торговле — насколько он полезен и эффективен, и как его можно использовать. Эффективность трейлинг-стопа во многом зависит от волатильности цены и подбора уровня стоп-лосса. Для установки стоп-лосса могут использоваться самые разные подходы.
Популяционные алгоритмы оптимизации: Эволюция социальных групп (Evolution of Social Groups, ESG) Популяционные алгоритмы оптимизации: Эволюция социальных групп (Evolution of Social Groups, ESG)
В статье рассмотрим принцип построения многопопуляционных алгоритмов и в качестве примера такого вида алгоритмов разберём Эволюцию социальных групп (ESG), новый авторский алгоритм. Мы проанализируем основные концепции, механизмы взаимодействия популяций и преимущества этого алгоритма, а также рассмотрим его производительность в задачах оптимизации.
Причинно-следственный вывод в задачах классификации временных рядов Причинно-следственный вывод в задачах классификации временных рядов
В этой статье мы рассмотрим теорию причинно-следственного вывода с применением машинного обучения, а также реализацию авторского подхода на языке Python. Причинно-следственный вывод и причинно-следственное мышление берут свои корни в философии и психологии, это важная часть нашего способа мыслить эту реальность.