Практическое использование нейросетей Кохонена в алгоритмическом трейдинге (Часть II). Оптимизация и прогнозирование

28 декабря 2018, 08:41
Stanislav Korotky
21
2 088

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


Поиск оптимальных параметров советника

Общие принципы

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

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

Некоторые трейдеры практикуют собственный синтетический критерий, включающий в расчет несколько стандартных показателей — с таким походом действительно меньше шансов получить равные строчки в отчете, но он фактически переводит вопрос в плоскость мета-оптимизации того самого синтетического критерия (как правильно выбрать его формулу?). А это целая отдельная тема. Поэтому вернемся к анализу результатов стандартной оптимизации.

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

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

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

Количество кластеров логически связано с размером карты и прикладной задачей. В нашем случае первый фактор работает только в одну сторону, поскольку мы ранее решили устанавливать размер по формуле (7). Соответственно, зная этот размер, мы получаем ограничение сверху на количество кластеров — вряд ли их может быть больше, чем размер одной стороны. С другой стороны, исходя из прикладной задачи, нас, пожалуй, устроила бы всего пара кластеров: "хорошие" настройки и "плохие". Вот в этом диапазоне и можно проводить эксперименты. Все это относится только к алгоритмам, которые построены на явном указании количества кластеров, такие как K-Means. Наш альтернативный алгоритм не имеет такой настройки, но благодаря упорядочиванию кластеров по качеству мы можем просто исключать из рассмотрения все кластеры с номером выше заданного.

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

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

Вот некоторые пути решения проблемы:

  1. Отказаться от генетической оптимизации в пользу полной; хотя это не всегда возможно в полной мере, допустимо задействовать иерархический подход — выполнить сперва генетическую оптимизацию с крупным шагом, локализовать интересные области, а в них уже проводить полную оптимизацию (и затем анализ сетью Кохонена); также существует мнение, что излишнее расширение списка оптимизируемых параметров придает системе количество степеней свободы, из-за которых она становится, во-первых, неустойчивой, а во-вторых, оптимизация выливается в подгонку; поэтому рекомендуется выбирать постоянные значения для большей части параметров, основываясь на их физическом смысле и фундаментальном анализе (например, периоды выбирать согласно типу стратегии: внутридневная — сутки, среднесрочная — неделя, и т.д.); тогда пространство оптимизации удастся уменьшить и отказаться от генетики;
  2. Проводить генетическую оптимизацию несколько раз, используя в качестве критерия не только максимумы, но и минимумы, и нули целевой функции; например, можно провести оптимизацию трижды:
    • по фактору прибыльности (PF), как обычно;
    • по его обратной величине 1/PF;
    • по формуле (min(PF, 1/PF) / max(PF, 1/PF)), которая соберет статистику возле 1;
    после этого объединить результаты всех оптимизаций и анализировать их сетью как единое целое;
  3. Полумера, но все же стоящая исследования — строить карты Кохонена в метрике, исключающей оптимизированный показатель (а в принципе, все экономические показатели, не являющиеся параметрами эксперта); иными словами, в процессе обучения сети мера похожести между весами нейронов и входными данными должна считаться только по избранным компонентам, соответствующим параметрам эксперта; обратная метрика тоже интересна, когда меры близости считаются только по экономическим показателям, и мы, вероятно, видим топологический разброс в плоскостях параметров, что свидетельствует о нестабильности системы; в обоих случаях подстройка весов нейронов производится полностью — по всем компонентам;

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

В принципе, для случая анализа результатов оптимизации существует явное разделение входного вектора на две логически разобщенных составляющих — входные параметры эксперта и его показатели (выход). Обучая сеть Кохонена на полном векторе, мы делаем попытку выявить зависимость "входов" и "выходов" (двунаправленную безусловную связь). Когда обучение проводится лишь на части признаков, мы можем попытаться увидеть направленные связи: как "выходы" кластеризуются в зависимости от "входов" или наоборот, как "входы" кластеризуются в зависимости от "выходов". Мы посмотрим оба варианта.


Доработка SOM-Explorer

Для реализации такого режима обучения — с маскированием части признаков — потребуется доработка классов CSOM и CSOMNode.

Вычисление расстояний производится в классе CSOMNode. Исключение конкретных компонент из расчета является единообразным для всех объектов класса, поэтому сделаем соответствующие переменные статическими:

    static int dimensionMax;
    static ulong dimensionBitMask;

dimensionMax позволяет задать максимальное количество обсчитываемых весов нейрона. Например, если размерность пространства — 5, то dimensionMax равный 4 будет означать, что последний компонент вектора исключается из расчета.

dimensionBitMask позволяет исключать компоненты с произвольным расположением с помощью битовой маски: если i-й бит равен 1, то i-я компонента обрабатывается, а если бит равен 0, то — нет.

Для установки переменных добавим статический метод:

static void CSOMNode::SetFeatureMask(const int dim = 0, const ulong bitmask = 0)
{
  dimensionMax = dim;
  dimensionBitMask = bitmask;
}

Теперь изменим расчет расстояния с применением новых переменных:

double CSOMNode::CalculateDistance(const double &vector[]) const
{
  double distSqr = 0;
  if(dimensionMax <= 0 || dimensionMax > m_dimension) dimensionMax = m_dimension;
  for(int i = 0; i < dimensionMax; i++)
  {
    if(dimensionBitMask == 0 || ((dimensionBitMask & (1 << i)) != 0))
    {
      distSqr += (vector[i] - m_weights[i]) * (vector[i] - m_weights[i]);
    }
  }
  return distSqr;
}

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

void CSOM::SetFeatureMask(const int dim, const ulong bitmask)
{
  m_featureMask = 0;
  m_featureMaskSize = 0;
  if(bitmask != 0)
  {
    m_featureMask = bitmask;
    Print("Feature mask enabled:");
    for(int i = 0; i < m_dimension; i++)
    {
      if((bitmask & (1 << i)) != 0)
      {
        m_featureMaskSize++;
        Print(m_titles[i]);
      }
    }
  }
  CSOMNode::SetFeatureMask(dim == 0 ? m_dimension : dim, bitmask);
}

А в тестовом эксперте создадим строковый параметр FeatureMask, в котором пользователь сможет задать маску признаков, и будем парсить его на наличие символов '1' и '0':

    ulong mask = 0;
    if(FeatureMask != "")
    {
      int n = MathMin(StringLen(FeatureMask), 64);
      for(int i = 0; i < n; i++)
      {
        mask |= (StringGetCharacter(FeatureMask, i) == '1' ? 1 : 0) << i;
      }
    }
    KohonenMap.SetFeatureMask(0, mask);

Все это делается непосредственно перед запуском метода Train и потому влияет на расчет расстояний как в процессе обучения, так и на стадии расчета U-Matrix и кластеров. Однако в некоторых случаях нам будет интересно проводить кластеризацию по другим правилам: например, обучать сеть без маски и накладывать её только для нахождения кластеров. С этой целью введем дополнительных управляющий параметр ApplyFeatureMaskAfterTraining, равный по умолчанию false, но если он установлен в true, то вызовем SetFeatureMask после Train.

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

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

bool LoadSettings(const string filename, double &v[])
{
  int h = FileOpen(filename, FILE_READ | FILE_TXT);
  if(h == INVALID_HANDLE)
  {
    Print("FileOpen error ", filename, " : ",GetLastError());
    return false;
  }
  
  int n = KohonenMap.GetFeatureCount();
  ArrayResize(v, n);
  ArrayInitialize(v, EMPTY_VALUE);
  int count = 0;

  while(!FileIsEnding(h))
  {
    string line = FileReadString(h);
    if(StringFind(line, ";") == 0) continue;
    string name2value[];
    if(StringSplit(line, '=', name2value) != 2) continue;
    int index = KohonenMap.FindFeature(name2value[0]);
    if(index != -1)
    {
      string values[];
      if(StringSplit(name2value[1], '|', values) > 0)
      {
        v[index] = StringToDouble(values[0]);
        count++;
      }
    }
  }
  
  Print("Settings loaded: ", filename, "; features found: ", count);
  
  ulong mask = 0;
  for(int i = 0; i < n; i++)
  {
    if(v[i] != EMPTY_VALUE)
    {
      mask |= (1 << i);
    }
    else
    {
      v[i] = 0;
    }
  }
  if(mask != 0)
  {
    KohonenMap.SetFeatureMask(0, mask);
  }
 
  FileClose(h);
  return count > 0;
}

