English 中文 Español Deutsch 日本語 Português
preview
Теория категорий в MQL5 (Часть 17): Функторы и моноиды

Теория категорий в MQL5 (Часть 17): Функторы и моноиды

MetaTrader 5Торговые системы | 18 декабря 2023, 15:47
722 0
Stephen Njuki
Stephen Njuki

Введение

Мы продолжаем рассматривать теорию категорий применительно к функторам. До сих пор мы видели применение теории категорий при реализации пользовательских экземпляров трейлинг-класса Expert и класса Expert Signal. В этой статье мы рассмотрим приложения с использованием класса Expert Money. Все эти классы поставляются с Meta Editor IDE и используются с мастером MQL5 для сборки советников, при этом минимизируя написание кода.

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

Функторы — это мост междукатегориями, фиксирующий различия не только между объектами в каждой категории, но и различия в их морфизмах. Мы смогли увидеть, как эту собранную информацию можно использовать для прогнозирования изменений волатильности и рыночных тенденций, рассматривая категории, представленные в форме графов и линейных порядков. Графы полноты (recall graphs) и линейные порядки не являются категориями сами по себе, но рассматривались как таковые, потому что присутствовали ключевые аксиомы категорий.

Функторы были реализованы с использованием простых линейных уравнений в статьях 14 и 15, где для отображения требовался лишь коэффициент наклона, а также сдвиг по оси Y для определения объектов и морфизмов категории кодомена. Начиная со статьи 16, мы начали рассматривать функторы как простую нейронную сеть, называемую многослойным перцептроном. Эта сеть, созданная на основе работ Уоррена Мак-Каллока и Уолтера Питтса, аппроксимирует любую непрерывную функцию от -1 до +1, а также в течение некоторого времени воспроизводит любое исключающее ИЛИ при условии его многослойности.

В этой статье мы подведем итог нашей работе с функторами и рассмотрим, как в сочетании с моноидами и предпорядком цен на торговые активы, мы можем сформулировать систему для установки размеров позиций при торговле ценной бумагой, которой будет BTCUSD (биткоин). Когда мы в последний раз рассматривали моноиды, их основное применение в трейдинге сводилось к классификации торговых шагов таким образом, чтобы решения могли приниматься на основе операций каждого моноида. Напомним, что моноид — это множество, бинарная операция и единичный элемент. Поэтому мы создали моноиды для каждого из условных шагов, с которыми сталкивается трейдер при принятии решения об открытии позиции.


Функторы и моноиды в трейдинге

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

Таким образом, связь между категориями, обеспечиваемая функторами, оказалась полезной при составлении прогнозов, поскольку категория кодомена представляет собой временной ряд, прогноз которого мы здесь и рассматриваем. В нашей последней статье это было информирование о том, открывать ли нам длинную или короткую позицию по S&P 500.

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


Функторы как многослойные перцептроны (MLP)

Роль многослойного перцептрона в трейдинге невозможно переоценить. Огромный объем статей о нейронных сетях служит четким свидетельством того, что он становится все более незаменимым. Большинство трейдеров используют его для прогнозирования следующих ценовых трендов, что вполне оправданно. Однако, на мой взгляд, они часто упускают из виду выбор (и, возможно, регуляризацию) входных данных для сети. Допустим, вы хотите спрогнозировать временной ряд, но на каком потоке данных вы будете основывать свой прогноз и почему? Это может показать не таким важным, но, возможно, это одна из причин, почему многие сети, обученные на больших объемах данных, не работают так, как при тестовых запусках.

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

Итак, вот как мы определим размер позиции на основе прогнозируемого разрыва стоп-лосса:

         //output from MLP forecast
         double _stoploss_gap=_y_output[0];
         
         //printf(__FUNCSIG__+" ct call: "+DoubleToString(_stoploss_gap));
      
         sl=m_symbol.Ask()+fabs(_stoploss_gap);
         
         //--- select lot size
         double _ct_1_lot_loss=(_stoploss_gap/m_symbol.TickSize())*m_symbol.TickValue();
         double lot=((m_percent/100.0)*m_account.FreeMargin())/_ct_1_lot_loss;
         
         //--- calculate margin requirements for 1 lot
         if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_SELL,lot,m_symbol.Bid())<0.0)
         {
            printf(__FUNCSIG__" insufficient margin for sl lot! ");
            lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_SELL,m_symbol.Bid(),m_percent);
         }
         
         //--- return trading volume
         return(Optimize(lot));

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


Моноидные операции для определения размера позиций

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

//+------------------------------------------------------------------+
//| Enumeration for Monoid Operations                                |
//+------------------------------------------------------------------+
enum EOperations
  {
      OP_FURTHEST=5,
      OP_CLOSEST=4,
      OP_MOST=3,
      OP_LEAST=2,
      OP_MULTIPLY=1,
      OP_ADD=0
  };


