Расцвет автоматизированной торговли: машины, торгующие S&P 500

Опубликовано: 2022-03-11

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

В этой статье я собираюсь показать вам, как с хорошей точностью предсказать, как следует разместить следующую сделку, чтобы получить положительную прибыль. Для этого примера в качестве базового актива для торговли я выбрал индекс S&P 500, средневзвешенное значение 500 компаний США с большей капитализацией. Очень простая стратегия заключается в том, чтобы купить индекс S&P 500, когда Wall Street Exchange начнет торговать, в 9:30 утра, и продать его на закрытии сессии в 16:00 по восточному поясному времени. Если цена закрытия индекса выше цены открытия, прибыль положительная, тогда как отрицательная прибыль будет достигнута, если цена закрытия ниже цены открытия. Итак, возникает вопрос: как узнать, закончится ли торговая сессия с ценой закрытия выше, чем цена открытия? Машинное обучение — это мощный инструмент для решения такой сложной задачи, и он может быть полезным инструментом, помогающим нам принимать торговые решения.

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

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

Создание вашей первой программы автоматической торговли финансовыми данными

Итак, вы хотите создать свою первую программу для анализа финансовых данных и предсказания правильной сделки? Позвольте мне показать вам, как это сделать. Я буду использовать код Python для машинного обучения, а мы будем использовать исторические данные из службы Yahoo Finance. Как упоминалось ранее, исторические данные необходимы для обучения модели, прежде чем делать наши прогнозы.

Для начала нам нужно установить:

  • Python, и, в частности, я предлагаю использовать блокнот IPython.
  • Пакет Yahoo Finance Python (точное название — yahoo-finance ) через команду терминала: pip install yahoo-finance .
  • Бесплатная пробная версия пакета машинного обучения под названием GraphLab. Не стесняйтесь проверять полезную документацию этой библиотеки.

Обратите внимание, что только часть GraphLab имеет открытый исходный код, SFrame, поэтому для использования всей библиотеки нам нужна лицензия. Существует 30-дневная бесплатная лицензия и некоммерческая лицензия для студентов или тех, кто участвует в соревнованиях Kaggle. С моей точки зрения, GraphLab Create — очень интуитивно понятная и простая в использовании библиотека для анализа данных и обучения моделей машинного обучения.

Копаемся в коде Python

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

Итак, давайте создадим новый блокнот IPython и напишем код для загрузки исторических цен индекса S&P 500. Обратите внимание: если вы предпочитаете использовать другие инструменты, вы можете начать с нового проекта Python в предпочитаемой вами среде IDE.

 import graphlab as gl from __future__ import division from datetime import datetime from yahoo_finance import Share # download historical prices of S&P 500 index today = datetime.strftime(datetime.today(), "%Y-%m-%d") stock = Share('^GSPC') # ^GSPC is the Yahoo finance symbol to refer S&P 500 index # we gather historical quotes from 2001-01-01 up to today hist_quotes = stock.get_historical('2001-01-01', today) # here is how a row looks like 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 = [] # reverse the list 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 , который представляет собой хорошо масштабируемый фрейм данных на основе столбцов, и он сжат. Одним из преимуществ является то, что он также может быть больше, чем объем оперативной памяти, поскольку он поддерживается диском. Вы можете проверить документацию, чтобы узнать больше о SFrame.

Итак, давайте сохраним, а затем проверим исторические данные:

 qq = gl.SFrame({'datetime' : l_date, 'open' : l_open, 'high' : l_high, 'low' : l_low, 'close' : l_close, 'volume' : l_volume}) # datetime is a string, so convert into datetime object qq['datetime'] = qq['datetime'].apply(lambda x:datetime.strptime(x, '%Y-%m-%d')) # just to check if data is sorted in ascending mode qq.head(3)
близко дата и время высоко низкий открытым объем
1283,27 2001-01-02 00:00:00 1320,28 1276.05 1320,28 1129400000
1347,56 03.01.2001 00:00:00 1347,76 1274,62 1283,27 1880700000
1333,34 04.01.2001 00:00:00 1350,24 1329,14 1347,56 2131000000

Теперь мы можем сохранить данные на диск с помощью метода SFrame save следующим образом:

 qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)

Давайте посмотрим, как выглядит S&P 500

Чтобы увидеть, как будут выглядеть загруженные данные S&P 500, мы можем использовать следующий код:

 import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])

Результатом кода является следующий график:

График S&P 500, обычно растущий со временем, показанный вышеприведенным кодом Python.