В ней мы парсим строки set-файла и находим в них названия параметров, которые соответствуют признакам обученной сети. Совпадающие настройки сохраняются в векторе, несовпадающие - пропускаются. Маска признаков заполняется единицами только для компонент, имеющихся в наличии. Функция возвращает логический признак того, что настройки были прочитаны.

Теперь, имея в распоряжении данную функцию, для загрузки параметров из файла настроек вставим следующие строки в ту ветку оператора if, где проводится проверка DataFileName != "":

      // process .set file as a special case of pattern
      if(StringFind(DataFileName, ".set") == StringLen(DataFileName) - 4)
      {
        double v[];
        if(LoadSettings(DataFileName, v))
        {
          KohonenMap.AddPattern(v, "SETTINGS");
          ArrayPrint(v);
          double y[];
          CSOMNode *node = KohonenMap.GetBestMatchingFeatures(v, y);
          Print("Matched Node Output (", node.GetX(), ",", node.GetY(),
            "); Hits:", node.GetHitsCount(), "; Error:", node.GetMSE(),
            "; Cluster N", node.GetCluster(), ":");
          ArrayPrint(y);
          KohonenMap.CalculateOutput(v, true);
          hasOneTestPattern = true;
        }
      }

Настройки будут помечаться на карте меткой SETTINGS.


Рабочий эксперт

И вот мы подбираемся все ближе и ближе к задаче выбора оптимальных параметров.

Для проверки теорий с кластеризацией результатов оптимизации нам прежде нужно создать рабочий эксперт. Я сгенерировал его с помощью Мастера MQL5 с использованием нескольких стандартных модулей и назвал WizardTest (исходный код приложен в конце статьи). Вот список входных параметров:

input string             Expert_Title                 ="WizardTest"; // Document name
ulong                    Expert_MagicNumber           =17897;        // 
bool                     Expert_EveryTick             =false;        // 
//--- inputs for main signal
input int                Signal_ThresholdOpen         =10;           // Signal threshold value to open [0...100]
input int                Signal_ThresholdClose        =10;           // Signal threshold value to close [0...100]
input double             Signal_PriceLevel            =0.0;          // Price level to execute a deal
input double             Signal_StopLevel             =50.0;         // Stop Loss level (in points)
input double             Signal_TakeLevel             =50.0;         // Take Profit level (in points)
input int                Signal_Expiration            =4;            // Expiration of pending orders (in bars)
input int                Signal_RSI_PeriodRSI         =8;            // Relative Strength Index(8,...) Period of calculation
input ENUM_APPLIED_PRICE Signal_RSI_Applied           =PRICE_CLOSE;  // Relative Strength Index(8,...) Prices series
input double             Signal_RSI_Weight            =1.0;          // Relative Strength Index(8,...) Weight [0...1.0]
input int                Signal_Envelopes_PeriodMA    =45;           // Envelopes(45,0,MODE_SMA,...) Period of averaging
input int                Signal_Envelopes_Shift       =0;            // Envelopes(45,0,MODE_SMA,...) Time shift
input ENUM_MA_METHOD     Signal_Envelopes_Method      =MODE_SMA;     // Envelopes(45,0,MODE_SMA,...) Method of averaging
input ENUM_APPLIED_PRICE Signal_Envelopes_Applied     =PRICE_CLOSE;  // Envelopes(45,0,MODE_SMA,...) Prices series
input double             Signal_Envelopes_Deviation   =0.15;         // Envelopes(45,0,MODE_SMA,...) Deviation
input double             Signal_Envelopes_Weight      =1.0;          // Envelopes(45,0,MODE_SMA,...) Weight [0...1.0]
input double             Signal_AO_Weight             =1.0;          // Awesome Oscillator Weight [0...1.0]
//--- inputs for trailing
input double             Trailing_ParabolicSAR_Step   =0.02;         // Speed increment
input double             Trailing_ParabolicSAR_Maximum=0.2;          // Maximum rate
//--- inputs for money
input double             Money_FixRisk_Percent=5.0;          // Risk percentage

Для наших исследований будем оптимизировать не все, а лишь несколько параметров.

Signal_ThresholdOpen
Signal_ThresholdClose
Signal_RSI_PeriodRSI
Signal_Envelopes_PeriodMA
Signal_Envelopes_Deviation
Trailing_ParabolicSAR_Step
Trailing_ParabolicSAR_Maximum

Я выполнил генетическую оптимизацию эксперта с января по июнь 2018 года (20180101-20180701) на EURUSD D1, M1 OHLC, по прибыли. Set-файл с настройками параметров для оптимизации приложен в конце статьи (WizardTest-1.set). Результаты оптимизации были сохранены в файл Wizard2018plus.csv (также приложен ниже), из которого исключены сильно выбивающиеся записи (outliers), в частности, с количеством сделок меньше 5 и заоблачными не отвечающими реальности коэффициентами Шарпа. Кроме того, поскольку генетическая оптимизация по определению дала смещение в прибыльные проходы, я решил полностью исключить убыточные и оставил только записи, в которых прибыль составила не менее 10000.

Кроме того, я убрал пару колонок показателей, поскольку они фактически являются зависимыми от других (баланс, Expected Payoff, и есть еще кандидаты, такие как ПФ (Profit Factor), ФВ (Фактор Восстановления) и коэффициент Шарпа — с высокой корреляцией между собой, плюс ФВ сильно обратно коррелирован с просадкой, но я их оставил, чтобы наглядно показать эту зависимость на картах).

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

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

Чтобы иметь в дальнейшем ориентир для оценки своего выбора, посмотрим как выглядит форвард-тест для набора параметров из самой первой строки результатов оптимизации (см. WizardTest-1.set). Даты теста 01.07.2018-01.12.2018.

Отчет тестера для первого (по списку) варианта настроек

Отчет тестера для первого (по списку) варианта настроек

Запомним эти, в общем-то, не очень хорошие показатели и займемся сетью.


Нейросетевой анализ

Количество записей в csv-файле — около 2000, поэтому размер сети Кохонена посчитанный по формуле (7) составит 15.

Запустим CSOM-Explorer, введем имя файла с данными (Wizard2018plus.csv) в DataFileName, 15 — в CellsX и CellsY, EpochNumber оставим равным 100. Для обзора сразу всех плоскостей выберем мелкие изображения — ImageW и ImageH по 210, MaxPictures — 6. В результате работы эксперта получим примерно следующее:

Обучение сети Кохонена на результатах оптимизации советника

Обучение сети Кохонена на результатах оптимизации советника

Верхний ряд полностью состоит из карт экономических показателей, второй ряд и первая карта третьего ряда - рабочие параметры эксперта, и наконец, последние 5 карт — специальные карты, построение которых было реализовано в первой части.

Посмотрим, как карты выглядят вблизи:

Прибыль, ПФ, ФВ и первая тройка параметров рабочего эксперта

Прибыль, ПФ, ФВ и первая тройка параметров рабочего эксперта

Коэффициент Шарпа, просадка, количество трейдов и вторая тройка параметров эксперта

Коэффициент Шарпа, просадка, количество трейдов и вторая тройка параметров эксперта

Последний параметр эксперта, а также счетчик попаданий, U-Matrix, ошибки квантизации, кластеры и выход сети

Последний параметр эксперта, а также счетчик попаданий, U-Matrix, ошибки квантизации, кластеры и выход сети

Проведем экспертный анализ полученных карт, так сказать, вручную.

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

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

Signal_ThresholdOpen = 40
Signal_ThresholdClose = 85
Signal_RSI_PeriodRSI = 73
Signal_Envelopes_PeriodMA = 20
Signal_Envelopes_Deviation = 0.5

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

