В
настоящее время более 60 процентов торговых операций с различными
активами (такими как акции, фьючерсы на индексы, товары) больше не
производятся трейдерами - людьми, а выполняются роботами. Существуют
специализированные программы, основанные на конкретных алгоритмах,
которые автоматически покупают и продают активы на разных рынках, что
обеспечивает достижение положительного результата в долгосрочной
перспективе.
В
этой статье я покажу вам, как с хорошей точностью прогнозировать, как
должна быть выполнена следующая сделка, чтобы получить положительный
результат. Для этого примера, в качестве основного актива для торговли, я
выбрал индекс S&P 500, средневзвешенный по 500 американским
компаниям с наибольшей капитализацией. Очень простая для реализации стратегия - купить индекс S&P 500, когда биржа Wall Street Exchange
начнет торговлю в 9:30 утра, и продать его на закрытии сессии в 16:00
по восточному времени. Если цена закрытия индекса выше цены открытия,
будет положительная прибыль, если цена закрытия будет ниже, чем цена
открытия, мы получим убыток. Поэтому возникает вопрос: как мы узнаем,
закончится ли торговая сессия с ценой закрытия выше цены открытия?
Машинное обучение - мощный инструмент для решения такой сложной задачи, и оно может быть полезным инструментом для выработки торгового решения.
Машинное
обучение - новый рубеж многих полезных приложений для реальной жизни.
Финансовый трейдинг - одно из них, и оно используется очень часто в этом
секторе. Важная концепция машинного обучения заключается в том, что нам
не нужно писать код для всех возможных правил, таких как распознавание
образов. Это связано с тем, что каждая модель, связанная с машинным
обучением, учится на самих данных, а затем впоследствии может
использоваться для прогнозирования новых данных.
Отказ
от ответственности: Цель этой статьи - показать, как применять методы
машинного обучения, а в приведенных примерах кода не объясняются все
функции. Эта статья не предназначена для того, чтобы копировать и
вставлять весь код и запускать те же тесты вне области действия статьи.
Кроме того, необходимы базовые знания Python.
Основная цель статьи - показать пример того, как машинное обучение
может быть эффективным для прогнозирования покупок и продаж в финансовом
секторе. Однако торговля с реальными деньгами означает наличие многих
других навыков, таких как управление деньгами и управление рисками. Эта
статья - всего лишь небольшая часть «большой картины».
Создание вашей первой автоматизированной торговой программы
Итак,
вы хотите создать свою первую программу для анализа финансовых данных и
прогнозирования правильной торговли? Позвольте мне показать вам, как
это сделать. Я буду использовать для машинного обучения код Python, и мы будем использовать исторические данные из Yahoo Finance. Как уже упоминалось ранее, исторические данные необходимы для подготовки модели до создания наших прогнозов.
Для начала нам нужно установить:
- Python, в частности я предлагаю использовать IPython notebook.
- пакет Yahoo Finance Python (точное имя yahoo-finance), устанавливается с помощью команды: pip install yahoo-finance.
- Бесплатная пробная версия пакета машинного обучения под названием GraphLab. Не стесняйтесь почитать полезную документацию этой библиотеки.
Обратите внимание, что только часть GraphLab под названием SFrame
является открытым исходным кодом, поэтому для использования всей
библиотеки нам нужна лицензия. Существует 30-дневная бесплатная лицензия
и некоммерческая лицензия для студентов или тех, кто участвует в
соревнованиях Kaggle. С моей точки зрения, GraphLab Create - это очень интуитивная и простая в использовании библиотека для анализа данных и обучения моделей машинного обучения.
Углубляемся в код Python
Давайте разберем код Python, чтобы узнать, как загружать финансовые данные из интернета. Я предлагаю использовать IPython notebook для тестирования кода, потому что у IPython
есть много преимуществ по сравнению с традиционной IDE, особенно когда
нам нужно объединить исходный код, табличные данные и диаграммы в одном
документе.
Итак, давайте создадим новый блокнот IPython и напишем код для загрузки исторических цен индекса S&P 500. Обратите внимание, что если вы предпочитаете использовать другие инструменты, вы можете начать с нового проекта Python в своей любимой среде IDE.
import graphlab as gl
from __future__ import division
from datetime import datetime
from yahoo_finance import Share
# загружаем исторические котировки индекса S&P 500
today = datetime.strftime(datetime.today(), "%Y-%m-%d")
stock = Share('^GSPC') # ^GSPC - это символYahoo finance для ссылки на индекс S&P 500
# мы скачиваем котировки с 2001-01-01 по сегодняшний день
hist_quotes = stock.get_historical('2001-01-01', today)
# вот как они выглядят
hist_quotes[0]
{'Adj_Close': '2091.580078',
'Close': '2091.580078',
'Date': '2016-04-22',
'High': '2094.320068',
'Low': '2081.199951',
'Open': '2091.48999',
'Symbol': '%5eGSPC',
'Volume': '3790580000'}
Здесь hist_quotes представляет собой список словарей, и каждый объект словаря является торговым днем со значениями Open, High, Low, Close, Adj_close, Volume, Symbol и Date. В течение каждого торгового дня цена изменяется начиная с цены открытия Open до цены закрытия Close и достигает максимального и минимального значений High и Low. Нам нужно прочитать и и
создать списки каждой из наиболее релевантных данных. Кроме того,
сначала данные должны быть упорядочены от самых последних значений,
поэтому нам нужно реверсировать их:
l_date = []
l_open = []
l_high = []
l_low = []
l_close = []
l_volume = []
# реверсируем список
hist_quotes.reverse()
for quotes in hist_quotes:
l_date.append(quotes['Date'])
l_open.append(float(quotes['Open']))
l_high.append(float(quotes['High']))
l_low.append(float(quotes['Low']))
l_close.append(float(quotes['Close']))
l_volume.append(int(quotes['Volume']))
Мы можем упаковать все загруженные котировки в объект SFrame, который представляет собой масштабируемый data frame.
Одно из его преимуществ заключается в том, что он может быть больше,
чем объем оперативной памяти, поскольку он хранится на диске. Вы можете
посмотреть документацию, чтобы узнать больше о SFrame.
Итак, давайте сохраним, а затем проверим исторические данные:
qq = gl.SFrame({'datetime' : l_date,
'open' : l_open,
'high' : l_high,
'low' : l_low,
'close' : l_close,
'volume' : l_volume})
# datetime - строка, поэтому давайте конвертируем ее в объект datetime
qq['datetime'] = qq['datetime'].apply(lambda x:datetime.strptime(x, '%Y-%m-%d'))
# просто проверьте, отсортированы ли данные по восходящей (дате)
qq.head(3)
close
|
datetime
|
high
|
low
|
open
|
volume
|
1283.27
|
2001-01-02 00:00:00
|
1320.28
|
1276.05
|
1320.28
|
1129400000
|
1347.56
|
2001-01-03 00:00:00
|
1347.76
|
1274.62
|
1283.27
|
1880700000
|
1333.34
|
2001-01-04 00:00:00
|
1350.24
|
1329.14
|
1347.56
|
2131000000
|
Теперь мы можем сохранить данные на диск с помощью метода save класса SFrame следующим образом:
qq.save(“SP500_daily.bin”)
# после сохранения данных мы можем использовать следующую команду для их извлечения
qq = gl.SFrame(“SP500_daily.bin/”)
Посмотрим, как выглядит S&P 500
Чтобы увидеть, как будут выглядеть загруженные данные S&P 500, мы используем следующий код:
import matplotlib.pyplot as plt
%matplotlib inline # только для тех, кто использует IPython notebook
plt.plot(qq['close'])
В результате мы увидим следующий график:
Обучение некоторых моделей машинного обучения
Добавление прибылей
Как
я уже говорил во вводной части этой статьи, целью модели является
предсказать, будет ли цена закрытия выше цены открытия. Следовательно, в
этом случае мы можем добиться положительной отдачи при покупке базового
актива. Итак, нам нужно добавить столбец результатов, который будет
целевой или прогнозируемой переменной. Каждая строка этого нового
столбца будет иметь значение:
+1 для дня с ценой закрытия выше цены открытия.
-1 для дня с ценой закрытия ниже цены открытия.
# добавляем переменную outcome, 1 если сессия была положительной (close>open), 0 в противном случае
qq['outcome'] = qq.apply(lambda x: 1 if x['close'] > x['open'] else -1)
# нам также нужно добавить три новых столбца: ‘ho’ ‘lo’ и ‘gain’
# они нам понадобятся позже для тестирования модели
qq['ho'] = qq['high'] - qq['open'] # расстояние между максимумом и ценой открытия
qq['lo'] = qq['low'] - qq['open'] # расстояние между минимумом и ценой открытия
qq['gain'] = qq['close'] - qq['open']
Поскольку
нам нужно оценить несколько дней до последнего торгового дня, нам нужно
добавить запаздывание данных на один или несколько дней. Для такой
операции нам нужен еще один объект из пакета GraphLab под названием
TimeSeries. TimeSeries имеет метод shift, который сдвигает данные на
определенное количество строк.
ts = gl.TimeSeries(qq, index='datetime')
# добавляем переменную outcome, 1 если бар был положительным (close>open), 0 в противном случае
ts['outcome'] = ts.apply(lambda x: 1 if x['close'] > x['open'] else -1)
# Генерируем некоторые запаздывающие временные ряды
ts_1 = ts.shift(1) # на 1 день
ts_2 = ts.shift(2) # на 2 дня
# ...etc....
# это произвольное решение о том, сколько дней задержки требуется для создания хорошего прогноза, поэтому
# каждый может экспериментировать по собственному желанию
Добавление предикторов
Предикторы
представляют собой набор функциональных переменных, которые должны быть
выбраны для обучения модели и предсказания нашего результата. Таким
образом, выбор предиктора имеет решающее значение, это может быть самый
важный компонент прогноза.
В
качестве примера, можно учитывать, было ли сегодняшнее закрытие выше,
чем вчерашнее закрытие, и это может быть расширено на два предыдущих дня
и т. д. Аналогичный выбор можно представить следующим кодом:
ts['feat1'] = ts['close'] > ts_1['close']
ts['feat2'] = ts['close'] > ts_2['close']
Как
показано выше, я добавил два новых столбца, feat1 и feat2 в наш набор
данных (ts), содержащие 1, если результат сравнения истина и 0 в
противном случае.
Эта
статья призвана привести пример машинного обучения, применяемого к
финансовому сектору. Я предпочитаю сосредоточиться на том, как модели
машинного обучения могут использоваться с финансовыми данными, и мы не
будем вдаваться в подробности относительно того, как выбирать правильные
параметры для обучения моделей. Это слишком глубокий вопрос, чтобы
объяснить в рамках данной статьи, почему используются именно эти
параметры. Цель данной работы - изучить различные гипотезы о выборе
парметров для создания хорошего предиктора. Поэтому для начала я
предлагаю вам поэкспериментировать с множеством различных комбинаций
параметров, чтобы увидеть, могут ли они повысить точность модели.
# add_features - вспомогательная функция, описание которой выходит за рамки данной статьи,
# и она возвращает кортеж (tuple) с:
# ts: объект timeseries содержащий, помимо уже включенных столбцов, столбцы с запаздыванием
# а также некоторые признаки, добавленные для обучения модели, как показано выше, с примерами feat1 и feat2
# l_features: список всех признаков, используемых для обучения моделей классификации
# l_lr_features: список всех моделей, используемых для обучения моделей линейной регрессии
ts, l_features, l_lr_features = add_features(ts)
# добавляем столбец gain column, для торговых операций только с длинными позициями.
# gain - это разность Closing price - Opening price
ts['gain'] = ts['close'] - ts['open']
ratio = 0.8 # 80% - обучающая выборка и 20% - тестовая выборка
training = ts.to_sframe()[0:round(len(ts)*ratio)]
testing = ts.to_sframe()[round(len(ts)*ratio):]
Создание вашей первой автоматизированной торговой программы
Обучение модели дерева решений (Decision Tree)
GraphLab
Create имеет очень приятный интерфейс для реализации моделей машинного
обучения. Каждая модель имеет метод create, используемый для
сопоставления модели с набором обучающих данных. Типичными параметрами
являются:
training - это обучающий набор данных, содержащий столбцы признаков и целевой столбец.
target - это имя столбца, содержащего целевую переменную.
validation_set - это набор данных для мониторинга эффективности модели. В нашем случае у нас нет validation_set.
features - это список имен столбцов признаков, используемых для обучения модели.
verbose - если он равен true, выводится информация о ходе обучения в режиме реального времени.
В то время как другие параметры типичны для самой модели, например:
max_depth - - максимальная глубина дерева.
С помощью следующего кода мы строим дерево решений:
max_tree_depth = 6
decision_tree = gl.decision_tree_classifier.create(training, validation_set=None,
target='outcome', features=l_features,
max_depth=max_tree_depth, verbose=False)
Оценка эффективности подогнанной модели
Точность
является важной метрикой для оценки качества прогноза. Это число
правильных предсказаний, деленное на общее количество точек данных.
Поскольку модель подгоняется под обучающие данные, точность, которую
оценивают с помощью обучающей выборки, лучше, чем полученная с тестовой
выборкой.
Точность
- это доля положительных предсказаний, которые являются положительными
на самом деле. Нам нужна точность как можно ближе к 1, чтобы достичь
«идеального» винрейта. Наш decision_tree, как и другие классификаторы из
пакета GraphLab Create, имеет свой метод evaluate для оценки многих
важных показателей модели.
Точность
количественно оценивает способность классификатора прогнозировать
положительные примеры. Точность можно интерпретировать как вероятность
того, что случайный выбранный положительный пример правильно
идентифицируется классификатором. Нам нужна точность, которая будет как
можно ближе к 1, чтобы достичь «идеального» винрейта.
Следующий код покажет точность созданной модели как с обучающей, так и с тестовой выборкой.
decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy']
(0.6077348066298343, 0.577373211963589)
Как
было показано выше, точность модели на обучающей выборке составляет
около 57 процентов, что немного лучше, чем бросание монеты (50
процентов).
Прогнозирование данных
GraphLab
Create предлагает тот же интерфейс для прогнозирования данных с помощью
разных моделей. Мы будем использовать метод прогнозирования, которому
нужен обучающий набор для прогнозирования целевой переменной, в нашем
случае +/-. Теперь мы можем прогнозировать данные из обучающего набора:
predictions = decision_tree.predict(testing)
# и мы добавляем столбец предикторов в тестовую выборку
testing['predictions'] = predictions
# давайте просмотрим первые 10 предсказаний в сравнении с реальными значениями (столбец outcome)
testing[['datetime', 'outcome', 'predictions']].head(10)
datetime
|
outcome
|
predictions
|
2013-04-05 00:00:00
|
-1
|
-1
|
2013-04-08 00:00:00
|
1
|
1
|
2013-04-09 00:00:00
|
1
|
1
|
2013-04-10 00:00:00
|
1
|
-1
|
2013-04-11 00:00:00
|
1
|
-1
|
2013-04-12 00:00:00
|
-1
|
-1
|
2013-04-15 00:00:00
|
-1
|
1
|
2013-04-16 00:00:00
|
1
|
1
|
2013-04-17 00:00:00
|
-1
|
-1
|
2013-04-18 00:00:00
|
-1
|
1
|
False positive
- это случаи, когда модель предсказывает положительный результат, в то
время как реальный результат из тестовой выборки отрицательный. И
наоборот, False negative - это случаи, когда модель предсказывает
отрицательный результат, когда реальный результат положительный.
Наша
торговая стратегия ждет положительного прогнозируемого результата,
чтобы купить S&P 500 по цене открытия и продать по цене закрытия,
поэтому мы надеемся, что у вас будет минимальное количество случаев
False positive, чтобы избежать потерь. Другими словами, мы ожидаем, что
наша модель будет иметь максимальную точность.
Как
мы видим, есть два false negative (2013-04-10 и 2013-04-11) и два false
positive (2013-04-15 и 2013-04-18) в течение первых десяти
предсказанных значений тестовой выборки.
Делаем простой расчет на базе этих десяти прогнозов:
accuracy = 6/10 = 0.6 or 60%
precision =3/5 = 0.6 or 60%
recall = 3/5 = 0.6 or 60%
Обратите внимание, что обычно эти числа отличаются друг от друга, но в этом случае они одинаковы.
Тестирование модели
Теперь
мы посмотрим, как модель будет торговать с использованием прогнозных
значений. Если предсказанный результат равен +1, это означает, что мы
ожидаем день с ростом цены. Мы покупаем индекс в начале сессии и продаем
в конце сессии в течение того же дня. И наоборот, если предсказанный
результат равен -1, мы ожидаем день с падением цены, поэтому в этот день
мы не будем торговать.
Прибыль и убыток (pnl) для целого дня рассчитывается следующим образом:
pnl = Close - Open (для каждого торгового дня)
В
приведенном ниже коде я вызываю вспомогательную функцию
plot_equity_chart для создания диаграммы с кривой кумулятивной прибыли
(кривой equity). Она просто получает серию значений прибыли и убытков и
рассчитывает ряд кумулятивных сумм для графика.
pnl = testing[testing['predictions'] == 1]['gain'] # the gain column contains (Close - Open) values
# Я написал простую вспомогательную функцию для построения графика результата всех сделок, #примененных к
# тестовой выборке и представляющую общий доход, выраженный базовыми пунктами индекса
# (не в долларах $)
plot_equity_chart(pnl,'Decision tree model')
Mean of PnL is 1.843504
Sharpe is 1.972835
Round turns 511
Здесь Sharpe является годовым коэффициентом Шарпа, важным показателем качества торговой модели.
mean
- это среднее по списку прибылей и убытков, а sd - стандартное
отклонение. Для простоты в приведенной выше формуле я принял , что
безрисковый доход равен 0.
Некоторые основы трейдинга
Торговля
индексом требует покупки актива, который непосредственно получен из
индекса. Многие брокеры копируют индекс S&P 500 с помощью
производного продукта под названием CFD (Contract for difference),
который является соглашением между двумя сторонами об обмене разницей
между ценой открытия и ценой закрытия контракта.
Пример:
купите 1 CFD S&P 500 при открытии (значение 2000), продайте его при
закрытии дня (значение - 2020). Разница составляет 20 пунктов. Если
один пункт стоит $25:
20 пунктов x $25 = $500 за 1 контракт CFD.
Предположим, что брокер удерживает 0,6 пункта в качестве комиссии:
Чистая прибыль равна (20 - 0,6) пункта x $25 = $485.
Еще
один важный аспект, который необходимо учитывать, чтобы избежать
значительных потерь в торговле. Они могут возникать всякий раз, когда
предсказанный результат равен +1, но реальное значение результата равно
-1, то есть false positive. В этом случае цена закрытия ниже открытия, и
мы получаем убыток.
Должен
быть поставлен стоп-лосс, чтобы защитить себя от максимального убытка, и
такой ордер срабатывает всякий раз, когда цена актива падает ниже
фиксированного значения, которое мы установили раньше.
Если
мы посмотрим на временные ряды, загруженные с Yahoo Finance в начале
этой статьи, каждый день имеет цену Low, которая является самой низкой
ценой, достигнутой в этот день. Если мы установим стоп-лосс на -3 пункта
от цены открытия, а Low - Open = -5, стоп-ордер сработает, и открытая
позиция будет закрыта с потерей -3 пунктов вместо -5. Это простой метод
снижения риска. Следующий код представляет мою вспомогательную функцию
для имитации торговли со стопом:
# Это вспомогательная функция для торговли 1 баром (например, 1 день) с ордером Buy на открытии сессии
# и Sell на закрытии сессии. Чтобы защитить от неблагоприятных движений цены, ордер STOP
# будет ограничивать потерю до уровня стопа (параметр stop должен быть отрицательным числом)
# каждая строка должна содержать следующие атрибуты:
# Open, High, Low, Close, а также gain = Close - Open и lo = Low - Open
def trade_with_stop(bar, slippage = 0, stop=None):
"""
Для бара, с прибылью, полученной по разности цена закрытия - цена открытия
она применяет стоп-лимит, чтобы ограничить потери
Если стоп равен None, она возвращает bar ['gain']
"""
bar['gain'] = bar['gain'] - slippage
if stop<>None:
real_stop = stop - slippage
if bar['lo']<=stop:
return real_stop
# stop == None
return bar['gain']
Расходы на торговлю
Транзакционные
издержки - это расходы, понесенные при покупке или продаже ценных
бумаг. Затраты на транзакцию включают комиссионные и спрэды брокеров
(разница между ценой, которую заплатил дилер за ценную бумагу, и ценой,
которую платит покупатель), и их нужно учитывать, если мы хотим увидеть
реальный сценарий. Проскальзывание в торговле акциями часто происходит
при изменении спреда. В этом примере и для следующих моднлирований
торговые затраты фиксируются:
Проскальзывание = 0.6 пункта
Комиссия = 1$ за каждую сделку (в круговую 2$ - покупка + продажа)
Для
примера, если наша прибыть составила 10 пунктов, 1 пункт = $25, то
наша чистая прибыль будет равна (10 - 0.6)*$25 - 2 = $233.
Приведенный
ниже код показывает моделирование предыдущей торговой стратегии со
стоп-лоссом -3 пункта. Синяя кривая - это кривая совокупной прибыли.
Единственными затратами являются проскальзывание (0,6 пункта), и
результат выражается в базовых пунктах (тот же базовый блок значений
S&P 500, загруженный из Yahoo Finance).
SLIPPAGE = 0.6
STOP = -3
trades = testing[testing['predictions'] == 1][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]
trades['pnl'] = trades.apply(lambda x: trade_with_stop(x, slippage=SLIPPAGE, stop=STOP))
plot_equity_chart(trades['pnl'],'Decision tree model')
print("Slippage is %s, STOP level at %s" % (SLIPPAGE, STOP))
Mean of PnL is 2.162171
Sharpe is 3.502897
Round turns 511
Slippage is 0.6
STOP level at -3
Следующий
код используется, чтобы делать прогнозы несколько иначе. Обратите
внимание на метод прогнозирования, который вызывается с дополнительным
параметром output_type = “probability”. Этот параметр используется для
возврата вероятностей предсказанных значений вместо предсказания класса
(+1 для положительного прогнозируемого результата, -1 для отрицательного
предсказанного результата). Вероятность, превышающая или равная 0,5,
связана с прогнозируемым значением +1, а значение вероятности менее 0,5
связано с прогнозируемым значением -1. Чем выше вероятность, тем больше
шансов, что мы точно предскажем день с ростом цены.
predictions_prob = decision_tree.predict(testing, output_type = 'probability')
# predictions_prob будет включать вероятности вместо предсказаний класса (-1 или +1)
Теперь
мы проверяем модель с помощью вспомогательной функции
backtest_ml_model, которая вычисляет ряд кумулятивных доходностей,
включая проскальзывание и комиссионные, и строит график их значений. Для
краткости, не расписывая полностью функцию backtest_ml_model, важно
подчеркнуть, что вместо фильтрации дней с прогнозируемым результатом =
1, как это было в предыдущем примере, теперь мы фильтруем значения
predictions_probb, равные или превышающие порог = 0,5, следующим
образом:
trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]
Помните, что чистая прибыль за каждый торговый день: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION.
Другим
важным показателем, используемым для оценки эффективности торговой
стратегии, является максимальная просадка. В целом, она измеряет
наибольшую единичную просадку в сравнении со стоимостью инвестированного
портфеля. В нашем случае это самый значительный спад от максимума до
нижней части кривой собственного капитала (у нас есть только один актив в
нашем портфеле, S&P 500). Таким образом, для данного SArray
прибылей и убытков pnl, мы вычисляем просадку как:
drawdown = pnl - pnl.cumulative_max()
max_drawdown = min(drawdown)
Внутри вспомогательной функции backtest_summary вычисляются значения:
- максимальная просадка (в долларах), как показано выше;
- правильность (Accuracy), с помощью метода Graphlab.evaluation;
- точность (Precision), с помощью метода Graphlab.evaluation;
- Recall, с помощью метода Graphlab.evaluation.
Объединив
все это, в следующем примере показана кривая собственного капитала,
отражающая совокупную доходность модельной стратегии, при этом все
значения выражены в долларах.
model = decision_tree
predictions_prob = model.predict(testing, output_type="probability")
THRESHOLD = 0.5
bt_1_1 = backtest_ml_model(testing, predictions_prob, target='outcome',
threshold=THRESHOLD, STOP=-3,
MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree')
backtest_summary(bt_1_1)
Mean of PnL is 54.054286
Sharpe is 3.502897
Round turns 511
Name: DecisionTree
Accuracy: 0.577373211964
Precision: 0.587084148728
Recall: 0.724637681159
Max Drawdown: -1769.00025
Чтобы
повысить точность прогнозируемых значений, вместо стандартной
вероятности 0,5 (50 процентов) мы выбираем более высокое пороговое
значение, чтобы быть более уверенным, что модель прогнозирует день с
ростом цены.
THRESHOLD = 0.55
bt_1_2 = backtest_ml_model(testing, predictions_prob, target='outcome',
threshold=THRESHOLD, STOP=-3,
MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree')
backtest_summary(bt_1_2)
Mean of PnL is 118.244689
Sharpe is 6.523478
Round turns 234
Name: DecisionTree
Accuracy: 0.560468140442
Precision: 0.662393162393
Recall: 0.374396135266
Max Drawdown: -1769.00025
Как
видно из диаграммы, кривая equity намного лучше, чем раньше
(коэффициент Шарпа - 6.5 вместо 3.5), даже с меньшим количеством сделок.
С этого момента мы будем рассматривать все модели с порогом, превышающим стандартное значение.
Обучение логистического классификатора
Мы
можем применить наше исследование, как это было ранее с деревом
решений, в модели логистического классификатора. GraphLab Create имеет
тот же интерфейс для объекта Logistic Classifier, и мы будем вызывать
метод create для построения нашей модели с тем же списком параметров.
Более того, мы предпочитаем предсказывать вектор вероятности вместо
вектора класса (состоящего из +1 для положительного результата и -1 для
отрицательного результата), поэтому у нас будет использоваться порог
более 0,5 для достижения лучшей точности прогнозирования.
model = gl.logistic_classifier.create(training, target='outcome', features=l_features,validation_set=None, verbose=False)
predictions_prob = model.predict(testing, 'probability')
THRESHOLD = 0.6
bt_2_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name())
backtest_summary(bt_2_2)
Mean of PnL is 112.704215
Sharpe is 6.447859
Round turns 426
Name: LogisticClassifier
Accuracy: 0.638491547464
Precision: 0.659624413146
Recall: 0.678743961353
Max Drawdown: -1769.00025
В
этом случае результат очень похож на Decision Tree. В конце концов, обе
модели являются классификаторами, они предсказывают только класс
бинарных результатов (+1, -1).
Обучение модели линейной регрессии
Основное
отличие этой модели заключается в том, что она работает с непрерывными
значениями вместо двоичных классов. Нам не нужно обучать модель с
целевой переменной, равной +1 для дней с ростом цены и -1 для дней с
падением цены, наша цель должна быть непрерывной переменной. Поскольку
мы хотим предсказать положительный выигрыш, или, другими словами, цену
закрытия выше цены открытия, в настоящее время целью должна быть столбец
gain нашего учебного набора. Кроме того, список признаков должен
состоять из непрерывных значений, таких как предыдущие Open, Close и т.
д.
Для
краткости я не буду вдаваться в подробности того, как выбирать
правильные функции, поскольку это выходит за рамки этой статьи, я хочу
показать, как мы должны применять разные модели машинного обучения на
наборе данных. Список параметров, переданных методу create:
training - это обучающая выборка, содержащая столбцы признаков и целевой столбец.
target - имя столбца, содержащего целевую переменную.
validation_set - это набор данных для мониторинга эффективности обобщения модели. В нашем случае у нас нет validation_set.
features
- это список имен столбцов функций, используемых для обучения модели,
для этой модели мы будем использовать другой набор, отличающийся от
использовавшегося для классификаторов.
verbose - если эта переменная равна true, будет выводиться информация о ходе обучения.
max_iterations
- максимальное количество разрешенных проходов через данные. Увеличение
количества проходов должно приводить к повышению точности обучения
модели.
model = gl.linear_regression.create(training, target='gain', features = l_lr_features,validation_set=None, verbose=False, max_iterations=100)
predictions = model.predict(testing)
# модель линейной регрессии, прогнозируем непрерывные значения, поэтому нам нужно сделать оценку
# вероятностей успеха и нормализовать все значения, чтобы получить вектор вероятностей
predictions_max, predictions_min = max(predictions), min(predictions)
predictions_prob = (predictions - predictions_min)/(predictions_max - predictions_min)
До
сих пор у нас были предсказания, которые являлись массивом SArray
прогнозируемых достижений, тогда как predictions_prob - это массив
SArray с нормированными значениями прогнозных значений. Чтобы получить
хорошую точность при количестве расчетов, сопоставимом с предыдущими
моделями, я выбрал пороговое значение 0,4. Для predictions_prob менее
0,4 вспомогательная функция backtest_linear_model не будет открывать
сделку, потому что ожидается день с падением цены. В противном случае
будет открыта позиция.
THRESHOLD = 0.4
bt_3_2 = backtest_linear_model(testing, predictions_prob, target='gain', threshold=THRESHOLD, STOP = -3, plot_title=model.name())
backtest_summary(bt_3_2)
Mean of PnL is 138.868280
Sharpe is 7.650187
Round turns 319
Name: LinearRegression
Accuracy: 0.631989596879
Precision: 0.705329153605
Recall: 0.54347826087
Max Drawdown: -1769.00025
Обучение модели Boosted Tree
Поскольку
мы предварительно тренировали дерево решений, теперь мы собираемся
подготовить классификатор boosted tree с теми же параметрами, которые
используются для других моделей классификаторов. Кроме того, мы
устанавливаем количество max_iterations = 12, чтобы увеличить
максимальное количество итераций для бустинга. Каждая итерация приводит к
созданию дополнительного дерева. Мы также устанавливаем более высокое
значение порога, чтобы повысить точность.
model = gl.boosted_trees_classifier.create(training, target='outcome', features=l_features, validation_set=None, max_iterations=12,verbose=False)
predictions_prob = model.predict(testing, 'probability')
THRESHOLD = 0.7
bt_4_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name())
backtest_summary(bt_4_2)
Mean of PnL is 112.002338
Sharpe is 6.341981
Round turns 214
Name: BoostedTreesClassifier
Accuracy: 0.563068920676
Precision: 0.682242990654
Recall: 0.352657004831
Max Drawdown: -1769.00025
Обучение модели Random Forest
Это
наша последняя обучаемая модель, Random Forest Classifier,
представленная ансамблем деревьев решений. Максимальное количество
деревьев, используемых в модели, num_trees = 10, чтобы избежать слишком
большой сложности и переобучения.
model = gl.random_forest_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False, num_trees = 10)
predictions_prob = model.predict(testing, 'probability')
THRESHOLD = 0.6
bt_5_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name())
backtest_summary(bt_5_2)
Mean of PnL is 114.786962
sharpe is 6.384243
Round turns 311
Name: RandomForestClassifier
Accuracy: 0.598179453836
Precision: 0.668810289389
Recall: 0.502415458937
Max Drawdown: -1769.00025
Собираем все модели вместе
Теперь
мы можем объединить все стратегии и увидеть общий результат. Интересно
посмотреть на резюме работы всех моделей машинного обучения,
отсортированных по их точности.
name
|
accuracy
|
precision
|
round turns
|
sharpe
|
LinearRegression
|
0.63
|
0.71
|
319
|
7.65
|
BoostedTreesClassifier
|
0.56
|
0.68
|
214
|
6.34
|
RandomForestClassifier
|
0.60
|
0.67
|
311
|
6.38
|
DecisionTree
|
0.56
|
0.66
|
234
|
6.52
|
LogisticClassifier
|
0.64
|
0.66
|
426
|
6.45
|
Если
мы соберем всю прибыль и убытки для каждой из моделей в массиве pnl, на
следующей диаграмме показана кривая капитала, полученная суммированием
всех прибылей и убытков, день за днем.
Mean of PnL is 119.446463
Sharpe is 6.685744
Round turns 1504
First trading day 2013-04-09
Last trading day 2016-04-22
Total return 179647
Просто,
чтобы представить цифры, примерно за 3 года торговли, все модели имеют
общую прибыль около 180 000 долларов. Максимальный размер позиции - 5
контрактов CFD, но для уменьшения риска все они закрываются в конце
каждого дня, поэтому овернайт не допускается.
Статистика и аггрегирование всех моделей
Поскольку
каждая модель может открыть сделку, но мы добавили 5 одновременных
моделей вместе, в течение одного дня может быть от 1 контракта до 5
контрактов CFD. Если все модели согласны открыть сделки в течение одного
и того же дня, существует высокая вероятность предсказания дня с ростом
цены. Более того, мы можем группировать по числу моделей, которые
открывают сделки одновременно во время открытия сессии. Затем мы
оцениваем точность как функцию количества моделей.
Как
видно из графика, изображенного выше, точность становится лучше с
увеличением количества моделей, согласных открыть сделку. Чем больше
моделей согласны, тем больше точность мы получаем. Например, при 5
моделях в один и тот же день вероятность предсказания дня с ростом цены
превышает 85%.
Заключение
Даже
в финансовом мире Machine Learning приветствуется как мощный инструмент
изучения данных и дает нам отличные инструменты прогнозирования. Каждая
модель показывает разные значения точности, но в целом все модели могут
быть объединены для достижения лучшего результата, чем каждая из них,
взятая отдельно. GraphLab Create - отличная библиотека, простая в
использовании, масштабируемая и способная управлять большими данными
очень быстро.
Комментариев нет:
Отправить комментарий