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

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

Обратите внимание, что только часть 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)

Закрыть datetime высокий низкий открытый том 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

Теперь мы можем сохранить данные на диск с помощью метода 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'])

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

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

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

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

  • +1 за день повышения, когда цена закрытия выше цены открытия.
  • -1 за день с понижением, когда цена закрытия ниже цены открытия.
# 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 имеет сдвиг метода, который отстает от данных на определенное количество строк.

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, используемый для подбора модели с набором обучающих данных. Типичные параметры:

  • обучение — это обучающий набор, содержащий столбцы функций и целевой столбец.
  • target — это имя столбца, содержащего целевую переменную.
  • validation_set — это набор данных для мониторинга эффективности обобщения модели. В нашем случае у нас нет validation_set.
  • функции — это список названий столбцов функций, используемых для обучения модели.
  • 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, чтобы достичь «идеального» винрейта. Наше дерево решений, как еще один классификатор из пакета GraphLab Create, имеет метод оценки для получения многих важных показателей подобранной модели.

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

Следующий код покажет точность подобранной модели как с обучающим, так и с тестовым набором:

decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy']
(0.6077348066298343, 0.577373211963589)

Как показано выше, точность модели с тестовым набором составляет около 57 процентов, что несколько лучше, чем подбрасывание монеты (50 процентов).

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

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

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)

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

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

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

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

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

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

Теперь мы моделируем, как модель будет торговать, используя ее предсказанные значения. Если прогнозируемый результат равен +1, это означает, что мы ожидаем Up day. При дне роста мы покупаем индекс в начале сессии и продаем индекс в конце сессии в тот же день. И наоборот, если прогнозируемый результат равен -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')

Mean of PnL is 1.843504
Sharpe is 1.972835
Round turns 511

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

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

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

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

Пример: купите 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, поэтому это ложное срабатывание. В этом случае завершающаяся сессия оказывается днем понижения с ценой закрытия ниже цены открытия, и мы получаем убыток.

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

Если мы посмотрим на временной ряд, загруженный из Yahoo Finance в начале этой статьи, каждый день имеет Низкую цену, которая является самой низкой ценой, достигнутой в течение этого дня. Если мы установим стоп-уровень в -3 пункта далеко от цены открытия, а 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))
Mean of PnL is 2.162171
Sharpe is 3.502897
Round turns 511
Slippage is 0.6
STOP level at -3

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

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, следует подчеркнуть важную деталь: вместо фильтрации тех дней с прогнозируемым исходом = 1, как мы делали в предыдущем примере, теперь мы фильтруем те прогнозы_prob, которые равны или превышают пороговое значение = 0,5, следующим образом:

trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]

Помните, что Чистая прибыль каждого торгового дня составляет: Чистая прибыль = (Валовая прибыль — ПРОМАГ) * МУЛЬТ — 2 * КОМИССИЯ.

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

drawdown = pnl - pnl.cumulative_max()
max_drawdown = min(drawdown)

Внутри вспомогательной функции вычисляется backtest_summary:

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

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)

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:

  • обучение — это обучающий набор, содержащий столбцы функций и целевой столбец.
  • target — это имя столбца, содержащего целевую переменную.
  • validation_set — это набор данных для мониторинга эффективности обобщения модели. В нашем случае у нас нет validation_set.
  • функции — это список имен столбцов функций, используемых для обучения модели, для этой модели мы будем использовать другой набор, относящийся к моделям классификатора.
  • 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)

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

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

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

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)

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

Собираем все модели вместе

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

имя точность точность округление повороты резкость 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 4526 6,4

Если мы соберем все прибыли и убытки для каждой из предыдущих моделей в массив 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%.

Вывод

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

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