Trailing_ParabolicSAR_Step = 0.15
Trailing_ParabolicSAR_Maximum = 1.59

Запустим рабочий эксперт с этими параметрами (см. WizardTest-plus-allfeatures-manual.set) на том же форвард-периоде до 1 декабря 2018. Получим следующий результат.

Отчет тестера для настроек, выбранных с помощью визуального анализа карт Кохонена

Отчет тестера для настроек, выбранных с помощью визуального анализа карт Кохонена

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

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

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

Результаты всех форвард-тестов

Результаты всех форвард-тестов

Статистика распределения прибыли в форвард-тестах

Статистика распределения прибыли в форвард-тестах

Статистика следующая: среднее — 1007, стандартное отклонение — 1444, медиана — 1085, минимум и максимум — соответственно -3813.78 и 4202.82. Таким образом, с помощью экспертной оценки мы получили прибыль даже больше, чем среднее плюс стандартное отклонение.

Наша задача научиться автоматически делать такой же или близкий по качеству выбор. Для этого используем кластеризацию. Поскольку нумерация кластеров идет в порядке их предпочтительного использования, будем смотреть кластер 0 (хотя кластеры 1-5 по идее можно использовать для торговли портфелем настроек).

Цвета кластеров на карте всегда соответствуют их порядковому номеру по такому списку:

{clrRed, clrGreen, clrBlue, clrYellow, clrMagenta, clrCyan, clrGray, clrOrange, clrSpringGreen, clrDarkGoldenrod}

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

SOM-Explorer выводит в лог координаты центров кластеров, например для текущей карты:

Clusters [20]:
[ 0] "Profit"                        "Profit Factor"                 "Recovery Factor"               "Sharpe Ratio"                 
[ 4] "Equity DD %"                   "Trades"                        "Signal_ThresholdOpen"          "Signal_ThresholdClose"        
[ 8] "Signal_RSI_PeriodRSI"          "Signal_Envelopes_PeriodMA"     "Signal_Envelopes_Deviation"    "Trailing_ParabolicSAR_Step"   
[12] "Trailing_ParabolicSAR_Maximum"
N0 [3,2]
[0] 18780.87080     1.97233     3.60269     0.38653    16.76746    63.02193    20.00378
[7]    65.71576    24.30473    19.97783     0.50024     0.13956     1.46210
N1 [1,4]
[0] 18781.57537     1.97208     3.59908     0.38703    16.74359    62.91901    20.03835
[7]    89.61035    24.59381    19.99999     0.50006     0.12201     0.73983
...

Вначале идет легенда признаков, а далее для каждого кластера — его номер, координаты X и Y, а также значения признаков.

Здесь для 0-го кластера имеем следующие значения параметров рабочего эксперта:

Signal_ThresholdOpen=20
Signal_ThresholdClose=66
Signal_RSI_PeriodRSI=24
Signal_Envelopes_PeriodMA=20
Signal_Envelopes_Deviation=0.5
Trailing_ParabolicSAR_Step=0.14
Trailing_ParabolicSAR_Maximum=1.46

Запустим форвард-тест с данными параметрами (WizardTest-plus-allfeatures-auto-nomasks.set) и получим отчет:

Отчет тестера для настроек, выбранных автоматически из кластеров карт Кохонена без маски признаков

Отчет тестера для настроек, выбранных автоматически из кластеров карт Кохонена без маски признаков

Результат не намного лучше первого случайного теста и заметно хуже экспертного. Все дело в качестве кластеризации. В данный момент она выполняется по всем признакам сразу, включая и экономические показатели, и параметры эксперта. Но нам нужно искать "плато" исключительно в смысле торговых показателей. Применим для кластеризации маску признаков, которая учитывает только их - то есть первые шесть. Для этого установим:

FeatureMask=1111110000000
FeatureMaskAfterTraining=true

И снова запустим обучение. Кластеризация в SOM-Explorer выполняется как заключительный этап обучения, и полученные кластеры сохраняются в файле сети. Это нужно, чтобы загруженная позднее сеть могла сразу использоваться для "распознавания" новых образцов. Такие образцы могут содержать не все показатели (т.е. вектор может быть неполным), как например, со случаем анализа set-файлов — в нем содержатся только параметры эксперта, но нет экономических показателей (и они не имеют смысла). Поэтому для таких образцов в функции LoadSettings конструируется своя маска признаков, соответствующая имеющимся компонентам вектора, и затем она применяется к сети. Таким образом, маска кластеризации должна в неявном виде уже присутствовать в сети, чтобы не конфликтовать с маской "распознаваемого" вектора.

Но вернемся к обучению с новой маской. Она изменит плоскость U-Matrix и кластеров. Рисунок кластеров существенно изменится (слева до применения маски, справа — после).

Кластеры на картах Кохонена, построенных без применения маски по экономическим показателям (слева) и с применением маски (справа)

Кластеры на картах Кохонена, построенных без применения маски по экономическим показателям (слева) и с применением маски (справа)

Теперь и значения центра нулевого кластера иные.

N0 [14,1] 
[0] 17806.57263     2.79534     6.78011     0.48506    10.70147    49.90295    40.00000
[7]    85.62392    73.51490    20.00000     0.49750     0.13273     1.29078

Если Вы помните, в своей экспертной оценке мы выбрали нейрон в правом верхнем углу, то есть с координатами [14,0]. Сейчас система предлагает нам соседний нейрон [14,1]. Их веса мало отличаются. Перенесем предлагаемые настройки в параметры рабочего эксперта.

Signal_ThresholdOpen=40
Signal_ThresholdClose=86
Signal_RSI_PeriodRSI=74
Signal_Envelopes_PeriodMA=20
Signal_Envelopes_Deviation=0.5
Trailing_ParabolicSAR_Step=0.13
Trailing_ParabolicSAR_Maximum=1.29

Получим следующие результаты:

Отчет тестера для настроек, выбранных автоматически из кластеров карт Кохонена с использованием маски экономических показателей

Отчет тестера для настроек, выбранных автоматически из кластеров карт Кохонена с использованием маски экономических показателей

Они идентичны экспертным результатам, несмотря на некоторое отличие параметров.

Для облегчения переноса результатов кластеризации в настройки эксперта напишем вспомогательную функцию, которая будет генерировать set-файл с именами и значениями признаков выбранного кластера (по-умолчанию, нулевого). Функция называется SaveSettings и включается в работу, когда новый параметр SaveClusterAsSettings содержит индекс кластера, подлежащего экспорту. По-умолчанию в этом параметре содержится -1, что означает, что генерировать set-файл не нужно. Разумеется, данная функция не "знает" прикладной смысл признаков и сохраняет их все поименованными в set-файл, поэтому там будут и торговые показатели, в частности Profit, Profit Factor и т.д. Пользователь может скопировать из сгенерированного файла только признаки, соответствующие реальным параметрам эксперта. Значение параметров сохраняются в формате вещественных чисел — потребуется их корректировка для параметров целого типа.

Теперь, когда мы умеем сохранять найденные настройки в переносимом виде, создадим настройки для кластера 0 (Wizard2018plusCluster0.set) и загрузим их обратно в SOM-Explorer (напомним, что наша утилита уже умеет читать set-файлы). В параметре NetFileName необходимо указать имя сети, созданной на предыдущем этапе обучения (должно быть Wizard2018plus.som, потому что мы использовали данные Wizard2018plus.csv — а после каждого обучения сети, она сохраняется в файл с именем, соответствующем входному, но с расширением som). В параметре DataFileName укажем имя сгенерированного set-файла. Метка SETTINGS будет перекрываться с центром кластера C0.

Переименуем som-файл с сетью, чтобы он не был перезаписан в ходе следующих экспериментов.

Первый эксперимент будет таким. Обучим сеть с "обратной" маской — по параметрам эксперта, исключив торговые показатели. Для этого вновь укажем файл Wizard2018plus.csv в параметре DataFileName, очистим параметр NetFileName и укажем 