Обучение некоторых моделей машинного обучения

Добавление результата

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

  • +1 за день повышения, когда цена Closing выше цены Opening .
  • -1 за Даун-день с ценой Closing ниже цены Opening .
 # add the outcome variable, 1 if the trading session was positive (close>open), 0 otherwise qq['outcome'] = qq.apply(lambda x: 1 if x['close'] > x['open'] else -1) # we also need to add three new columns 'ho' 'lo' and 'gain' # they will be useful to backtest the model, later qq['ho'] = qq['high'] - qq['open'] # distance between Highest and Opening price qq['lo'] = qq['low'] - qq['open'] # distance between Lowest and Opening price qq['gain'] = qq['close'] - qq['open']

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

 ts = gl.TimeSeries(qq, index='datetime') # add the outcome variable, 1 if the bar was positive (close>open), 0 otherwise ts['outcome'] = ts.apply(lambda x: 1 if x['close'] > x['open'] else -1) # GENERATE SOME LAGGED TIMESERIES ts_1 = ts.shift(1) # by 1 day ts_2 = ts.shift(2) # by 2 days # ...etc.... # it's an arbitrary decision how many days of lag are needed to create a good forecaster, so # everyone can experiment by his own decision

Добавление предикторов

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

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

 ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close']

Как показано выше, я добавил два новых столбца функций, feat1 и feat2 , в наш набор данных ( ts ), содержащий 1 , если сравнение истинно, и 0 в противном случае.

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

 # add_features is a helper function, which is out of the scope of this article, # and it returns a tuple with: # ts: a timeseries object with, in addition to the already included columns, also lagged columns # as well as some features added to train the model, as shown above with feat1 and feat2 examples # l_features: a list with all features used to train Classifier models # l_lr_features: a list all features used to train Linear Regression models ts, l_features, l_lr_features = add_features(ts) # add the gain column, for trading operations with LONG only positions. # The gain is the difference between Closing price - Opening price ts['gain'] = ts['close'] - ts['open'] ratio = 0.8 # 80% of training set and 20% of testing set training = ts.to_sframe()[0:round(len(ts)*ratio)] testing = ts.to_sframe()[round(len(ts)*ratio):]

Обучение модели дерева решений

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 имеет одинаковый интерфейс для прогнозирования данных из разных подогнанных моделей. Мы будем использовать метод predict , которому нужен тестовый набор для предсказания целевой переменной, в нашем случае — outcome . Теперь мы можем предсказать данные из тестового набора:

 predictions = decision_tree.predict(testing) # and we add the predictions column in testing set testing['predictions'] = predictions # let's see the first 10 predictions, compared to real values (outcome column) testing[['datetime', 'outcome', 'predictions']].head(10)
дата и время исход предсказания
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

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

Наша торговая стратегия ожидает положительно прогнозируемого результата, чтобы купить S&P 500 по цене Opening и продать его по цене Closing , поэтому мы надеемся иметь самый низкий уровень ложных срабатываний, чтобы избежать потерь. Другими словами, мы ожидаем, что наша модель будет иметь самый высокий уровень точности .

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

С помощью простого расчета, учитывая этот небольшой набор из десяти прогнозов:

  • точность = 6/10 = 0,6 или 60%
  • точность = 3/5 = 0,6 или 60%
  • отзыв = 3/5 = 0,6 или 60%

Обратите внимание, что обычно числа выше отличаются друг от друга, но в данном случае они совпадают.

Тестирование модели на истории

Теперь мы моделируем, как модель будет торговать, используя ее предсказанные значения. Если прогнозируемый результат равен +1 , значит, мы ожидаем Ап-день . При ап-дне мы покупаем индекс в начале сессии и продаем индекс в конце сессии в тот же день. И наоборот, если прогнозируемый результат равен -1 , мы ожидаем Даун-день , поэтому мы не будем торговать в этот день.

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

  • pnl = Close - Open (для каждого торгового дня)

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

 pnl = testing[testing['predictions'] == 1]['gain'] # the gain column contains (Close - Open) values # I have written a simple helper function to plot the result of all the trades applied to the # testing set and represent the total return expressed by the index basis points # (not expressed in dollars $) plot_equity_chart(pnl,'Decision tree model') 

Модель дерева решений, обычно идущая вверх и вправо, как показано в приведенном выше коде Python.

 Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511

Здесь Шарп — это годовой коэффициент Шарпа, важный показатель качества торговой модели.

Коэффициент Шарпа равен квадратному корню из 252, умноженному на среднее значение pnl, деленное на стандартное отклонение pnl.

