Поиск по материалам

Парный трейдинг: стратегия, поиск пар, индикаторы и реализация на Python

·

Парный трейдинг — рыночно-нейтральная стратегия: одновременно лонг одной бумаги и шорт другой, коррелирующей. Заработок идёт не на движении рынка, а на временном расхождении пары. Разбираем, как искать пары на MOEX, чем коинтеграция отличается от корреляции, как считать хедж-коэффициент и Z-score спреда, какие индикаторы использовать, как реализовать стратегию на Python и где её главные ловушки — разрыв коинтеграции и корпоративные события эмитента.

Что такое парный трейдинг

Парный трейдинг (pairs trading) — стратегия одновременной покупки одного инструмента и продажи (шорта) другого, связанного с ним. Идея в том, что пара активов с общими фундаменталиями (один сектор, общая выручка, схожая бизнес-модель) исторически движется вместе. Когда они расходятся — рынок временно неэффективен, и расхождение должно сократиться.

Прибыль возникает не из движения рынка, а из сужения спреда между ценами пары. Если общий рынок вырастет на 5% — обе бумаги вырастут, ваш лонг приносит +5%, шорт даёт −5%, P&L по рынку нулевой. Если внутри пары спред сужается — заработок есть. Это и есть «рыночная нейтральность».

Стратегия родилась в институциональной среде (Morgan Stanley, конец 1980-х, отдел Нунцио Тартальи и Дэвида Шоу). С тех пор парный трейдинг — один из главных кирпичиков статистического арбитража и стратегий хедж-фондов.

Зачем рыночная нейтральность

Главное преимущество парного трейдинга по сравнению с направленной торговлей:

  • Не нужно угадывать направление рынка. Только относительное движение пары.
  • Меньшие просадки в кризисах. Когда рынок падает на 30%, портфель направленных лонгов теряет 30%; нейтральная парная книга — близка к нулю по дельте.
  • Большее количество сделок. Можно одновременно держать 5–20 пар, диверсифицируя риск каждой.
  • Стабильнее P&L. При правильно подобранных парах — серия мелких прибылей с предсказуемой волатильностью.

Главный недостаток — меньшая ожидаемая доходность по сравнению с трендовой торговлей. Парный трейдинг даёт типично 8–15% годовых при скрытом риске 5–10% maximum drawdown. Это не «обыграть рынок в три раза» — это «зарабатывать стабильно при низкой волатильности».

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

Это ключевая концепция. Большинство новичков ищут пары по корреляции — и ошибаются.

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

Коинтеграция — наличие линейной комбинации двух нестационарных рядов, которая стационарна. То есть: цены SBER и VTB — нестационарные ряды (они движутся как случайное блуждание). Но VTB − β × SBER — стационарный ряд, у которого есть среднее и есть возврат к нему. Это и есть условие применимости парного трейдинга.

Проверка коинтеграции — тест Энгла-Грейнджера:

from statsmodels.tsa.stattools import coint

# pair_a, pair_b — серии цен (pd.Series)
score, pvalue, _ = coint(pair_a, pair_b)
print(f"p-value коинтеграции: {pvalue:.4f}")
# pvalue < 0.05 → пара коинтегрирована

Альтернатива — тест Йохансена (для трёх и более бумаг одновременно), но для частной торговли пары достаточно Энгла-Грейнджера.

Хедж-коэффициент: какие позиции открывать

Когда коинтеграция есть, нужно понять в каком соотношении открывать лонг и шорт. Это и есть хедж-коэффициент β.

Считается через линейную регрессию одной бумаги на другую:

VTB_t = α + β × SBER_t + ε_t
import numpy as np
from sklearn.linear_model import LinearRegression

X = sber_prices.values.reshape(-1, 1)
y = vtb_prices.values
reg = LinearRegression().fit(X, y)
alpha, beta = reg.intercept_, reg.coef_[0]