FeatureMask=0000001111111

Заметьте, что FeatureMaskAfterTraining по-прежнему равно true, то есть маска влияет только на кластеризацию.

По завершении обучения загрузим обученную сеть и проверим на ней наш set-файл. Для этого перенесем имя созданного файла сети Wizard2018plus.som в параметр NetFileName, а в DataFileName снова скопируем Wizard2018plusCluster0.set. Общий набор карт не изменится, но U-Matrix и, как следствие, кластеры станут другими.

Результат кластеризации показан на следующем изображении, слева:

Кластеры на картах Кохонена, построенных с маской по параметрам эксперта: только на стадии кластеризации (слева) и на стадии обучения сети и кластеризации (справа)

Кластеры на картах Кохонена, построенных с маской по параметрам эксперта: только на стадии кластеризации (слева) и на стадиях обучения сети и кластеризации (справа)

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

В качестве второго эксперимента обучим сеть еще раз точно с такой же маской (по параметрам эксперта), но теперь распространив её и на фазу обучения:

FeatureMaskAfterTraining=false

Полный набор карт приведен ниже, а изменение кластеров более крупно показано на рисунке выше, справа. Здесь нейроны "кучкуются" только по подобию параметров. Данные карты следует читать следующим образом: при выбранных параметрах — какие экономические показатели следует ожидать. Несмотря на то, что тестовые настройки попали в кластер номер 2 (он хотя и хуже, чем 0, но не намного), его размер один из самых больших, а это — положительная черта.

Карты Кохонена, построенные с маской по параметрам эксперта

Карты Кохонена, построенные с маской по параметрам эксперта

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

"Зеркальным" исследованием будет полное обучение и кластеризация с маской на экономические показатели:

FeatureMask=1111110000000
FeatureMaskAfterTraining=false

Результат такой:

Карты Кохонена, построенные с маской по экономическим показателям

Карты Кохонена, построенные с маской по экономическим показателям

Теперь контрастные цвета сконцентрированы в первых шести плоскостях экономических показателей, а карты параметров более аморфные. В частности, можно видеть, что значения параметров Trailing_ParabolicSAR_Step и Trailing_ParabolicSAR_Maximum практически не меняются (карты монотонные), значит - их можно исключить из оптимизации взяв за основу нечто среднее, например, 0.11 и 1.14 соответственно. Также среди карт параметров особо выделяется своим контрастом Signal_ThresholdOpen - из неё становится ясно, что для успешной торговли обязательно выбирать Signal_ThresholdOpen равный 0.4. Карта U-Matrix явно разделена на два "бассейна" — верхний и нижний: верх — успех, низ — неудача. Карта попаданий — очень редкая и неоднородная (большая часть пространства — пустоты, а активные нейроны — с большим значением счетчика), поскольку в исследованном советнике прибыли группируются по нескольким явным уровням.

Данные карты следует читать следующим образом: при выбранных значениях экономических показателей — какие параметры следует выбирать.

Наконец, для последнего эксперимента доработаем слегка SOM-Explorer, чтобы он мог давать осмысленные названия кластерам. Напишем функцию SetClusterLabels, которая будет анализировать значения отдельных компонент кодовых векторов внутри каждого кластера и соотносить их с диапазоном значений этих компонент. Когда значение веса в каком-то нейроне будет приближаться к максимальному или минимальному, это станет поводом пометить его названием соответствующей компоненты. Например, если вес 0-й связи (соответствует прибыли), больше, чем веса в других кластерах, значит данный кластер характеризуется высокой прибылью. Следует отметить, что знак экстремума — максимум или минимум — определяется смыслом показателя: так для прибыли — чем больше значение, тем лучше, а для просадки — наоборот. В связи с этим мы введем специальный входной параметр FeatureDirection, в котором будем обозначать показатели с положительным эффектом символом '+', с отрицательным эффектом — символом '-', а неважные (или не имеющие смысла) пропускать с помощью символа ','. Обратите внимание, что в нашем случае имеет смысл маркировать только экономические показатели, т.к. значения рабочих параметров эксперта могут быть любыми и не трактуются хорошими или плохими в зависимости от близости к границам области определения. Поэтому установим значение FeatureDirection только для первых шести признаков:

FeatureDirection=++++-+

Вот как выглядят метки для сети с обучением на полном векторе и кластеризацией по экономическим показателям (слева) и для сети с обучением и кластеризацией с маской по параметрам эксперта.

Метки кластеров для случаев: использования маски экономических показателей на стадии кластеризации (слева) и использования маски параметров на стадиях обучения и кластеризации (справа)

Метки кластеров для случаев: использования маски экономических показателей на стадии кластеризации (слева) и использования маски параметров на стадиях обучения и кластеризации (справа)

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

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


Цветная классификация результатов оптимизации

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

Отобразим веса признаков на карте Кохонена в цветовом пространстве RGB и выберем наиболее светлую точку. Поскольку RGB-кодировка цвета состоит из 3-х плоскостей, мы можем визуализировать таким образом ровно 3 признака. Если их будет меньше или больше, вместо RGB-цветов покажем яркость в градациях серого и все равно определим наиболее светлую точку.

Напомним, что при выборе признаков желательно отбирать максимально независимые (о чем уже шла речь выше).

При реализации нового подхода одновременно продемонстрируем возможность расширять классы CSOM своими собственными. Создадим класс CSOMDisplayRGB, в котором переопределим лишь несколько виртуальных методов родительского CSOMDisplay и за счет этого добъемся, что RGB-карта станет отображаться вместо последней плоскости DIM_OUTPUT.

class CSOMDisplayRGB: public CSOMDisplay
{
  protected:
    int indexRGB[];
    bool enabled;
    bool showrgb;

    void CalculateRGB(const int ind, const int count, double &sum, int &col) const;
  
  public:
    void EnableRGB(const int &vector[]);
    void DisableRGB() { enabled = false; };
    virtual void RenderOutput() override;
    virtual string GetNodeAsString(const int node_index, const int plane) const override;
    virtual void Reset() override;
};

Полный код приведен в приложении.

Для использования этой версии создадим модификацию SOM-Explorer-RGB и проведем следующие изменения.

Добавим входной параметр для включения RGB-режима:

input bool ShowRGBOutput = false;

Объект карты станет дочернего класса:

CSOMDisplayRGB KohonenMap;

Непосредственно отображение RGB-плоскости реализуем в отдельной ветке кода:

  if(ShowRGBOutput && StringLen(FeatureDirection) > 0)
  {
    int rgbmask[];
    ArrayResize(rgbmask, StringLen(FeatureDirection));
    for(int i = 0; i < StringLen(FeatureDirection); i++)
    {
      rgbmask[i] = -(StringGetCharacter(FeatureDirection, i) - ',');
    }
    KohonenMap.EnableRGB(rgbmask);
    KohonenMap.RenderOutput();
    KohonenMap.DisableRGB();
  }

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

FeatureDirection=++,,-

чтобы первые два признака — прибыль и ПФ — попали в карту напрямую (соответствуют символам '+'), а пятый признак — просадка — как инвертированное значение (соответствует символу '-'). Признаки, соответствующие ',' пропускаются. Все последующие - также не участвуют в рассмотрении. Файл с настройками WizardTest-rgb.set прилагается (предполагается, что файл сети Wizard2018plus.som имеется с предыдущего этапа).

Вот как выглядит RGB-пространство для этих настроек (слева).

RGB-пространство для признаков (прибыль, ПФ, просадка) и (ПФ, просадка, сделки)

RGB-пространство для признаков (прибыль, ПФ, просадка) и (ПФ, просадка, сделки)

Цвета соответствуют признакам в порядке очередности: прибыль — красный, ПФ - зеленый, просадка — синий. Нейрон с максимальной яркостью помечен меткой "RGB".

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

Поскольку прибыль и ПФ сильно коррелированы, изменим маску на другую:

FeatureDirection=,+,,-+

