Мы продолжаем публикацию серии материалов, посвященных вопросам создания стратегий для торговли на бирже, основанную на статьях автора блога Financial Hacker. В предыдущих топиках мы поговорили об использовании неэффективностей рынка на примере истории с ценовыми ограничением для швейцарского франка, рассмотрели важные факторы, влияющие на эффективность стратегии и обсудили общие принципы разработки модель-ориентированных торговых систем.
Сегодня же речь пойдет об использовании для этих целей технологий дата майнинга и машинного обучения.
В 1996 году компьютер Deep Blue впервые победил чемпиона мира по шахматам. Прошло еще 20 лет и программа AlphaGo победила в серии с лучшим игроком в Го, уступив лишь одну игру. Deep Blue представлял собой модель-ориентированную систему с жестким набором шахматных правил. AlphaGo же использует технологии Data Mining. Это нейронная сеть, обученная на примерах тысячи партий Го. В отличие от шахмат, в этой игре пространство выбора вариантов настолько огромно, что простой перебор не поможет. Поэтому прорыв произошел не за счет совершенствования «железа», а исключительно благодаря новому софту.
Сегодня мы мы рассмотрим подход к использованию дата-майнинга для разработки торговых стратегий, который не подразумевает глубокого анализа рыночных механизмов. Вместо этого он использует информацию из кривой цен и других источников для поиска в ней предсказуемых аномалий. Машинное обучение или «искусственный интеллект» - не всегда обязательная часть подобной стратегии. На практике самым популярным и самым успешным вариантом применения данного метода является работа без привлечения навороченных нейронных сетей или метода опорных векторов.
Принципы машинного обучения
В основе обучающего алгоритма заложена концепция шаблонов. Обычно это исторические данные о ценах. Каждый шаблон состоит из n переменных x1- xn, обычно называемых маркерами предсказания (предикторами) или просто параметрами. В качестве таких предикторов могут выступать ценовой возврат последних n-делений или набор классических индикаторов, а также любые другие функции кривой цен. Каждый шаблон включает целевую величину y - например, прибыль следующей сделки после применения шаблона или следующее движение цены. В процессе обучения алгоритм узнает, как получить целевую величину, основываясь на предикторах. Это знание хранится в структуре данных, именуемой в нашем случае моделью, индивидуальной для каждого алгоритма. Эта модель может быть функцией языка C, описывающей правила прогнозирования, выработанные в процессе обучения. Или это может быть набор соединений в нейронной сети.
Обучение: x1- xn, y => модель Предсказание: x1- xn, модель => y
Предикторы должны аккуратно обрабатывать информацию, которая необходима для предсказания целевой величины. Они обязаны отвечать двум формальным условиям: все значения этих маркеров должны быть одного порядка (например, -1- +1 для алгоритмов на R или -100- +100 для алгоритмов на языке Zorro). Это значит, что до отправки торговому «движку» их нужно нормализовать. Во-вторых, шаблоны должны быть сбалансированы, то есть равномерно распределены по всем значениям целевой величины. Шаблонов, описывающих выигрышный вариант, должно быть столько же, сколько и проигрышный.
Регрессивные алгоритмы предсказывают числовые значения, такие как величина следующего изменения цены. Классификационные алгоритмы генерируют класс качественных шаблонов. Например, связанных с прибылью или убытком. Ряд алгоритмов нейронных сетей или опорных векторов могут одновременно работать в обеих версиях. Некоторые алгоритмы не нуждаются в целевой величине для разделения шаблонов на классы. Это так называемое обучение без учителя (unsupervised learning), в отличие от обычного с учителем (supervised learning).
Какие бы сигналы мы не использовали в качестве маркеров предсказания в сфере финансов, большинство из них будет содержать много шума и мало полезной информации. Поэтому финансовое прогнозирование - самая сложная задача в машинном обучении. Более сложные алгоритмы не всегда дают более качественный результат. Для конечного успеха критичным является выбор предикторов. На этот случай в стратегии интеллектуального анализа предусмотрен алгоритм предварительного отбора, который выбирает несколько полезных маркеров предсказаний из множества вариантов. Этот отбор может проходить на основе их корреляции, значимости или просто берутся те, что прошли тест.
Далее мы поговорим о наиболее популярных методиках интеллектуального анализа, используемых в мире финансов.
Метод проб и ошибок
Большинство торговых систем, которые компания автора блога Financial Hacker разрабатывает для своих клиентов, изначально основаны не на финансовой модели. Заказчик хочет получать сигналы для совершения транзакций, опирающиеся на конкретные технические индикаторы, фильтруемые через индикаторы с использованием еще большего числа технических индикаторов. Обычно, никто толком не может ответить на вопрос о том, как это месиво индикаторов может являться рабочей стратегией. Ответ, как правило, такой: «Просто поверьте. Я так вручную торгую уже много лет, и все работает».
На самом деле, все так и есть. По крайней мере, в некоторых случаях. Хотя многие эти системы не были прошли форвардный анализ (некоторые - даже элементарный бэктест), большинство неплохо справляется со своими задачами. Клиент систематически экспериментирует с техническими индикаторами, пока не найдет нужную комбинацию, которая работает на реальном рынке с выбранными активами. Метод проб и ошибок - это классический вариант интеллектуального анализа. Просто он производится человеком, а не машиной. Иногда это дает хороший результат.
Свечные паттерны
Нет смысла останавливаться на разборе устаревших методик, типа японских свечных паттернов, которые были популярны 200 лет назад. Современный эквивалент свечных паттернов - это безиндикаторный анализ price action. В нем трейдеры все еще пытаются найти паттерн, предсказывающий движение цены. Но в данном случае они анализируют современные ценовые кривые. Для этой цели существует набор специальных программ. Они подбирают подходящие паттерны по заложенным пользователем критериям и используют их для построения функции. В системе Zorro это может выглядеть так:
Функция C делает возврат 1, когда сигнал соответствует одному из паттернов. В ином случае значение - 0. По этому коду можно видеть, что это не самый быстрый путь поиска паттернов. Альтернативный вариант - вначале сортировать сигналы по их значению, затем проверять порядок сортировки.
Даже несмотря на применение техник дата-майнинга такой ценовой трейдинг должен иметь под собой какие-то рациональные основания. Можно представить, что определенные последовательности движения цены приводят к определенной реакции участников рынка. Это и будет паттерном прогноза. Число паттернов всегда будет ограничено, если внимательно присмотреться к последовательности смежных свечей. Следующий шаг - сравнение свечей, которые находятся на расстоянии друг от друга. Мы выбираем их произвольно за достаточно долгий период времени. В этом случае число паттернов может быть безграничным. Но здесь легко потерять почву под ногами. В то же время, трудно представить, что движение цены может быть предсказано свечным паттерном недельной давности. Но в целом задача поиска свечных паттернов архисложная и чревата множеством ошибок.
Линейная регрессия
Смысл работы большинство сложных алгоритмов машинного обучения прост: нужно предсказать переменную целевую величину y через линейную комбинацию предикторов x1 - xn.
Коэффициент an рассчитывается для минимизации суммы квадратов различий между истинным значением y обучающего шаблона и предсказываемыми значениями y по следующей формуле:
Для нормального распределения шаблонов минимизация возможна через математическую матрицу, поэтому никакой итерации не требуется. В случае n = 1 с одной переменной предиктора x формула регрессии упрощается до:
Это является простой линейной регрессией. Она применяется на большинстве торговых платформ. Если y = цена, а x = время, то она часто используется как альтернатива скользящим средним. Есть еще полиноминальная регрессия, когда все еще есть один предиктор x, но есть и x2 и в более высокой степени. Таким образом, xn == xn.
Перцепция
Нередко этот метод рассматривают как нейронную сеть с одним нейроном. На самом деле перцепция - это та же функция регрессии, которую мы рассмотрели выше. Но с двоичным результатом. Поэтому ее еще называют логической регрессией. Хотя, по сути, это алгоритм классификации. Функция advise(PERCEPTRON, -) в Zorro генерирует C-код, делающий возврат 100 или -100, в зависимости от того, расположен ли предсказанный результат в рамках или за рамками установленного порога.
В этом фрагменте видно, что массив sig эквивалентен нашим маркерам предсказания xn в формуле регрессии, а числовые множители - это коэффициенты an.
Нейронные сети
Линейная или логическая регрессия может решить лишь линейные проблемы. Многие просто не работают с такой категорией вопросов. Искусственная нейронная сеть (ИНС) призвана решать нелинейные проблемы. Она представляет собой пучок перцептронов, соединенных в набор слоев. Каждый из них является нейроном сети. Вот как выглядят инпуты и аутпуты такой сети:
Нейронная сеть обучается через распознавание коэффициента, который бы минимизировал расхождение между шаблоном предсказания и шаблоном цели. Но теперь нам нужно задействовать еще и процесс аппроксимации. Обычно он применяется вместе с методом обратного распространения ошибки от входных данных к выходным, по пути оптимизируя нагрузку.
Этот процесс ставит два ограничения. Первый: нейронные аутпуты теперь должны быть непрерывно дифференцируемыми функциями, вместо того чтобы быть просто порогами перцепторов. Второе: сеть не должна быть чересчур глубокой, нужно избегать слишком большого числа скрытых слоев между инпутами и аутпутами. Это, конечно, ограничивает сложность проблем, которые простая нейронная сеть способна решать.
Если использовать нейронную сеть для торговли, то необходимо варьировать и изменять множество параметров. Неосторожность в обращении с ними может привести к тому, что будут искажены:
число скрытых слоев;
число нейронов для каждого скрытого слоя;
число циклов обратного распространения, или эпох;
скорость обучения, ширина шага одной эпохи;
моментум;
функция активации.
Функция активации имитирует порог перцептора. Для обратного распространения ошибки нужна непрерывно дифференцируемая функция, которая генерирует «мягкий» шаг на определенное значение x. Для этого обычно используются функции sigmoid, tanh, или softmax. В нашем примере функция может быть использована для регрессии и предсказания числовых значений вместо двоичного выхода.
Глубокое обучение
Если речь идет о множестве скрытых слоев и тысячах нейронов, то это уже глубокое обучение. Здесь стандартное обратное распространение не работает. В последние несколько лет появились несколько популярных методик обучения такой огромной системы. Обычно они включают этап пред-обучения скрытых слоев для достижения нужного эффекта. Один из вариантов - машина Больцмана - неконтролируемый классифицирующий алгоритм со специальной структурой сети, где отсутствуют соединения между скрытыми нейронами. Разреженный автокодировщик (Sparse Autoencoder) - другой вариант, он использует стандартную структуру сети и предобучает скрытые слои через воспроизводство сигналов инпута для аутпута слоев с минимальным, насколько это возможно, количеством активных соединений. Такие методы уже позволяют решать серьезные задачи. Ну, например, побеждать лучшего в мире игрока в го.
Ниже пример скрипта на R, использующий автокодировщик с тремя скрытыми слоями для определения сигналов трейдинга с помощью функции neural() пакета Zorro:
library('deepnet', quietly = T) library('caret', quietly = T) # called by Zorro for training neural.train = function(model,XY) { XY <- as.matrix(XY) X <- XY[,-ncol(XY)] # predictors Y <- XY[,ncol(XY)] # target Y <- ifelse(Y > 0,1,0) # convert -1..1 to 0..1 Models[[model]] <<- sae.dnn.train(X,Y, hidden = c(20,20,20), activationfun = "tanh", learningrate = 0.5, momentum = 0.5, learningrate_scale = 1.0, output = "sigm", sae_output = "linear", numepochs = 100, batchsize = 100, hidden_dropout = 0, visible_dropout = 0) } # called by Zorro for prediction neural.predict = function(model,X) { if(is.vector(X)) X <- t(X) # transpose horizontal vector return(nn.predict(Models[[model]],X)) } # called by Zorro for saving the models neural.save = function(name) { save(Models,file=name) # save trained models } # called by Zorro for initialization neural.init = function() { set.seed(365) Models <<- vector("list") } # quick OOS test for experimenting with the settings Test = function() { neural.init() XY <<- read.csv('C:/Project/Zorro/Data/signals0.csv',header = F) splits <- nrow(XY)*0.8 XY.tr <<- head(XY,splits) # training set XY.ts <<- tail(XY,-splits) # test set neural.train(1,XY.tr) X <<- XY.ts[,-ncol(XY.ts)] Y <<- XY.ts[,ncol(XY.ts)] Y.ob <<- ifelse(Y > 0,1,0) Y <<- neural.predict(1,X) Y.pr <<- ifelse(Y > 0.5,1,0) confusionMatrix(Y.pr,Y.ob) # display prediction accuracy }
Метод опорных векторов
Также как и нейронная сеть, метод опорных векторов - это расширенный вариант линейной регрессии. Взглянем на эту формулу еще раз:
Маркеры предсказания xn можно рассматривать как координаты пространства с n измерениями. Привязав целевую величину y к фиксированному значению, мы определим плоскость или, как ее еще называют - гиперплоскость. Она отделяет шаблоны с y > 0 от шаблонов с y < 0. Коэффициент an может быть рассчитан как максимальное расстояние от плоскости к ближайшему шаблону, называемому опорным вектором. Таким образом, мы получаем бинарный классификатор с оптимальным делением шаблонов на выигрышные и проигрышные.
Есть небольшая проблема: обычно эти шаблоны нельзя отделить линейно, они рассредоточены в нашем пространстве маркеров нерегулярно. Мы не можем поместить в него ровную плоскость. Если бы и могли, то есть более простой способ определить плоскость - линейный дискриминантный анализ. Но в большинстве случаем мы вынуждены воспользоваться уловкой, которую предоставляет метод опорных векторов: добавить больше измерений в наше пространство. После этого алгоритм производит больше маркеров с помощью функции ядра, которая объединяет два любых предиктора в новый маркер. Чем больше измерений будет добавлено, тем легче разделить шаблоны с помощью гиперплоскости. Затем эта плоскость трансформируется обратно в исходное пространство с n измерениями, обрастая по пути складками. Для того чтобы функция ядра не сбивалась и работала оптимально, процесс должен выполняться без фактического вычисления параметров такого преобразования.
Метод опорных векторов может быть использован не только для классификации, но и для регрессии. Он также позволяет оптимизировать процесс предсказания через следующие параметры:
функция ядра: обычно используют радиальную базисную функцию ядра, но у вас есть выбор, можно использовать полиномиальный, сигмоидный или линейный вариант;
гамма, ширина захвата радиальной базисной функции ядра;
параметр стоимости C, «штраф» за неверную классификацию шаблонов в процессе обучения.
Метод k ближайших соседей
В сравнении с опорными векторами, это довольно простой алгоритм с кучей уникальных возможностей. Он не требует обучения. Поэтому шаблоны в данном случае - это модель. В трейдинговых системах он позволяет обучаться непрерывно через добавление большего количества шаблонов. Метод ближайших соседей производит расчет расстояния от текущих значений маркеров к ближайшим k-шаблонам. В пространстве с n измерениями это расстояние рассчитывается по двум измерениям:
Алгоритм просто предсказывает целевую величину по среднему значению целевых переменных k ближайшего шаблона, распределенных по их обратным расстояниям. Его можно использовать как для классификации, так и для регрессии. Заимствованные из графических редакторов штуки (например, адаптивное двоичное дерево) помогут отыскать ближайшего соседа довольно быстро. Раньше такие вещи часто использовали в программирование игрушек, когда нужно было запустить самообучение интеллекта врага. Можно использовать для наших целей функцию knn в R или написать свою на C.
Метод k-средних
Метод k-средних использует алгоритм аппроксимации для неконтролируемой классификации. Он в чем-то очень похож на предыдущий метод. Для разделения шаблонов алгоритм первым делом размещает случайные отметки k в пространстве маркеров. Затем этим точкам назначаются все ближайшие к ним шаблоны. Следующий шаг: эти точки перемещаются к среднему значению ближайших шаблонов. И мы получаем новое распределение шаблонов, теперь определенные шаблоны становятся ближе к другим отметкам. Процесс повторяется, пока распределение не станет неизменным. То есть каждая отметка будет располагаться в точности по среднему значению ближайших шаблонов. Следовательно, у нас в наличие класс k-шаблонов, каждый из которых в непосредственной близости от одной из k-точек. Алгоритм простой, но он может привести к неожиданно хорошим результатам.
Наивный байесовский классификатор
Следующий алгоритм использует теорему Байеса для классификации шаблонов по нечисловым признакам (событиям), так же, как уже обговоренный метод свечных паттернов. Предположим, у нас есть событие X, проявляющее себя в 80% случаях удачных шаблонов. Что можно из этого извлечь? Подсчитать вероятность выигрыша варианта, содержащего X. Она не будет равна 0,8, как это можно было бы предположить. Действие этой вероятности может быть рассчитано по байесовской теореме:
P(Y|X) - это вероятность того, что событие Y (выигрыш) появляется во всех шаблонах, содержащих событие X. Она будет равна вероятности появления X во всех выигрышных шаблонах (то есть 0,8) умноженной на вероятность проявления Y во всех шаблонах (в нашем случае 0,5, если вы внимательно прочли советы по балансировке шаблонов) и разделенной на вероятность наличия X во всех имеющихся шаблонах.
Если мы в меру наивны и предполагаем, что все события X независимы друг от друга, мы можем подсчитать общую вероятность того, что определенный шаблон будет выигрышным, по следующей формуле, используя коэффициент масштабирования s:
Для того чтобы формула сработала, нужно разделить маркеры предсказаний так, чтобы они были максимально независимы друг от друга. А это и есть самое главное препятствие для использования теоремы Байеса в трейдинге. В большинстве случаев два события будут, так или иначе, зависеть друг от друга.
Метод байесовского классификатора доступен в пакете e1071 для языка R.
Дерево принятия решения и дерево регрессии
Оба дерева нацелены на предсказание числовых значений на выходе, основываясь на серии решений да/нет. Ответ в каждом случае зависит от наличия или отсутствия события (в варианте нечисловых признаков) или от сравнения значений маркеров предсказания с фиксированным порогом. Стандартная функция дерева на Zorro будет выглядеть так:
Как это дерево возникает из набора шаблонов? Есть несколько методов. Zorro предпочитает метод информационной шенноновской энтропии. Для начала он проверяет один из маркеров, допустим, x1. Он устанавливает гиперплоскость по формуле x1 = t . Эта плоскость отделяет шаблоны со значением x1 > t от шаблонов со значением x1 < t. Разделительный порог t является выборочным, поэтому информационный прирост (отношение информационной энтропии всего пространства к сумме информационных энтропий двух разделенных субпространств) будет максимальным. Это тот случай, когда шаблоны в субпространствах более схожи друг с другом, чем шаблоны в целом пространстве.
Затем процесс запускается для следующего маркера x2, тогда уже две гиперплоскости расщепляют два субпространства. В каждом случае условием для этого является сравнение маркера с установленным порогом. В скором времени мы получаем разветвленное дерево с тысячами сравнений. Следом процесс запускается в обратном направлении, нам нужно подрезать дерево, удалив все решения, которые не ведут к существенному информационному приросту. Наконец, получается относительно небольшое дерево, как в примере кода выше.
Дерево решений может быть применено по-разному. Но его нельзя использовать в качестве решения всех проблем, так как его плоскости деления всегда параллельны осям пространства маркеров. Это ограничивает возможности делать точные предсказания. Для регрессии его также можно использовать. Например, для определения доли шаблонов, привязанных к определенной ветке дерева. Дерево Zorro - это дерево регрессии. Самый распространенный алгоритма классификации по методу дерева - C5.0, доступный в пакете C50 для R.
Заключение
В распоряжении трейдеров сегодня есть множество различных методов интеллектуального анализа. Но что эффективней: ориентированные на определенную модель стратегии или стратегии машинного обучения? Несомненно, у последних есть масса преимуществ. Не нужно беспокоиться по поводу микроструктуры рынка, психологии трейдеров и прочей не выражаемой в числах чепухе. Можно сконцентрироваться на чистой математике. Машинное обучение выглядит более изысканным вариантом, более привлекательным способом создания трейдинговой системы. Все говорит в его пользу. Кроме одной вещи: несмотря на восторженные отзывы на форумах, в живом трейдинге все это оказывается странным образом неэффективным.
Каждую неделю в специализированных изданиях появляется новая статья о методах машинного обучения. К выводам этих статей стоит относиться с долей скепсиса. Многие из них обещают фантастический уровень отдачи в 70-85% прибыли. Если бы все это было правдой, количество миллиардеров среди математиков бы уже зашкаливало. В реальности удачных стратегий, основанных на машинном обучении до обидного мало.