print(f"VTB = {alpha:.4f} + {beta:.6f} × SBER")

Если β = 0.0003 — на каждый 1 ₽ движения SBER, VTB движется на 0.3 коп. Значит для рыночно-нейтральной позиции:

1 акция SBER (≈ 280 ₽) шорт = 1 акция × 280 = 280 ₽ экспозиции
N акций VTB лонг = 280 / β / VTB_цена

При VTB ≈ 0.012 ₽:

N = 280 / 0.0003 / 0.012 ≈ 77 700 акций ≈ 7770 лотов VTB (лот = 10000)

В России для большинства blue-chip бумаг проще брать денежно-нейтральную пару: одинаковая сумма в рублях лонг и шорт, без расчёта β. Это менее точно, но проще и не требует регулярного пересчёта β.

Z-score сигнала

После расчёта спреда ε_t = VTB_t − α − β × SBER_t — нормализуем его:

z_t = (ε_t − rolling_mean_60(ε)) / rolling_std_60(ε)

Сигналы:

  • z > 2 → продать VTB, купить SBER (спред «слишком вверх», ждём сужения).
  • z < −2 → купить VTB, продать SBER (спред «слишком вниз», ждём расширения).
  • |z| < 0.5 → закрыть позицию (вернулись к среднему).
  • |z| > 4 → стоп-лосс, фундаменталии пары сломались.

Чем шире пороги entry/exit — тем меньше сделок, но больше ожидание на каждой. Стандартное соотношение: entry = 2, exit = 0.5, stop = 4. Подгонка этих параметров — главный риск overfitting на бэктесте.

Реализация на Python: полный пример

import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import coint
from sklearn.linear_model import LinearRegression

def find_pairs(prices: pd.DataFrame, alpha=0.05) -> list[tuple]:
    """Возвращает список коинтегрированных пар."""
    pairs = []
    tickers = prices.columns.tolist()
    for i in range(len(tickers)):
        for j in range(i + 1, len(tickers)):
            t1, t2 = tickers[i], tickers[j]
            score, pvalue, _ = coint(prices[t1], prices[t2])
            if pvalue < alpha:
                pairs.append((t1, t2, pvalue))
    return sorted(pairs, key=lambda x: x[2])

def compute_spread(p1: pd.Series, p2: pd.Series, window=60):
    """Скользящая регрессия и Z-score спреда."""
    spread = []
    zscore = []
    for i in range(window, len(p1)):
        x = p1.iloc[i-window:i].values.reshape(-1, 1)
        y = p2.iloc[i-window:i].values
        reg = LinearRegression().fit(x, y)
        beta = reg.coef_[0]
        alpha = reg.intercept_
        eps = p2.iloc[i] - alpha - beta * p1.iloc[i]
        spread.append(eps)
        # Z-score по самому спреду в окне
        if len(spread) >= window:
            z = (eps - np.mean(spread[-window:])) / np.std(spread[-window:])
        else:
            z = 0
        zscore.append(z)
    return pd.Series(spread, index=p1.index[window:]), \
           pd.Series(zscore, index=p1.index[window:])

def backtest_pair(p1: pd.Series, p2: pd.Series,
                  entry=2.0, exit=0.5, stop=4.0):
    spread, z = compute_spread(p1, p2)
    position = 0  # +1 = long p2/short p1; -1 = short p2/long p1
    pnl = []
    entry_spread = 0
    for i in range(1, len(z)):
        if position == 0:
            if z.iloc[i] > entry:
                position = -1
                entry_spread = spread.iloc[i]
            elif z.iloc[i] < -entry:
                position = 1
                entry_spread = spread.iloc[i]
        else:
            if abs(z.iloc[i]) > stop:
                pnl.append(position * (entry_spread - spread.iloc[i]))
                position = 0
            elif abs(z.iloc[i]) < exit:
                pnl.append(position * (spread.iloc[i] - entry_spread))
                position = 0
    return pd.Series(pnl)