В этом случае прибыль пропускается, но добавлено количество сделок (у некоторых брокеров есть бонусы за объемы). Результат — на рисунке выше (справа). Здесь соответствие цветов другое: ПФ - красный, просадка - зеленый, количество сделок — синий.

Если выбрать только признаки ПФ и просадка, получим карту в тонах серого (слева):

RGB-пространство для признаков (ПФ, просадка) и (прибыль)

RGB-пространство для признаков (ПФ, просадка) и (прибыль)

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


Анализ форвард-тестов

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

Для этого возьмем результаты оптимизации WizardTest вместе с форвард-тестами (напомню, оптимизация проводилась по прибыли на интервале 20180101-20180701, а форвард, соответственно, — 20180701-20181201), отбросим из неё все показатели кроме прибыли в прошлом и будущем, и получим новый входной файл для сети — Wizard2018-with-forward.csv (приложен к статье). Файл с настройками — WizardTest-with-forward.set — также прилагается.

Обучение сети по всем признакам, включая параметры эксперта, дает следующую картину:

Карты Кохонена с анализом форвад-тестов по всем признакам (показателям прибыльности и параметрам эксперта)

Карты Кохонена с анализом форвад-тестов по всем признакам (показателям прибыльности и параметрам эксперта)

Мы видим, что пятно с прибылью в будущем значительно меньше прибыли в прошлом, и нулевой кластер (красный) содержит это место. Кроме того, я включил цветовой анализ по маске двух показателей (FeatureDirection=++), и на последней карте самая светлая точка также попадает в данный кластер. Однако, ни центр кластера, ни светлая точка не содержат супер-выгодных настроек — при проверке в тестере результаты хоть и положительные, но раза в два хуже полученных ранее.

Попробуем построить карту Кохонена с использованием маски по первым двум признакам, т.к. только они являются показателями. Для этого применим другие настроки с FeatureMask=110000000 (см. WizardTest-with-forward-mask11.set) и переобучим сеть. В результате получится следующее.

Карты Кохонена с анализом форвад-тестов по показателям прибыльности

Карты Кохонена с анализом форвад-тестов по показателям прибыльности

Здесь видно, что первые две плоскости получили наиболее выраженную пространственную структуру и имеют пересечение областей с высокими значениями (прибылями) в нижнем левом углу, он же подсвечен в последней карте. Взяв настройки из этого нейрона, мы получим такой результат:

Отчет тестера для настроек, выбранных по принципу "стабильности прибыли"

Отчет тестера для настроек, выбранных по принципу "стабильности прибыли"

Это снова хуже достигнутой ранее прибыли.

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


Прогнозирование временных рядов

Обзор

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

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

Одна сеть Кохонена A обучается на данных с неполными векторами размером (n - 1), то есть без последней компоненты. Для каждого нейрона i первой сети обучается дополнительная сеть Кохонена Bi для усеченного набора полных векторов размером n, соответствующих тем неполным, которые были отображены в этот i-й нейрон сети A. По нейронам в сети Bi считается вероятность конкретных значений последней координаты n. Далее, на стадии эксплуатации, при попадании прогнозируемого вектора в нейрон i сети A, производится моделирование будущего отсчета согласно полученным вероятностям из сети Bi.

Другой вариант. Одна сеть Кохонена A обучается на полных векторах. Вторая сеть Кохонена B обучается на модифицированных векторах, в которых вместо значений берутся приращения, т.е.

yk = xk+1 - xk, k = 1 .. n - 1

где yk и xk - компоненты, соответственно исходного и модифицированного вектора. Очевидно, что размер модифицированного вектора на 1 меньше исходного. Далее, параллельно подавая на вход обеих сетей обучающие данные, подсчитывают количество одновременных отображений полного вектора в нейрон i в первой сети и в нейрон j второй сети. Таким образом получают условные вероятности pij того, что вектор y попадет в нейрон j (в сети B) при условии, что соответствующий ему вектор x попал в нейрон i (в сети A). На стадии эксплуатации подают неполный вектор x- (без последней компоненты) на сеть A, находят ближайший нейрон i* по (n - 1) компонентам и далее генерируют возможное приращение согласно вероятностям pi*. Этот метод, как и некоторые другие, перекликается с методом Монте-Карло, суть которого в генерации случайных значений для прогнозируемой компоненты и нахождение наиболее вероятного исхода согласно статистике заселения нейронов данными из обучающей выборки.

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

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

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

Будем подавать на вход сети полные вектора временного ряда, однако расчет расстояний — выполнять только для (n - 1) первых компонент. Подстройка весов будет осуществляться полностью по всем компонентам. Затем, для прогнозирования подадим на вход сети неполный вектор, найдем лучший нейрон по (n - 1) компоненте и прочитаем значение веса последнего n-го синапса. Это и будет прогноз.

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


Кластерный индикатор Unity

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

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

Теоретически, их будущие движения должны учитывать текущие котировки, а также реагировать на котировки Forex.

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

Для этого был создан оригинальный кластерный индикатор Unity (исходный код приложен в конце статьи). Суть его работы описывается следующим алгоритмом. Рассмотрим его на минимальном возможно примере — с одной парой валют EURUSD и золотом XAUUSD.

Каждый временной отсчет (текущие цены по состоянию на начало или конец дня) описывается очевидными формулами:

EUR / USD = EURUSD

XAU / USD = XAUUSD

где переменные EUR, USD, XAU — некие самостоятельные "стоимости" активов, а EURUSD и XAUUSD — константы (известные котировки).

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

EUR*EUR + USD*USD + XAU*XAU = 1

Отсюда и название индикатора — Unity.

Путем простой подстановки получим:

EURUSD*USD*EURUSD*USD + USD*USD + XAUUSD*USD*XAUUSD*USD = 1

Откуда находим USD:

USD = sqrt(1 / (1 + EURUSD*EURUSD + XAUUSD*XAUUSD))

и затем все остальные переменные.

Или в более общем виде:

x0 = sqrt(1 / (1 + sum(C(xi, x0)**2))), i = 1..n

xi = C(xi, x0) * x0, i = 1..n

где n — количество переменных, C(xi,x0) — котировка i-ой пары, включающей соответствующие переменные. Обратите внимание, что количество переменных на 1 больше, чем инструментов.

Поскольку коэффициенты C, участвующие в расчете, — это котировки, которые обычно сильно отличаются, в индикаторе они дополнительно умножаются на размер контракта: тем самым получаются более или менее сравнимые значения (по крайней мере, одного порядка). Чтобы увидеть их в окне индикатора (просто для сведения) имеется входной параметр AbsoluteValues, который следует установить в true. По-умолчанию, он, разумеется, равен false, и индикатор всегда рассчитывает приращения переменных:

yi = xi0 / xi1 - 1,

где xi0 и xi1 — значения на последнем и предпоследнем барах соответственно.

Технические особенности реализации индикатора здесь рассматриваться не будут — ознакомиться с исходным кодом можно самостоятельно.

Таким образом, получаем следующую картину:

Кластерный (мультивалютный) индикатор Unity, XAUUSD

Кластерный (мультивалютный) индикатор Unity, XAUUSD

Линии активов, составляющих рабочий инструмент текущего чарта (в данном случае, XAU и USD), изображаются широкими, остальные — обычной толщины.

Среди прочих входных параметров индикатора стоит отметить:

  • Instruments — строка с названиями рабочих инструментов через запятые; необходимо, чтобы у всех инструментов совпадала одна из валют — либо базовая, либо котирования;
  • BarLimit — количество баров для расчета; когда мы дойдем до обучения нейросети, это станет размером обучающей выборки;
  • SaveToFile — название csv-файла, в который индикатор экспортирует значения для последующей загрузки в нейросеть; структура файла простая: первый столбец — дата, все последующие - значения соответствующих индикаторных буферов;
  • ShiftLastBuffer - флаг переключения режима, в котором формируется csv-файл; когда опция равна false, в файл в каждой строке выводятся данные одного и того же бара, количество столбцов равно количеству инструментов плюс один из-за разделения тикеров на составляющие и плюс еще один столбец — самый первый — для даты, имена столбцов соответствуют валютам и металлам; когда опция равна true, создается дополнительный столбец с именем FORECAST, в который со сдвигом на один день вперед сохраняются значения из столбца с последним активом; таким образом, в каждой строке мы видим не только все данные за текущий день, но и завтрашнее изменение последнего инструмента.