Учитывая сделки, выраженные день за днем, тогда как mean — это среднее значение списка прибылей и убытков, а sd — стандартное отклонение. Для простоты в приведенной выше формуле я посчитал безрисковую доходность равной 0.

Некоторые основы торговли

Торговля индексом требует покупки актива, который напрямую связан с индексом. Многие брокеры копируют индекс S&P 500 с производным продуктом, называемым CFD (контракт на разницу), который представляет собой соглашение между двумя сторонами об обмене разницей между ценой открытия и ценой закрытия контракта.

Пример : купите 1 CFD S&P 500 на Open (значение 2000), продайте его на Close дня (значение 2020). Разница, а значит и выигрыш, составляет 20 очков. Если каждая точка имеет значение $25:

  • Валовая прибыль составляет 20 points x $25 = $500 с 1 контрактом CFD.

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

  • Чистая прибыль составляет (20 - 0.6) points x $25 = $485 .

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

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

Если мы посмотрим на временной ряд, загруженный из Yahoo Finance в начале этой статьи, каждый день имеет Low цену, которая является самой низкой ценой, достигнутой в течение этого дня. Если мы установим стоп-уровень на -3 пункта дальше от цены Opening , а Low - Open = -5 , то сработает стоп-ордер, и открытая позиция будет закрыта с убытком -3 пункта вместо -5 . Это простой способ снизить риск. Следующий код представляет мою вспомогательную функцию для имитации сделки со стоп-уровнем:

 # This is a helper function to trade 1 bar (for example 1 day) with a Buy order at opening session # and a Sell order at closing session. To protect against adverse movements of the price, a STOP order # will limit the loss to the stop level (stop parameter must be a negative number) # each bar must contains the following attributes: # Open, High, Low, Close prices as well as gain = Close - Open and lo = Low - Open def trade_with_stop(bar, slippage = 0, stop=None): """ Given a bar, with a gain obtained by the closing price - opening price it applies a stop limit order to limit a negative loss If stop is equal to None, then it returns 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 долларов США, то есть 250 долларов США, включая торговые издержки, наша чистая прибыль была бы (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)) 

Модель дерева решений, как правило, поднимающаяся вверх и вправо, но с менее выраженными шипами, как показано в приведенном выше коде Python.

 Mean of PnL is 2.162171 Sharpe is 3.502897 Round turns 511 Slippage is 0.6 STOP level at -3

Следующий код используется для прогнозирования немного другим способом. Обратите внимание на метод predict , который вызывается с дополнительным параметром output_type = “probability” . Этот параметр используется для возврата вероятностей предсказанных значений вместо предсказания их класса ( +1 для положительного предсказанного результата, -1 для отрицательного предсказанного результата). Вероятность больше или равная 0.5 связана с предсказанным значением +1 , а значение вероятности меньше 0.5 связано с предсказанным значением -1 . Чем выше эта вероятность, тем больше у нас шансов предсказать реальный Up Day .

 predictions_prob = decision_tree.predict(testing, output_type = 'probability') # predictions_prob will contain probabilities instead of the predicted class (-1 or +1)

Теперь мы протестируем модель с помощью вспомогательной функции backtest_ml_model , которая вычисляет ряд совокупных доходов, включая проскальзывание и комиссии, и отображает их значения. Для краткости, без подробного объяснения функции backtest_ml_model , следует подчеркнуть важную деталь: вместо фильтрации тех дней с прогнозируемым outcome = 1 , как мы делали в предыдущем примере, теперь мы фильтруем те predictions_prob , которые равны threshold = 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 :

  • Максимальная просадка (в долларах), как показано выше.
  • Точность с помощью метода Graphlab.evaluation .
  • Точность, с методом Graphlab.evaluation .
  • Напомним, с методом 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) 

График DecisionTree с осью Y, помеченной как «доллары» и достигающей 30 000, и осью X, помеченной как «количество оборотов» и доходящей до 600, как показано в приведенном выше коде Python. Сами графические данные идентичны предыдущему рендерингу.

 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 # it's the minimum threshold to predict an Up day so hopefully a good day to trade 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) 

График DecisionTree, где ось Y помечена как «доллары» и достигает 30 000, а ось X помечена как «количество оборотов» и на этот раз расширена только до 250, как показано в приведенном выше коде Python. Сами графические данные аналогичны предыдущему рендерингу, но еще более сглажены.

 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

