Парный трейдинг: описание стратегии на Python

Стратегия парного🎏 трейдинга очень популярна на рынке. Она основана на чистой статистике📊, что делает ее привлекательной для алгоритмической🤖 торговли. Общий смысл сводится к нескольким шагам: найти пару, проверить ее поведение, определить границы входа в позицию и направление (лонг/шорт).

Пары ищут с помощью корреляции, но корреляция в чистом виде может сослужить плохую службу. Спред пар должен быть стационарным и обладать коинтегрированностью. Весь представленный код на Python🐍.

В статье рассмотрены:

  • Введение в корреляцию/коинтеграцию на простом примере.
  • Корреляция без коинтеграции.
  • Коинтеграция без корреляции.

Основные понятия

Времянной ряд — статистические данные исследуемого процесса собранные в разные моменты времени.

Корреляция — статистическая взаимосвязь двух и более случайных величин. В нашем случае времянных рядов.

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

Стационарность — свойство процесса не менять свои характеристики со временем.

P-значение — величина используемая при тестировании статистических гипотез.

Простыми словами: две акции будут коинтегрированы, когда спред разницы их истории цен будет находиться в пределах фиксированных границ и не будет обладать трендом.

🎓Предположение

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

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

Пример стационарного спреда пары

Сигналами являются минимальные и максимальные отклонения спреда от нуля. Когда спред на минимуме — необходимо купить акцию А и продать в шорт акцию Б. Когда спред на максимуме — покупаем акцию Б и продаем в шорт акцию А. В обоих случаях закрываем позицию около нуля.

Введение в корреляцию/коинтеграцию

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

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

Нарисуем графики обоих рядов:

%matplotlib inline
import numpy as np
import pandas as pd
import statsmodels
from statsmodels.tsa.stattools import coint
import seaborn
import matplotlib.pyplot as plt

# устанавливаем зерно для повторимости случайных чисел
np.random.seed(107)

X_returns = np.random.normal(0, 1, 100) # генерируем историю доходности
X = pd.Series(np.cumsum(X_returns), name='X') + 50 # суммируем и смещаем на произвольную величину

some_noise = np.random.normal(0, 1, 100) # немного шума для второго ряда
Y = X + 5 + some_noise
Y.name = 'Y'
pd.concat([X, Y], axis=1).plot() # рисуем оба ряда

Нарисуем график спреда:

(Y-X).plot() # рисуем спрэд
plt.axhline((Y-X).mean(), color='red', linestyle='--') # добавляем среднее

Проверив значения коинтеграции и корреляции, видим что спред пары стационарен, p-значение рядом с нулем, и имеет высокую корреляцию около 95%. Код для получения значений оценки ниже:

def check_coint_corr(X, Y):
    # проверим стационарность двух рядов
    score, pvalue, _ = coint(X,Y)
    # проверим корреляцию двух рядов
    corr = X.corr(Y)
    print("p-значение: %.3f" % pvalue, "корреляция: %s" % corr)
check_coint_corr(X, Y)

Корреляция без коинтеграции

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

X_returns = np.random.normal(1, 1, 100)
Y_returns = np.random.normal(2, 1, 100)
X_diverging = pd.Series(np.cumsum(X_returns), name='X')
Y_diverging = pd.Series(np.cumsum(Y_returns), name='Y')
pd.concat([X_diverging, Y_diverging], axis=1).plot()

check_coint_corr(X_diverging, Y_diverging)

И нарисуем спред:

(Y_diverging-X_diverging).plot() # рисуем спрэд
plt.axhline((Y_diverging-X_diverging).mean(), color='red', linestyle='--') # добавляем среднее

Видим, что оба ряда имеют высокую корреляцию около 99% и проваливают тест на коинтеграцию с p-значением 0.885 (рядом с единицей). Мы видим, что спред имеет тенденцию роста со временем.

Коинтеграция без корреляции

Завершим знакомство примером стационарной пары без корреляции. Возьмем набор данных из нормального распределения и сравним его с квадратной волной:

Y2 = pd.Series(np.random.normal(0, 1, 1000), name='Y2') * 3 + 20
Y3 = pd.Series([30 if not (i//100)%2 else 10 for i in range(1, 1001)], name='Y3')
pd.concat([Y2, Y3], axis=1).plot()
plt.ylim([0, 40])

check_coint_corr(Y2, Y3)

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

🏁Вывод

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

💬В комментариях задавайте вопросы и напишите, где не хватает подробностей, а что можно описать иначе.

Александр Румянцев aka "i.am.raa"
Автор Quantrum.me
Интересуетесь алготрейдингом на Python? Присоединяйтесь к команде. Пишите в личку или на email.
🎓Обучение «Парному трейдингу» у профессионалов👍.