Например, чтобы подготовить файл с данными для прогнозирования изменения цены золота, нужно указать в параметре Instruments - "EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD,XAUUSD" (важно, что XAUUSD указано последним), а в ShiftLastBuffer - true. Получим csv-файл примерно со следующей структурой:

           datetime;      EUR;     USD;      GBP;      CHF;      JPY;      AUD;      CAD;      NZD;      XAU; FORECAST
2016.12.20 00:00:00; 0.001825;0.000447;-0.000373; 0.000676;-0.004644; 0.003858; 0.004793; 0.000118;-0.004105; 0.000105
2016.12.21 00:00:00; 0.000228;0.003705;-0.001081; 0.002079; 0.002790;-0.002885;-0.003052;-0.002577; 0.000105;-0.000854
2016.12.22 00:00:00; 0.002147;0.003368;-0.003467; 0.003427; 0.002403;-0.000677;-0.002715; 0.002757;-0.000854; 0.004919
2016.12.23 00:00:00; 0.000317;0.003624;-0.002207; 0.000600; 0.002929;-0.007931;-0.003225;-0.003350; 0.004919; 0.004579
2016.12.27 00:00:00;-0.000245;0.000472;-0.001075;-0.001237;-0.003225;-0.000592;-0.005290;-0.000883; 0.004579; 0.003232

Обратите внимание на 2 последних колонки: они содержат одни и те же числа со сдвигом на один ряд. Так, в строке за 20 декабря 2016 мы видим не только приращение XAU за этот день, но и его приращение за 21 декабря в колонке FORECAST.


SOM-Forecast

Настало время для реализации самого движка прогнозирования на основе сети Кохонена. Для начала, чтобы понять, как это работает, возьмем за основу уже известный SOM-Explorer и слегка адаптируем его для задачи прогнозирования.

Изменения коснуться в первую очередь входных переменных. Удалим всё связанное с настройкой масок: FeatureMask, ApplyFeatureMaskAfterTraining, FeatureDirection, поскольку маска для прогнозирования известна - при наличии вектора размером n отсчетов, в подсчете расстояния должны участвовать только первые (n - 1). Но мы добавим специальную логическую опцию - ForecastMode — которая позволит при необходимости отключать эту маску и использовать аналитические возможности сети Кохонена в их классическом виде. Это понадобится нам, чтобы исследовать рынок, а точнее систему из заданных в Unity инструментов, в статическом состоянии, то есть увидеть корреляции за один и тот же день.

В том случае, если ForecastMode равен true, мы ставим маску, а если false - маски нет.

    if(ForecastMode)
    {
      KohonenMap.SetFeatureMask(KohonenMap.GetFeatureCount() - 1, 0);
    }

Когда сеть уже обучена и во входном параметре DataFileName указан csv-файл с тестовыми данными, то в режиме ForecastMode мы проверим качество прогнозирования следующим образом:

      if(ForecastMode)
      {
        int m = KohonenMap.GetFeatureCount();
        KohonenMap.SetFeatureMask(m - 1, 0);
        
        int n = KohonenMap.GetDataCount();
        double vector[];
        double forecast[];
        double future;
        int correct = 0;
        double error = 0;
        double variance = 0;
        for(int i = 0; i < n; i++)
        {
          KohonenMap.GetPattern(i, vector);
          future = vector[m - 1]; // preserve future
          vector[m - 1] = 0;      // make future unknown for the net (it's not used anyway due to the mask)
          KohonenMap.GetBestMatchingFeatures(vector, forecast);
          
          if(future * forecast[m - 1] > 0) // check if the directions match
          {
            correct++;
          }
          
          error += (future - forecast[m - 1]) * (future - forecast[m - 1]);
          variance += future * future;
        }
        Print("Correct forecasts: ", correct, " out of ", n, " => ", DoubleToString(correct * 100.0 / n, 2), "%, error => ", error / variance);
      }

Здесь каждый тестовый вектор предъявляется сети с помощью GetBestMatchingFeatures и в ответ получается вектор предсказаний — forecast. Последняя его компонента сравнивается с правильным значением из тестового вектора. Совпадения направления подсчитываются в переменной correct, а также накапливается общая ошибка предсказания error и затем выводится в нормированном виде — относительно разброса самих данных.

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

Выбрав размер, мы сбросим ValidationSetPercent и ReframeNumber в 0, и обучим сеть, как обычно, с помощью метода Train.

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

void SetClusterLabels()
{
  const int nclusters = KohonenMap.GetClusterCount();

  double min, max, best;
  
  double bests[][3]; // [][0 - value; 1 - feature index; 2 - direction]
  ArrayResize(bests, nclusters);
  ArrayInitialize(bests, 0);
  
  int n = KohonenMap.GetFeatureCount();
  for(int i = 0; i < n; i++)
  {
    int direction = 0;
    KohonenMap.GetFeatureBounds(i, min, max);
    if(max - min > 0)
    {
      best = 0;
      double center[];
      for(int j = nclusters - 1; j >= 0; j--)
      {
        KohonenMap.GetCluster(j, center);
        double value = MathMin(MathMax((center[i] - min) / (max - min), 0), 1);
        
        if(value > 0.5)
        {
          direction = +1;
        }
        else
        {
          direction = -1;
          value = 1 - value;
        }
        
        if(value > bests[j][0])
        {
          bests[j][0] = value;
          bests[j][1] = i;
          bests[j][2] = direction;
        }
      }
    }
  }

  // ...
  
  for(int j = 0; j < nclusters; j++)
  {
    if(bests[j][0] > 0)
    {
      KohonenMap.SetLabel(j, (bests[j][2] > 0 ? "+" : "-") + KohonenMap.GetFeatureTitle((int)bests[j][1]));
    }
  }
}

Итак, будем считать, что SOM-Forecast готов. Попробуем подать на его вход значения индикатора Unity.


Анализ

Для начала проанализируем весь рынок (набор выбранных активов) в статичном, а точнее статистическом разрезе, то есть на данных, экспортируемых индикатором Unity строго по дням — каждый строка соответствует показаниям на одном баре D1, без дополнительного столбца из "будущего".

Для этого укажем в индикаторе набор инструментов Forex, золото и серебро, название файла в SaveToFile, а флаг ShiftLastBuffer поставим равным false. Пример полученного таким образом файла unity500-noshift.csv приложен в конце статьи.

Обучив на этих данных сеть с помощью SOM-Forecast (см. som-forecast-unity500-noshift.set), получим такую карту:

Визуальный анализ рынка Forex, золота и серебра на картах Кохонена по индикатору Unity для D1

Визуальный анализ рынка Forex, золота и серебра на картах Кохонена по индикатору Unity для D1

Два верхних ряда - карты активов. Их сопоставление позволяет выявить постоянные связи, работавшие, как минимум, на протяжении последних 500 дней. В частности, в глаза бросается два разноцветных пятна в центрах: у GBP это синий цвет, а у AUD - желтый. Это значит, что эти активы часто ходили в противофазе, и речь только о сильных движениях, т.е. по пробою уровня средней дисперсии рынка преобладали продажи GBPAUD. Эта тенденция может сохраниться, и дает вариант для выставления отложенных ордеров в этом направлении.

В верхнем правом углу наблюдается рост XAG, в то время как у CHF там падение. А в левом нижнем углу они ведут себя наоборот: у CHF — рост, а у XAG — падение. Эти активы при сильных движениях всегда расходятся — их можно торговать на пробой в обе стороны. EUR и CHF, как и следовало ожидать, похожи.

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

Посмотрим на кластеры повнимательнее.

Кластеры рынка Forex, золота и серебра на картах Кохонена по индикатору Unity для D1

Кластеры рынка Forex, золота и серебра на картах Кохонена по индикатору Unity для D1

