Воспроизведение «Прогнозирования рецессий с помощью машинного обучения» (Яздани, 2020 г.)

Блокнот со всем кодом доступен здесь.

Недавно я обнаружил увлекательную презентацию о предсказании рецессии в США на основе экономических данных. Хотя я не был полностью удивлен этой темой, что бросилось в глаза, так это просто невероятная точность их модели.

Мы говорим о площади под кривой 95 %, идеальной чувствительности и почти безупречной специфичности. Наверняка, они должны были совершить ошибку? Сама статья находится за платным доступом. Тем не менее, презентация дает достаточно информации для тиражирования.

Как они это делают?

Согласно презентации, они использовали довольно простые экономические показатели (разница в ставках по федеральным фондам, уровень безработицы и т. д.), а также разницу в ежемесячных ценах SP500 (500 крупнейших компаний США), индикатор рынка в целом. Иногда они использовали различия в журналах, поскольку они с большей вероятностью будут следовать нормальному распределению.

Для своего исследования они использовали данные за 01.1959–12.2019 месячных данных.

Кратко о том, откуда я взял свои данные:

Куандл:

  • Рецессии, рассчитанные по ВВП
  • Ставка по федеральным фондам
  • Индекс промышленного производства (Indpro)
  • Несельскохозяйственная заработная плата
  • Уровень безработицы
# you can download the data like this -> might require API key
recession_data = quandl.get("FRED/USRECQP", collapse="monthly") fed_funds_data = quandl.get("FRED/FEDFUNDS", collapse="monthly")
indpro_data = quandl.get("FRED/INDPRO", collapse="monthly")
nonfarm_payroll_data = quandl.get("FRED/PAYEMS", collapse="monthly")
unemploy_data = quandl.get("FRED/UNEMPLOY", collapse="monthly")

Обзор рынка

  • SPY500

Макротренды

  • 10-летние казначейские облигации

На Macrotrends были доступны только данные, начиная с 1962 года. Поэтому я буду рассматривать данные только с 1962 года.

# load in merged data 
data = pd.read_csv("../data/replication_data.csv", index_col=0)
# fill missing recession data
data["recession"] = data["recession"].fillna(method='ffill')

Подготовка данных

Подготовка данных довольно проста. Рассчитываем логарифмическую разницу и разности. Кроме того, мы разделяем данные в поезде на тестовый набор, начиная с 1962 года, но в остальном придерживаясь даты окончания, предложенной в презентации.

# calculate logarithmic differences 
def log_diff(v): 
    log_diff = np.log(v/v.shift(1))
    return log_diff
# preprocessing 
data["slope_yc"] = (data["ty10"] - data["fedfunds"]).diff()
data["fedfunds"] = data["fedfunds"].diff()
data["indpro"] = log_diff(data["indpro"]) 
data["nonfarm"] = log_diff(data["nonfarm"])
data["SP500"] = log_diff(data["SP500"])
data["ty10"] = (data["ty10"]).diff()
data["unemploy"] = log_diff(data["unemploy"])
data["recession"] = data["recession"].shift(1)

Наш период обучения — с 1962–02 по 2006–2012 годы.

Наш тестовый период длится с 2007 – 01 по 2019–2012 годы.

# get x and y 
X = data.drop(['recession'], axis=1)
y = data["recession"]
# divide in train test according to paper 
X_train, y_train = (X.loc["1962-02-28":"2007-01-01"], y.loc["1962-02-28":"2007-01-01"])
X_test, y_test = (X.loc["2007-01-01":], y.loc["2007-01-01":])

Обработка дисбаланса классов

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

Во-первых, мы могли бы спросить себя, как часто эти месяцы рецессии встречаются в данных?

print(data["recession"].value_counts())
0.0    612
1.0     83
Name: recession, dtype: int64

Класс меньшинств составляет всего 13,56 %!

Итак, как вы можете справиться с дисбалансом классов?

Ну, есть две основные стратегии:

  1. Недостаточная выборка по порядку

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

2.Передискретизация

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

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

## Sampling techniques

from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler

# oversample using SMOTE 
sm = SMOTE(random_state=42)
X_train_smote, y_train_smote = sm.fit_resample(X_train, y_train)

# undersample randomly
rus = RandomUnderSampler(random_state=42)
X_train_rnd, y_train_rnd  = rus.fit_resample(X_train, y_train)

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

train_test_pairs = {"Normal":(X_train, y_train), 
                    "SMOTE":(X_train_smote, y_train_smote), 
                    "Undersampling":(X_train_rnd, y_train_rnd)}

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

Установите нашу модель и оцените

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

fig = go.Figure()
fig.add_shape(
    type='line', line=dict(dash='dash'),
    x0=0, x1=1, y0=0, y1=1
)

