Когда выкупать падение

Мотивация: «Купите дип» — это разочаровывающе простой совет. Как и большинство советов, это легче сказать, чем сделать, и дающий такой совет, вероятно, не пытался практиковать то, что они проповедуют. Это вызывает FOMO, что приводит к «торговле надеждой», когда «сделка надежды» идет наперекосяк, вы застряли как «долгосрочный инвестор», который «действительно верит в миссию компании».

Купить падение еще сложнее в такие времена, когда S & P находится вблизи / на исторических максимумах. Оглядываясь на декабрь 2018 года, распродажа акций представила удивительную возможность покупки для любого инвестора или трейдера, готового вложить свой капитал в игру в трудное время.

Проблема понимания того, когда «купить провал» или «продать ралли», представляет собой интересную область исследования для разработки алгоритмов / науки о данных в финансах, поскольку существует много способов определить проблему и подойти к ней. Одним из таких подходов является использование скрытых марковских моделей (HMM) для определения периодов высокой и низкой волатильности доходности.

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

Скрытые марковские модели: Скрытые марковские модели… сложный. Я глубоко изучил их с мировым экспертом по этому вопросу, только чтобы иметь то, что я считаю «наполовину приличным» пониманием того, что я делаю в отношении математики, стоящей за ними. HMM использовались для решения проблем в прогнозировании землетрясений, машинном переводе речи и даже на финансовых рынках с некоторым утверждением, что иерархические HMM отвечают за некоторые из успешных торговых алгоритмов Renaissance Tech. Чтобы понять скрытую марковскую модель, мы можем сначала попытаться понять марковскую цепь.

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

Надеюсь, это встроено правильно…

Лучший способ понять цепь Маркова — нарисовать ее. Рассмотрим изображение ниже:

На диаграмме показаны 3 различных состояния: бычий рынок, медвежий рынок и застойный рынок. Стрелки показывают возможные способы перехода одного состояния в другое. Матрица P называется матрицей перехода и отображает вероятности перехода из одного состояния в другое. Например, вероятность перехода от бычьего рынка к бычьему рынку составляет 0,9, вероятность перехода от бычьего рынка к медвежьему рынку составляет 0,075, вероятность перехода от бычьего рынка к застойному рынку составляет 0,5. Важной особенностью любой матрицы перехода P является то, что сумма вероятностей перехода в каждой строке будет равна 1.

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

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

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

Получение данных: Теперь, когда у нас есть некоторая интуитивная основа для понимания нашей проблемы, мы приступаем к сбору некоторых рыночных данных. К сожалению, многие старые учебники по Python, ориентированные на анализ финансовых временных рядов, используют считыватель данных pandas или другие пакеты, указывающие на устаревшие бесплатные API, такие как google finance, yahoo finance и quandl.

Охотясь вокруг, я обнаружил, что IEX, вероятно, лучший источник бесплатных ежедневных торговых баров. Внутридневные данные не бесплатны, но их цены очень дешевы, особенно по сравнению с другими доступными вариантами. Хотя существуют способы использования средства чтения данных pandas для подключения к IEX, мы используем библиотеку iex-api-python, которую можно найти здесь: http://www.danielecook.com/iex-api-python/

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

#import packages for use later in the HMM code

import pandas as pd
import sklearn.mixture as mix

import numpy as np
import scipy.stats as scs

import datetime as dt

import matplotlib as mpl
from matplotlib import cm
import matplotlib.pyplot as plt
from matplotlib.dates import YearLocator, MonthLocator
%matplotlib inline

import seaborn as sns

from iex import Stock

ticker = ["SPY"]
all_historic_data = pd.DataFrame()

for t in ticker:
    ticker_data = Stock(t).chart_table(range="5y")
    
    ticker_data_clean = ticker_data[["date", "close", "high", "low"]]
    ticker_data_clean["date"] = pd.to_datetime(ticker_data_clean["date"])
    ticker_data_clean.insert(1, "ticker", t)
    ticker_data_clean["return"] = ticker_data_clean["close"].pct_change()
    
    ticker_data_clean["range"] = (ticker_data_clean["high"]/ticker_data_clean["low"])-1
    del ticker_data_clean["high"]
    del ticker_data_clean["low"]
    ticker_data_clean.dropna(how="any", inplace=True)

    
    all_historic_data = pd.concat([all_historic_data, ticker_data_clean])

all_historic_data.head()

Вывод из приведенного выше кода представляет собой фрейм данных с данными временных рядов для SPY-spyder ETF, который отслеживает S&P500, а также некоторые рассчитанные столбцы для дневной доходности и торгового диапазона:

Для этого конкретного примера мы вытащили только 5 лет истории. Затем мы разбиваем эти данные на обучающий и тестовый набор. Наборы выбираются случайным образом — преимущество этого подхода заключается в том, что он позволяет модели видеть более широкий спектр данных, а не один длинный отрезок режимов волатильности. Поскольку мы используем функцию rand, результаты здесь будут отличаться от любых последовательных запусков кода. Для лучшей диагностики моделей лучшим подходом было бы вывести поезд и тестовые наборы для повторной настройки, но будьте осторожны, чтобы не перегружать образцовые данные… используйте его скорее как руководство, чтобы понять, как изменения в параметрах меняют результаты.

#create train and test sets
#this methodology will randomly select 80% of our data

msk = np.random.rand(len(all_historic_data)) < 0.8
train = all_historic_data[msk]
test = all_historic_data[~msk]

Модель и выходы: После того, как мы создадим наш поезд и тестовые наборы, мы можем пойти дальше и обучить нашу модель, а затем подогнать модель к нашему тестовому набору. Для этого мы используем функцию GaussianMixture как часть библиотеки sklearn.mix. Мы указываем n_components = 3, потому что мы хотим смоделировать 3 дискретных скрытых состояния — низкая волатильность, нейтральная волатильность и высокая волатильность.

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

Обратите внимание, что код IEX содержит все необходимые импорты для этого шага. :

X_train = train[["date", "return", "range", "close"]].set_index("date")
X_test = test[["date", "return", "range", "close"]].set_index("date")

model = mix.GaussianMixture(n_components=3, 
                            covariance_type="full", 
                            n_init=100, 
                            random_state=7).fit(X_train)

# Predict the optimal sequence of internal hidden state
hidden_states = model.predict(X_test)

print("Means and vars of each hidden state")
for i in range(model.n_components):
    print("{0}th hidden state".format(i))
    print("mean = ", model.means_[i])
    print("var = ", np.diag(model.covariances_[i]))
    print()

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

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

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

1:low vol regime - lowest variance for return. return is positive so signal is long2:high vol regime - highest variance for return. return is positive so long conservatively because of high variance0:neutral vol regime - 2nd lowest variance. return is positive so long conservatively or stay out stocks

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

sns.set(font_scale=1.25)
style_kwds = {'xtick.major.size': 3, 'ytick.major.size': 3,
              'font.family':u'courier prime code', 'legend.frameon': True}
sns.set_style('white', style_kwds)

fig, axs = plt.subplots(model.n_components, sharex=True, sharey=True, figsize=(12,9))
colors = cm.rainbow(np.linspace(0, 1, model.n_components))

for i, (ax, color) in enumerate(zip(axs, colors)):
    # Use fancy indexing to plot data in each state.
    mask = hidden_states == i
    ax.plot_date(X_test.index.values[mask],
                 X_test["close"].values[mask],
                 ".-", c=color)
    ax.set_title("{0}th hidden state".format(i), fontsize=16, fontweight='demi')

    # Format the ticks.
    ax.xaxis.set_major_locator(YearLocator())
    ax.xaxis.set_minor_locator(MonthLocator())
    sns.despine(offset=10)

plt.tight_layout()

Затем мы объединяем выходные данные нашей модели с базовым тестовым набором, чтобы мы могли сделать правильный график режимов против закрытия рынка:

sns.set(font_scale=1.5)
states = (pd.DataFrame(hidden_states, columns=['states'], index=X_test.index)
          .join(X_test, how='inner')
          .reset_index(drop=False)
          .rename(columns={'index':'Date'}))
states.head()

#suppressing warnings because of some issues with the font package
#in general, would not rec turning off warnings.
import warnings
warnings.filterwarnings("ignore")

sns.set_style('white', style_kwds)
order = [0, 1, 2]
fg = sns.FacetGrid(data=states, hue='states', hue_order=order,
                   palette=colors, aspect=1.31, height=12)
fg.map(plt.scatter, 'date', "close", alpha=0.8).add_legend()
sns.despine(offset=10)
fg.fig.suptitle('Historical SPY Regimes', fontsize=24, fontweight='demi')

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

1:low vol regime - lowest variance for return. return is positive so signal is long2:high vol regime - highest variance for return. return is positive so long conservatively because of high variance0:neutral vol regime - 2nd lowest variance. return is positive so long conservatively or stay out stocks

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

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

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

Источник