Они тоже дают кое-какую информацию - например, о том, чего обычно не происходит (или происходит редко), а именно: EUR не растет одновременно с падением JPY или CAD. Рост GBP часто перекрывается с падением CHF и наоборот.

Судя по размерам кластеров, валюты EUR, GBP и CHF являются наиболее волатильными (их движения чаще перекрывают движения других), а доллар, как ни странно, проигрывает в этом и JPY, и CAD (напомню, мы считаем изменения в процентах). Если предположить, что номера кластеров тоже имеют значение (кластеры у нас отсортированы), то первые три, а это - +CHF, -JPY, +GBP - описывают, по-видимому, наиболее частые дневные движения (имеется в виду не тренд, а именно частота — ведь даже во время "флета" большее количество "шагов" вверх может компенсироваться одним большим "шагом" вниз).

Теперь, наконец, займемся проблемой прогнозирования.


Прогнозирование

Укажем в индикаторе набор инструментов, включающий Forex и золото ("EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD,XAUUSD"), количество баров в BarLimit (по-умолчанию, 500), название файла в SaveToFile (например, unity500xau.csv) и флаг ShiftLastBuffer поставим в true (режим создания дополнительной колонки для прогнозирования). Поскольку индикатор мультивалютный, количество доступных данных ограничено самой короткий историей из всех инструментов. На сервере MetaQuotes-Demo для таймфрейма D1 находится вполне достаточное количество - как минимум 2000 баров, и здесь стоит рассмотреть другой нюанс: имеет ли смысл обучать сеть на такую глубину — ведь рынок, скорее всего, заметно изменился. Вероятно, горизонт 2-3 года, или даже 1 год (250 баров на D1) - лучше подойдет для выявления текущих закономерностей.

Пример файла unity500xau.csv приложен в конце статьи.

Для загрузки его в SOM-Forecast установим следующие входные параметры: DataFileName — unity500xau, ForecastMode — true, ValidationSetPercent — 10, и что важно: ReframeNumber — 10. Так мы не просто запустим обучение сети размера 10*10 (значения по умолчанию), но и включим проверку ошибки на валидационной выборке и продолжение обучения с поэтапным увеличением размера сети пока ошибка уменьшается. В методе TrainAndReframe произойдет до 10 увеличений размеров, по 2 нейрона в каждом измерении. Тем самым мы определим оптимальный размер сети для входных данных. Файл с настройками (som-forecast-unity500xau.set) прилагается.

В процессе поэтапного обучения и увеличения сети в лог выводится примерно следующее (приведено с сокращениями):

FileOpen OK: unity500xau.csv
HEADER: (11) datetime;EUR;USD;GBP;CHF;JPY;AUD;CAD;NZD;XAU;FORECAST
Training 10*10 hex net starts
...
Exit by validation error at iteration 104; NMSE[old]=0.4987230270708455, NMSE[new]=0.5021707785446128, set=50
Training stopped by MSE at pass 104, NMSE=0.384537545433749
Training 12*12 hex net starts
...
Exit by validation error at iteration 108; NMSE[old]=0.4094350709134669, NMSE[new]=0.4238670029035179, set=50
Training stopped by MSE at pass 108, NMSE=0.3293719049246978
...
Training 24*24 hex net starts
...
Exit by validation error at iteration 119; NMSE[old]=0.3155973731785412, NMSE[new]=0.3177587737459486, set=50
Training stopped by MSE at pass 119, NMSE=0.1491464262340352
Training 26*26 hex net starts
...
Exit by validation error at iteration 108; NMSE[old]=0.3142964426509741, NMSE[new]=0.3156342534501801, set=50
Training stopped by MSE at pass 108, NMSE=0.1669971604289485
Exit map size increments due to increased MSE
...
Map file unity500xau.som saved

Таким образом, процесс остановился на размере сети 26*26 и вроде бы следует выбрать 24*24, когда ошибка была минимальной. Но на самом деле, при работе с нейронными сетями мы всегда оперируем случайностями. Если помните, один из параметров — RandomSeed — отвечает за инициализацию генератора случайных чисел, который используется для начальной установки весов в сети. Меняя RandomSeed, мы каждый раз будем получать новую сеть с новыми характеристиками. И при прочих равных условиях она будет лучше или хуже обучаться, чем другие экземпляры. Поэтому для выбора как размера сети, так и прочих настроек (включая размер обучающей выборки), обычно приходится выполнять много проб и ошибок. В дальнейшем мы по такому принципу сократим выборку обучающих данных и уменьшим размер сети до 15*15. Кроме того, я иногда практикую неканоническую модификацию описываемого метода темпоральной квантизации векторов. Модификация заключается в том, чтобы обучать сеть с отключенным флагом ForecastMode и включать его только при прогнозировании, и это в некоторых случаях дает положительный эффект. Работа с нейронными сетями никогда не предполагает готовых блюд, а только — рецепты. И эксперименты не возбраняются.

Прежде чем приступать к обучению нужно поделить исходный файл (в данном случае unity500xau.csv) на 2. Дело в том, что после обучения нам потребуется на каких-то данных проверить качество прогнозирования. Делать это на тех же данных, на которых обучалась сеть, не имеет смысла (точнее, имеет смысл только для того, чтобы убедиться, что процент верных ответов нереально высок - Вы можете проверить). Поэтому скопируем 50 векторов для теста в отдельный файл, а на остальных и будем обучать новую сеть. Соответствующие файлы unity500xau-training.csv и unity500xau-validation.csv прилагаются.

Указываем unity500xau-training в параметре DataFileName (расширение .csv подразумевается), размеры CellsX и CellsY — по 24, ValidationSetPercent — 0, ReframeNumber — 0, ForecastMode — по-прежнему true (см. som-forecast-unity500xau-training.set). В результате обучения получим такую карту:

Сеть Кохонена для прогноза движения золота на D1

Сеть Кохонена для прогноза движения золота на D1

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

Для этого введем следующие настройки. Имя unity500xau-training теперь переносим в параметр NetFileName (это имя получил файл обученной сети, но уже с расширением som, оно подразумевается). В параметре DataFileName указываем unity500xau-validation. Сеть выполнит предсказание для всех векторов, и выведет в лог статистику:

Map file unity500xau-training.som loaded
FileOpen OK: unity500xau-validation.csv
HEADER: (11) datetime;EUR;USD;GBP;CHF;JPY;AUD;CAD;NZD;XAU;FORECAST
Correct forecasts: 24 out of 50 => 48.00%, error => 1.02152123166208

Увы, точность прогноза — на уровне случайного гадания. А мог ли получиться грааль при таком простом выборе входных данных? Вряд ли. Да и глубину истории мы выбрали навскидку, хотя этот вопрос также требует исследований. Кроме того, необходимо "поиграть" набором инструментов — добавить, например, серебро, и вообще выяснить, какой из активов более зависим от остальных, чтобы облегчить прогнозирование. Чуть ниже мы рассмотрим пример с серебром.

Для внесения во входные данные большего контекста, я добавил в индикатор Unity возможность формировать вектор не только на основе одного дня, но и нескольких последних. Для этого появился параметр BarLookback, равный по умолчанию 1, что соответствует прежнему режиму. Но если ввести здесь, например, 5, то вектор будет содержать отсчеты всех активов за 5 дней (с фундаментальной точки зрения, неделя — один из основных периодов). При 9 активах (8 валют и золото) в каждой строке csv-файла хранится 46 значений. Это уже немало, расчеты замедляются, анализ карт затрудняется: даже мелкие карты с трудом умещаются на экран, а для больших может не хватить истории на графике.

Внимание. При просмотре такого большого количества карт в режиме объектов типа OBJ_BITMAP (MaxPictures = 0) на графике может не хватить баров - они хоть и скрыты, но используются для привязки изображений.

Для демонстрации работоспособности метода я остановил свой выбор на 3 днях обратного просмотра и сети размером 15*15. Файлы unity1000xau3-training.csv и unity1000xau3-validation.csv приложены в конце статьи. При обучении сети на первом файле и проверке на втором получим 58% точных предсказаний.