Полный pipeline:

# 1. Загружаем цены банковского сектора с MOEX
prices = pd.DataFrame({
    "SBER": load_close("SBER"),
    "VTBR": load_close("VTBR"),
    "TCSG": load_close("TCSG"),
    "MOEX": load_close("MOEX"),
    "BSPB": load_close("BSPB"),
})

# 2. Ищем коинтегрированные пары
pairs = find_pairs(prices)
for t1, t2, p in pairs[:5]:
    print(f"{t1} ↔ {t2}: p-value={p:.4f}")

# 3. Бэктестим лучшую пару
best = pairs[0]
result = backtest_pair(prices[best[0]], prices[best[1]])
print(f"средняя сделка: {result.mean():.2f}")
print(f"sharpe: {result.mean() / result.std() * np.sqrt(252):.2f}")

Это упрощённая модель — продакшен-версия должна учитывать комиссии (≥0.05% × 4 на каждую сделку: вход и выход × 2 ноги пары), проскальзывание, размер тика и доступность шорта.

Где работают пары на MOEX

Лучшие кандидаты — внутри одного сектора, где общие макро-факторы синхронизируют цены:

Банковский сектор

  • Сбер (SBER) ↔ ВТБ (VTBR) — классическая пара, исторически очень коинтегрированы.
  • Сбер ↔ Сбер-преф (SBERP) — внутрибумажная пара, спред определяется дивидендной премией.
  • Сбер ↔ ТКС (TCSG) — менее устойчиво, ТКС больше зависит от ритейла.

Нефтегаз

  • Лукойл (LKOH) ↔ Роснефть (ROSN) — обе на нефти Brent, очень похожая фундаменталия.
  • Лукойл ↔ Татнефть-преф (TATNP) — преф со специфической дивполитикой.
  • Газпром (GAZP) ↔ Новатэк (NVTK) — раньше работала, после санкций 2022 коинтеграция нестабильная.

Металлургия

  • Северсталь (CHMF) ↔ ММК (MAGN) — крупнейшие сталелитейные.
  • Северсталь ↔ НЛМК (NLMK) — пара тех же активов.
  • ГМК Норникель (GMKN) ↔ Полюс (PLZL) — драгметаллическая, но часто разваливается на корпоративных событиях.

Ритейл

  • Магнит (MGNT) ↔ X5 Retail (FIVE) — двое крупнейших, коинтегрированы.

Преф vs обыкновенные одной компании

  • Сургутнефтегаз обыкн (SNGS) ↔ Сургутнефтегаз-преф (SNGSP) — спред определяется специфической дивполитикой Сургута.
  • Татнефть (TATN) ↔ Татнефть-преф (TATNP) — относительно стабильная пара.
  • Роллеру (ROLO) ↔ Роллеру-преф (ROLOP) — для эмитентов с двумя классами акций.

Проверять коинтеграцию надо на свежих данных (последние 6–12 месяцев). Историческая коинтеграция не гарантирует текущую.

Размер позиции и риск

Несколько способов «равновесить» пару:

  1. Равные деньги (cash-neutral). Проще всего: одинаковая сумма в рублях лонг и шорт. Подходит для пар, где β близка к 1. Подходит большинству ритейл-сетапов.
  2. Равная дельта (β-neutral). Через расчёт β из регрессии. Точнее, но требует регулярного пересчёта.
  3. Равная волатильность (vol-neutral). Веса обратно пропорциональны исторической волатильности каждой ноги. Важно для пар с разной величиной свинга.

Размер общей позиции — фиксированный риск ≤1% депозита на пару. Для 10 одновременных пар совокупный максимальный убыток в один день не должен превышать 5% депозита.

Стоп — пробой |z| > 4 или ухудшение коинтеграции (если в скользящем окне p-value > 0.1). Тейк — возврат к нулевому Z или ослабление спреда до пол-стандартного отклонения.

