Парный трейдинг: использование МНК для расчета дельты позиций

При торговле по стратегии «Парного трейдинга» часто встречаются пары🎏, где цены каждого актива сильно отличаются друг от друга. Для получения лучшей доходности📈 и сокращения риска📉 необходимо правильно определить размер⚖ сделки по каждому активу.

Сегодня мы рассмотрим расчет дельты позиций используя метод наименьших квадратов (МНК).

Тестировать будем в Quantopian, а код пишем на Python🐍.

🗃Краткая справка

  • За основу статьи взяты материалы с Quantopian.
  • Стратегия «Парного трейдинга» подробно описана в этой статье.
  • Поиск пар для торговли можно изучить здесь.
  • Выбор основания для сигналов z-оценки здесь.
  • Регрессионный анализ описан в Вики.
  • Линейная регрессия доступна в Вики.
  • Метод Наименьших Квадратов (МНК, Ordinary Least Squares, OLS) описан в Вики.

Тестировать будем на парах, найденных в прошлых статьях:

  • CIT, STT
  • ALNY, DATA
  • DRE, O

🎓Зачем нам линейная регрессия?

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

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

    \[ Y = a + b * X \]

a — смещение по оси Y.
b — угол наклона. Эту величину мы будем использовать для расчета размера позиций.

🛠Алгоритм расчета размера позиции

Для получения величины зависимости мы будем использовать Python-пакет statsmodels. Ниже исходный код использования OLS метода:

import statsmodels.api as sm

# ...

def hedge_ratio(Y, X, add_const=True):
    if add_const:
        # добавление постоянной величины для нахождения "a"
        X = sm.add_constant(X)
        model = sm.OLS(Y, X).fit()
        # модель содержит оба параметра: "a" и "b"
        # нам интересен только "b"
        return model.params[1]
    model = sm.OLS(Y, X).fit()
    return model.params.values

В массив X необходимо добавить колонку с постоянной величиной, для нахождения значения a.

В алгоритме мы используем 20-дневный период для построения регрессии, чтобы зависимость была наиболее актуальной. Сигнальную z-оценку мы также построим относительно значений угла регресии. Исходный код алгоритма:

# ...
# используем OLS для расчета размера позиций
context.lookback = 20
context.z_window = 20
Y = hist['close'][stocks[0]][-context.lookback:]
X = hist['close'][stocks[1]][-context.lookback:]
try:
    hedge = hedge_ratio(Y, X, add_const=True)      
except ValueError as e:
    log.debug(e)
    return

# собираем историю найденных значений
context.hedgeRatioTS = np.append(context.hedgeRatioTS, hedge)

# при недостаточной истории данных, выходим
if context.hedgeRatioTS.size < context.HRlag: 
    return 
# получаем величину отношения размера позиции за прошлый день, 
# считаем величину спреда и добавляем в историю
hedge = context.hedgeRatioTS[-context.HRlag] 
context.spread = np.append(context.spread, Y[-1] - hedge * X[-1])

# z-оценка на основе истории величины спреда 
z_hedge = 0 
if context.spread.size > context.z_window:
    # оставляем ограниченную длину истории величины спреда
    spreads = context.spread[-context.z_window:]        
    z_hedge = (spreads[-1] - spreads.mean()) / spreads.std()

# ...
# пример ордеров
order_target_percent(stocks[0], -1)
order_target_percent(stocks[1], 1*hedge)

👎CIT, STT

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

На верхнем графике:

  • торговля с помощью OLS;
  • сигналы по z-оценке на основе OLS;
  • без проверки коинтеграции.

На нижнем графике:

  • торговля позициями на равные суммы;
  • сигналы по z-оценке на основе доходности;
  • фильтр по наличию коинтеграции за предыдущие 120 дней.

👎ALNY, DATA

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

👎DRE, O

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

🎁Код в студию

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

🏁Вывод

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

Как объяснить успех торговли равными долями? Данные пары были найдены на спреде относительных значений. То есть мы сравнивали спреды ежедневных изменений цены и ушли от самих цен. Торговать таким спредом достаточно на равные суммы. Дополнительно мы постоянно проверяем стационарность спреда.

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

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

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