Map file unity1000xau3-training.som loaded
FileOpen OK: unity1000xau3-validation.csv
HEADER: (29) datetime;EUR3;USD3;GBP3;CHF3;JPY3;AUD3;CAD3;NZD3;XAU3;EUR2;USD2;GBP2;CHF2;JPY2;AUD2;CAD2;NZD2;XAU2;EUR1;USD1;GBP1;CHF1;JPY1;AUD1;CAD1;NZD1;XAU1;FORECAST
Correct forecasts: 58 out of 100 => 58.00%, error => 1.131192104823076

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


Unity-Forecast

Для прогнозирования не обязательно выводить карты на экран. Внутри MQL-программ, например, экспертов и индикаторов, мы можем использовать класс CSOM вместо CSOMDisplay. Создадим индикатор Unity-Forecast, аналогичный Unity, но с дополнительным буфером для отображения прогноза. Сеть Кохонена для получения "будущих" значений может обучаться отдельно (в SOM-Forecast) и затем загружаться в индикатор, или сеть можно обучать на лету непосредственно в самом индикаторе. Реализуем оба режима.

Для загрузки сети добавим входной параметр NetFileName. Для обучения сети добавим группу параметров, аналогичную той, что имеется в SOM-Forecast: CellsX, CellsY, HexagonalCell, UseNormalization, EpochNumber, ShowProgress, RandomSeed.

Параметр AbsoluteValues здесь не нужен — заменим его на константу false. ShiftLastBuffer тоже не имеет смысла, т.к. новый индикатор всегда подразумевает прогноз. Экспорт в csv-файл исключен — удалим параметр SaveToFile. Зато добавим флаг SaveTrainedNetworks: если он равен true, индикатор будет сохранять обученные сети в файлы, чтобы их карты можно было изучить в SOM-Forecast.

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

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

Включим заголовочный файл:

#include <CSOM/CSOM.mqh>

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

  if(LastBarCount != rates_total || prev_calculated != rates_total)
  {
    static int prev_training = 0;
    if(prev_training == 0 || prev_calculated - prev_training > RetrainingBars)
    {
      if(NetFileName != "")
      {
        if(!KohonenMap.Load(NetFileName))
        {
          Print("Map loading failed: ", NetFileName);
          initDone = false;
          return 0;
        }
      }
      else
      {
        TrainSOM(BarLimit);
      }
      prev_training = prev_calculated > 0 ? prev_calculated : rates_total;
    }
    ForecastBySOM(prev_calculated == 0);
  }

Функции TrainSOM и ForecastBySOM приведены ниже (в упрощенном виде). Полный исходный код приложен к статье.

CSOM KohonenMap;

bool TrainSOM(const int limit)
{
  KohonenMap.Reset();

  LoadPatterns(limit);
  
  KohonenMap.Init(CellsX, CellsY, HexagonalCell);
  KohonenMap.SetFeatureMask(KohonenMap.GetFeatureCount() - 1, 0);
  KohonenMap.Train(EpochNumber, UseNormalization, ShowProgress);

  if(SaveTrainedNetworks)  
  {
    KohonenMap.Save("online-" + _Symbol + CSOM::timestamp() + ".som");
  }
  
  return true;
}

bool ForecastBySOM(const bool anew = false)
{
  double vector[], forecast[];
  
  int n = workCurrencies.getSize();
  ArrayResize(vector, n + 1);
  
  for(int j = 0; j < n; j++)
  {
    vector[j] = GetBuffer(j, 1); // 1-st bar is the latest completed
  }
  vector[n] = 0;
  
  KohonenMap.GetBestMatchingFeatures(vector, forecast);
  
  buffers[n][0] = forecast[n];
  if(anew) buffers[n][1] = GetBuffer(n - 1, 1);
  
  return true;
}

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

Попробуем на этот раз спрогнозировать поведение серебра и визуализируем весь процесс в тестере. Индикатор будет обучаться на лету на последних 250 днях (1 год) и переучиваться каждые 20 дней (1 месяц). Файл с настройками unity-forecast-xag.set приведен в конце статьи. Важно отметить, что набор инструментов расширен: EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD,XAUUSD,XAGUSD. Таким образом, мы прогнозируем XAG не только на основе котировок валют Forex и самого серебра, но и золота.

Вот как выглядит тестирование на периоде с 01.07.2018 по 01.12.2018.

Unity-Forecast: прогнозирование движений серебра по кластеру Forex и золота в визуальном тестере MetaTrader 5

Unity-Forecast: прогнозирование движений серебра по кластеру Forex и золота в визуальном тестере MetaTrader 5

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

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

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


Заключение

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


Прикрепленные файлы |
Kohonen2MQL5.zip (302.62 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (21)
fxsaber
fxsaber | 2 фев 2019 в 20:14
Maxim Dmitrievsky:

Вопрос не в этом, а в том нужна ли голая генетика вообще. Но если получится что-то интересное у Вас, то хорошо.

Ну все такие 30 млн проходов мне не перебрать полным перебором. И за 70 секунд получить неплохой результат - наверное, хорошо. Но методов улучшения нет, кроме ручного запуска.


Пока смотрел варианты по теме оптимизации (этот, например) пришел к выводу, что, наверное, будет не сложно забацать автооптимизацию на реальных тиках. Стало интересно, улучшаться ли результаты ТС?

fxsaber
fxsaber | 3 фев 2019 в 01:58
fxsaber:

не сложно забацать автооптимизацию на реальных тиках.

Столкнулся с проблемой повторяемости: ее нет от прохода к проходу. Какие есть способы получения одинаковых автопотимизационных проходов?

Maxim Dmitrievsky
Maxim Dmitrievsky | 3 фев 2019 в 02:08
fxsaber:

Столкнулся с проблемой повторяемости: ее нет от прохода к проходу. Какие есть способы получения одинаковых автопотимизационных проходов?

если там ГСЧ есть то MathSrand()

fxsaber
fxsaber | 3 фев 2019 в 08:57
Maxim Dmitrievsky:

если там ГСЧ есть то MathSrand()

Спасибо, помогло.

Stanislav Korotky
Stanislav Korotky | 4 фев 2019 в 12:52
fxsaber:

В статье во время анализа разных карт используется свойство, что в двух картах ячейка с совпадающими координатами (X; Y) соответствует одному и тому же набору входных параметров ТС. Как это правило формируется?

Если сравниваются карты, то каждая из них - это срез по i-ым весовым коэффициентам соответствующих нейронов (одна и та же пара координат X;Y - один и тот же нейрон). А каждый нейрон собирает в себя похожие (но не обязательно точно одни и те же) вектора, т.е. в нашем случае настройки ТС. Такое обобщение гарантирует сам алгоритм Кохонена.

Утилита для отбора и навигации на MQL5 и MQL4: добавляем вкладки "домашки" и сохраняем графические объекты Утилита для отбора и навигации на MQL5 и MQL4: добавляем вкладки "домашки" и сохраняем графические объекты

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

Практическое использование нейросетей Кохонена в алгоритмическом трейдинге (Часть I). Инструментарий Практическое использование нейросетей Кохонена в алгоритмическом трейдинге (Часть I). Инструментарий

Данная статья развивает идею использования сетей Кохонена в МетаТрейдер 5, освещавшуюся в нескольких предыдущих материалах. Исправленные и усовршенствованные классы предоставляют инструментарий для решения прикладных задач.

Анализ торговли по HTML-отчетам Анализ торговли по HTML-отчетам

Кроме торговых отчетов MetaTrader 5 позволяет сохранять отчеты о тестировании и оптимизации экспертов. Отчет тестирования так же, как и история торговли, может быть сохранен в двух форматах: XLSX и HTML, а отчет оптимизации сохраняется в формате XML. В этой статье будет рассмотрен разбор HTML-отчета тестера, XML-отчета оптимизации и HTML-отчет с историей торговли.

Практическое применение корреляций в торговле Практическое применение корреляций в торговле

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