//+------------------------------------------------------------------+
//|   Operate function for executing monoid binary operations        |
//+------------------------------------------------------------------+
void CMoneyCT::Operate(CMonoid<double> &M,EOperations &O,int IdenityIndex,int &OutputIndex)
   {
      OutputIndex=-1;
      //
      double _values[];
      ArrayResize(_values,M.Cardinality());ArrayInitialize(_values,0.0);
      //
      for(int i=0;i<M.Cardinality();i++)
      {
         m_element.Let();
         if(M.Get(i,m_element))
         {
            if(!m_element.Get(0,_values[i]))
            {
               printf(__FUNCSIG__+" Failed to get double for 1 at: "+IntegerToString(i+1));
            }
         }
         else{ printf(__FUNCSIG__+" Failed to get element for 1 at: "+IntegerToString(i+1)); }
      }
      
      //
      
      if(O==OP_LEAST)
      {
         ...
      }
      else if(O==OP_MOST)
      {
         ...
      }
      else if(O==OP_CLOSEST)
      {
         ...
      }
      else if(O==OP_FURTHEST)
      {
         ...
      }
   }

Теперь, поскольку нас сейчас интересует размер позиции, а не волатильность прогнозирования рыночных трендов, финальный моноид "действия" и его решение можно заменить функтором. Таким образом, первые четыре моноида будут выполнены, чтобы помочь определить значение индикатора и определить размер позиции. Мы будем придерживаться индикаторов RSI и регуляризированных полос Боллинджера, чтобы получить значение, подобное RSI, в диапазоне от 0 до 100. Таким образом, хотя это значение индикатора является результатом трех предыдущих результатов моноида, оно будет объединено с ними для создания набора из четырех значений, которые формируют входные данные нашего многослойного перцептрона. Таким образом входные данные о таймфрейме и применяемой цене необходимо будет привести в числовой формат, который может быть обработан MLP, как мы это делали в предыдущей статье.

Итак, повторюсь, моноид, который представляет собой просто множество, бинарную операцию и единичный элемент, просто позволяет выбрать элемент из этого множество, как определено бинарной операцией. Выбрав таймфрейм, период ретроспективного анализа и применяемую цену, мы получаем входные данные для нашего индикатора, нормализованное значение которого (0–100) будет служить четвертым входным значением в MLP.

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

         ENUM_TIMEFRAMES _timeframe_0=SetTimeframe(m_timeframe,0);
         int _lookback_0=SetLookback(m_lookback,_timeframe_0,0);
         ENUM_APPLIED_PRICE _appliedprice_0=SetAppliedprice(m_appliedprice,_timeframe_0,_lookback_0,0);
         double _indicator_0=SetIndicator(_timeframe_0,_lookback_0,_appliedprice_0,0);


Интеграция функторов и моноидов для комплексного определения размера позиции

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

Итак, здесь представлена синергия моноидов и MLP. Любой из них самостоятельно может создавать прогнозы по расстоянию стопа. В идеале для этого потребуется контроль для проверки, то есть нам понадобятся отдельные советники, которые реализуют только моноиды и только MLP и сравнивают все три набора результатов. К сожалению, это невозможно для данной статьи, однако исходный код, демонстрирующий обе идеи, прилагается, поэтому читателю может изучить и проверить (или опровергнуть?) эту идею синергии.

Итак, код, объединяющий эти два компонента, выглядит так:

      m_open.Refresh(-1);
      m_high.Refresh(-1);
      m_close.Refresh(-1);
      
      CMLPTrain _train;
      
      int _info=0;
      CMLPReport _report;
      CMatrixDouble _xy;_xy.Resize(1,__INPUTS+__OUTPUTS);
      
      _xy[0].Set(0,RegularizeTimeframe(_timeframe_1));
      _xy[0].Set(1,_lookback_1);
      _xy[0].Set(2,RegularizeAppliedprice(_appliedprice_1));
      _xy[0].Set(3,_indicator_1);
      //
      int _x=StartIndex()+1;
      
      double _sl_1=m_high.GetData(_x)-m_low.GetData(_x);
      
      if(m_open.GetData(_x)>m_close.GetData(_x))
      {
         _sl_1=m_high.GetData(_x)-m_open.GetData(_x);
      }
      
      double _stops=(2.0*(m_symbol.Ask()-m_symbol.Bid()))+((m_symbol.StopsLevel()+m_symbol.FreezeLevel())*m_symbol.Point());
      
      _xy[0].Set(__INPUTS,fmax(_stops,_sl_1));
      
      _train.MLPTrainLM(m_mlp,_xy,1,m_decay,m_restarts,_info,_report);


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