Главные риски

  • Разрыв коинтеграции. Самая болезненная ловушка. Корпоративное событие (доп. эмиссия, делистинг, M&A, санкции на конкретного эмитента) ломает связь между бумагами навсегда. Спред идёт в одну сторону и не возвращается. Если удерживать позицию «по теории», убыток нарастает.
  • Корпоративные события одной ноги. Дивиденды одной бумаги пары без аналога у другой ломают спред на размер дивиденда. Перед отсечкой стоит закрывать позицию вручную или хеджировать ногу опционами.
  • Изменение фундаменталий. Сбер vs ВТБ были коинтегрированы до санкций 2022; после санкций ВТБ стал отдельной историей. Каждые 6 месяцев надо проверять, что пара осталась коинтегрированной.
  • Падение ликвидности. В стрессе ликвидность второй ноги пары может пропасть быстрее первой; вы окажетесь с непрогнозируемым «голым» риском.
  • Шорт-комиссии. В России шорт акций облагается комиссией за заём бумаг (0.5–4% годовых от объёма). На годовой горизонт это съедает значимую часть прибыли.

Парный трейдинг на форексе

На валютном рынке прямые пары менее очевидны (там все «пары» — это уже валютные пары как один инструмент). Но коинтеграционные сетапы существуют:

  • EUR/USD ↔ GBP/USD — обе движутся против доллара, часто коинтегрированы.
  • USD/CAD ↔ нефть Brent (фьючерс) — канадский доллар сильно завязан на нефть.
  • AUD/USD ↔ медь (фьючерс) — австралийский доллар на сырьё.
  • USD/JPY ↔ доходность 10-летних USTreasury — чистая carry-связь.

Для российского физлица в 2026 году доступ к Forex и иностранным фьючерсам ограничен; через MOEX и FORTS можно частично закрыть эти стратегии (например, Si vs Brent-фьючерс на ICE — но Brent на MOEX недоступен).

Индикаторы парного трейдинга

Часто ищут «индикаторы для парного трейдинга» в QUIK или TradingView. Прямых готовых нет — обычно используются производные индикаторы на спред:

  • Bollinger Bands на спред — классическая визуализация Z-score.
  • RSI на спред — экстремальные значения индикатора.
  • MACD на спред — для подтверждения смены направления.

В QUIK Lua можно посчитать спред парой строк кода:

local price1 = getParamEx("TQBR", "SBER", "LAST").param_value
local price2 = getParamEx("TQBR", "VTBR", "LAST").param_value
local beta = 0.0003   -- из бэктеста
local spread = price2 - beta * price1
-- далее любые индикаторы на серии spread

В TradingView можно построить кастомный символ через функцию (VTBR/SBER)*1000, что даёт нормированный спред — не идеально, но визуально работает.

Что запомнить

  • Парный трейдинг = одновременно лонг одной бумаги и шорт другой коинтегрированной. Заработок на сужении спреда.
  • Коинтеграция (стационарный спред) важнее корреляции (мгновенное соответствие движений).
  • Пара тестируется через тест Энгла-Грейнджера, p-value < 0.05.
  • Хедж-коэффициент β из линейной регрессии; для простоты допустимо cash-neutral балансирование.
  • Сигналы по Z-score спреда: вход при |z| > 2, выход при |z| < 0.5, стоп при |z| > 4.
  • Лучшие пары на MOEX — внутри секторов: банки (Сбер ↔ ВТБ), нефтегаз, металлургия, преф vs обыкновенные.
  • Главный риск — разрыв коинтеграции на корпоративных событиях; перепроверка пары каждые 6 месяцев обязательна.
  • Шорт-комиссии (0.5–4% годовых) и тонкая ликвидность второй ноги — практические факторы, ломающие математически верный сетап.
парный трейдинг статистический арбитраж стратегии Python MOEX