Как мы видим на графике выше, кривая капитала стала намного лучше, чем раньше (коэффициент Шарпа равен 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) 

График LogisticClassifier с осью Y, помеченной как «доллары», на этот раз доходящей до 50 000, и осью X, помеченной как «количество круговых оборотов» и расширяющейся теперь до 450, как показано в приведенном выше коде Python. Сами графические данные аналогичны предыдущему рендерингу по общему тренду.

 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

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

Обучение модели линейной регрессии

Основное отличие этой модели в том, что она имеет дело с непрерывными значениями, а не с бинарными классами, как упоминалось ранее. Нам не нужно обучать модель с целевой переменной, равной +1 для дней подъема и -1 для дней спада , наша цель должна быть непрерывной переменной. Поскольку мы хотим предсказать положительную прибыль или, другими словами, цену закрытия выше цены открытия , теперь целью должен быть столбец прибыли нашего обучающего набора. Кроме того, список функций должен состоять из непрерывных значений, таких как предыдущие 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) # a linear regression model, predict continuous values, so we need to make an estimation of their # probabilities of success and normalize all values in order to have a vector of probabilities predictions_max, predictions_min = max(predictions), min(predictions) predictions_prob = (predictions - predictions_min)/(predictions_max - predictions_min)

На данный момент у нас есть прогнозы, которые представляют SArray прогнозируемых доходов, тогда как predictions_prob — это SArray с нормализованными значениями predictions . Чтобы иметь хорошую точность и определенное количество оборотов, сравнимое с предыдущими моделями, я выбрал пороговое значение 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) 

График линейной регрессии с осью Y, помеченной как «доллары» и доходящей до 45 000, и осью X, помеченной «количество оборотов» и доходящей до 350, как показано в приведенном выше коде Python. Сами графические данные снова похожи, но не полностью идентичны предыдущему рендерингу.

 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

Обучение усиленного дерева

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

 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) 

График BoostedTreesClassifier, где ось Y помечена как «доллары» и достигает 25 000, а ось X помечена как «количество оборотов» и расширена до 250, как показано в приведенном выше коде Python. Сами графические данные снова аналогичны предыдущему рендерингу, с более резким увеличением около 175 по оси X.

 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

Обучение случайного леса

Это наша последняя обученная модель, классификатор случайного леса, состоящий из множества деревьев решений. Максимальное количество деревьев, используемых в модели, установлено 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) 

График RandomForestClassifier с осью Y, помеченной как «доллары» и доходящей до 40 000, и осью X, помеченной как «количество оборотов» и доходящей до 350, как показано в приведенном выше коде Python. Сами графические данные снова аналогичны предыдущему рендерингу.

 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

Collecting All the Models Together

Now we can join all the strategies together and see the overall result. It's interesting to see the summary of all Machine Learning models, sorted by their precision.

имя 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

If we collect all the profit and loss for each one of the previous models in the array pnl , the following chart depicts the equity curve obtained by the sum of each profit and loss, day by day.

 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

Just to give some numbers, with about 3 years of trading, all models have a total gain of about 180,000 dollars. The maximum exposition is 5 CFD contracts in the market, but to reduce the risk they all are closed at the end of each day, so overnight positions are not allowed.

Statistics of the Aggregation of All Models Together

Since each model can open a trade, but we added 5 concurrent models together, during the same day there could be from 1 contract up to 5 CFD contracts. If all models agree to open trades during the same day, there is a high chance to have an Up day predicted. Moreover, we can group by the number of models that open a trade at the same time during the opening session of the day. Then we evaluate precision as a function of the number of concurrent models.

As we can see by the chart depicted above, the precision gets better as the number of models do agree to open a trade. The more models agree, the more precision we get. For instance, with 5 models triggered the same day, the chance to predict an Up day is greater than 85%.

Заключение

Even in the financial world, Machine Learning is welcomed as a powerful instrument to learn from data and give us great forecasting tools. Each model shows different values of accuracy and precision, but in general, all models can be aggregated to achieve a better result than each one of them taken singularly. GraphLab Create is a great library, easy to use, scalable and able to manage Big Data very quickly. It implements different scientific and forecasting models, and there is a free license for students and Kaggle competitions.

Связанный: Введение в теорию машинного обучения и ее приложения: наглядное руководство с примерами

Additional disclosure: This article has been prepared solely for information purposes, and is not an offer to buy or sell or a solicitation of an offer to buy or sell any security or instrument or to participate in any particular trading strategy. Examples presented on these sites are for educational purposes only. Past results are not necessarily indicative of future results.