for label, pair in train_test_pairs.items(): 
    clf = RandomForestClassifier(max_depth=10, n_estimators=100, class_weight = "balanced_subsample", random_state=0)
    clf.fit(pair[0], pair[1])
    y_pred= clf.predict(X_test) 
    fpr, tpr, thresholds = metrics.roc_curve(y_test, clf.predict_proba(X_test)[:,1])
    auc = metrics.roc_auc_score(y_test, clf.predict(X_test))

    name = f"{label} (AUC={auc:.2f})"
    fig.add_trace(go.Scatter(x=fpr, y=tpr, name=name, mode='lines'))

fig.update_layout(
    xaxis_title='False Positive Rate',
    yaxis_title='True Positive Rate',
    yaxis=dict(scaleanchor="x", scaleratio=1),
    xaxis=dict(constrain='domain'),
    width=900, height=700
)
fig.show()

Барабанная дробь, пожалуйста…

Хороший! Мы видим, что методы выборки имеют существенное значение в сообщаемых показателях AUC. Недостаточная выборка работает лучше, чем SMOTE, и также использовалась в статье. В целом я считаю, что репликация прошла успешно,учитывая, что я пропустил данные за первые три года и не настраивал никаких гиперпараметров.

Итак, учитывая, что мы можем предсказывать рецессии, что мы можем делать с этой информацией?

Куда вкладывать деньги во время рецессии

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

1) только шпион

Индекс SP500 послужит нашим эталоном, поскольку его часто считают хорошим представлением рынка США.

2) SPY минус рецессии

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

3) ШПАЙ и ГЛД

Золото всегда кажется безопасным вариантом. А в периоды рецессий можно было наблюдать набег на него.

4) ШПАЙ и ВИКС

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

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

Основная рецессия в этот период приходится на период между 2008 и 2010 годами. Мы видим вопреки распространенному мнению, что инвестирование в золото на самом деле не лучшая идея. Золото, похоже, сильно зависит от общего движения рынка. Вывод денег с рынка во время рецессии — это хорошо, но на самом деле это не дает нам преимущества. Инвестирование в индекс волатильности (VIX), с другой стороны, кажется настоящим прорывом для пассивных инвесторов. Когда наступает рецессия, страх и волатильность растут, а вместе с ними и прибыль.

Будет ли рецессия в ближайший месяц?

Чтобы определить, будет ли рецессия, мы можем получить более свежие данные, подобные этому.

# features 
fed_funds_data = quandl.get("FRED/FEDFUNDS", collapse="monthly")
indpro_data = quandl.get("FRED/INDPRO", collapse="monthly")
nonfarm_payroll_data = quandl.get("FRED/PAYEMS", collapse="monthly")
unemploy_data = quandl.get("FRED/UNEMPLOY", collapse="monthly")
spy = yf.Ticker("^GSPC").history(period='1d', start='2019-1-1').Close.resample("M").mean()
ty10 = yf.Ticker("^TNX").history(period='1d', start='2019-1-1').Close.resample("M").mean()
# subsetting recent data  
data = pd.DataFrame()
data["fedfunds"] = fed_funds_data.loc["01-01-2020":].Value
data["nonfarm"] = nonfarm_payroll_data.loc["01-01-2020":].Value
data["indpro"] = indpro_data.loc["01-01-2020":].Value
data["SP500"] = spy.loc["01-01-2020":]
data["ty10"] = ty10.loc["01-01-2020":]
data["unemploy"] = unemploy_data.loc["01-01-2020":].Value

# preprocessing 
data["slope_yc"] = (data["ty10"] - data["fedfunds"]).diff()
data["fedfunds"] = data["fedfunds"].diff()
data["indpro"] = log_diff(data["indpro"]).fillna(method="ffill") # no recent data
data["nonfarm"] = log_diff(data["nonfarm"])
data["SP500"] = log_diff(data["SP500"])
data["ty10"] = (data["ty10"]).diff()
data["unemploy"] = log_diff(data["unemploy"])

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

data.dropna(inplace=True)
data["recession_proba"] = clf.predict_proba(data)[:,1]
fig = go.Figure()
fig.add_scatter(x= data.index, y=data["recession_proba"], mode='lines', name="SPY long")
fig.update_layout(title_text="Probabilities of a Recession according to the model")
fig.show()

По состоянию на сентябрь наша модель уверена, что в ближайший месяц рецессии не будет. Мы видим, что из-за изменений ставок ФРС Джеромом Пауэллсом, а также роста числа безработных мы действительно столкнулись с более высокими уровнями уверенности в апреле и марте.

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

использованная литература

Яздани, А. (2020). Прогнозирование рецессий с помощью машинного обучения: несбалансированный подход к классификации. Журнал финансовых данных. Слайды презентации получены из: https://fdpinstitute.org/resources/Documents/webinars%20thoughtleadership%20pieces%20etc/Al%20Yazdani%20Webinar%20July%208,%202020.pdf