Чтобы проанализировать наш метод определения размера составной позиции, мы будем использовать биткойн (BTCUSD) в качестве тестовой ценной бумаги. Тестирование с оптимизацией будет проведено на периоде с 1 января 2020 года по 1 августа 2023 года на дневном таймфрейме. Помимо оптимизации для достижения идеального количества весов в скрытом слое (поскольку мы используем MLP только с одним скрытым слоем), мы также постараемся точно настроить четыре моноида, которые мы используем для получения размера позиции. Это означает, что мы постараемся установить идеальный элемент идентификации и тип операции для каждого из четырех моноидов, используемых при определении размера нашей позиции. Наш анализ сосредоточен исключительно на размере позиции, а это означает, что используемый сигнал советника должен быть одним из тех, которые предусмотрены в библиотеке MQL5. Для этого мы будем использовать класс экспертных сигналов RSI. Никакие трейлинг-стопы не будут реализованы, и, как всегда, как и во всех предыдущих тестах, значения тейк-профита или стоп-лосса использоваться не будут, поэтому параметры take level и stop level будут равны нулю. Однако наш советник будет открыт для использования отложенных ордеров, поэтому параметр price level также будет оптимизирован, как и раньше.

Выполняем тесты с функтором объектов и функтором морфизмов. Результаты показаны ниже:

r_1


r_2

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

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

r_ctrl

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


Ограничения и нюансы

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

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

В-третьих, чрезмерная подгонка и обобщение — это еще одна проблема, которая особенно характерна для MLP. Я намекнул на это ранее, отметив важность набора данных входного слоя, и, по моему мнению, обходной путь заключается в использовании значимых регуляризованных данных. "Значимых" в том смысле, что существуют заслуживающие доверия фундаментальные аспекты набора данных входного слоя, которые, как можно ожидать, повлияют на интересующий нас прогноз.

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

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

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


Заключение и перспективы

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

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

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


Ссылки

В работе приведены необходимые гиперссылки на статьи из Википедии.


Приложение: Фрагменты кода MQL5

Положите файл MoneyCT_17_.mqh в MQL5\include\Expert\Money\, а ct_9.mqh - в папку Include.

Возможно, вам будут полезны рекомендации, приведенные здесь о том, как собрать советник с помощью Мастера. Как указано в статье, я использовал осциллятор RSI в качестве сигнала входа. Трейлинг-стоп не использовался. Как всегда, цель статьи – не представить вам Грааль, а скорее, предложить идею, которую вы можете адаптировать к своей собственной стратегии. Файлы *.*mq5 собраны в Мастере. Вы можете скомпилировать их или создать самостоятельно. Файл ct_17_control был собран с фиксированной маржей.


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

Прикрепленные файлы |
ct_17.mq5 (9.25 KB)
ct_9.mqh (65.06 KB)
MoneyCT_17_.mqh (36.29 KB)
Тесты на перестановку Монте-Карло в MetaTrader 5 Тесты на перестановку Монте-Карло в MetaTrader 5
В статье рассматриваются тесты на перестановку на основе перетасованных тиковых данных на любом советнике исключительно силами MetaTrader 5.
Популяционные алгоритмы оптимизации: Изменяем форму и смещаем распределения вероятностей и тестируем на "Умном головастике" (Smart Cephalopod, SC) Популяционные алгоритмы оптимизации: Изменяем форму и смещаем распределения вероятностей и тестируем на "Умном головастике" (Smart Cephalopod, SC)
В данной статье исследуется влияние изменения формы распределений вероятностей на производительность алгоритмов оптимизации. Мы проводим эксперименты на тестовом алгоритме 'Умный головастик' (SC), чтобы оценить эффективность различных распределений вероятностей в контексте оптимизационных задач.
Индикатор исторических позиций на графике в виде диаграммы их прибыли/убытка Индикатор исторических позиций на графике в виде диаграммы их прибыли/убытка
В статье рассмотрим вариант получения информации о закрытых позициях по истории их сделок. Создадим простой индикатор, отображающий в виде диаграммы приблизительный профит/убыток позиций на каждом баре.
Нейросети — это просто (Часть 68): Офлайн оптимизация политик на основе предпочтений Нейросети — это просто (Часть 68): Офлайн оптимизация политик на основе предпочтений
С первых статей, посвященных обучению с подкреплением, мы так или иначе затрагиваем 2 проблемы: исследование окружающей среды и определение функции вознаграждения. Последние статьи были посвящены проблеме исследования в офлайн обучении. В данной статье я хочу Вас познакомить с алгоритмом, авторы которого полностью отказались от функции вознаграждения.