Торговая стратегия для торговли коинтегрированными парами акций

Цель данной статьи — поделиться простейшей стратегией статистического арбитража, основанной на торговле коинтегрированными парами акций, которые были выявлены на Московской и Нью-Йоркской биржах.

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

Торговая стратегия


Допустим, у нас есть коинтегрированная пара акций, $X$ и $Y$, а также цены этих акций за некий период времени $0,...,T$. Для примера возьмём пару акций с тикерами (VSYDP, NKHP) из моей предыдущей статьи про коинтеграцию. Для неё у нас есть данные о ценах за 215 торговых дней.

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

Для дальнейших рассуждений мне понадобится спред двух акций, $s_t = y_t - \beta x_t$. В нашем примере с парой (VSYDP, NKHP) мы уже находили спред в предыдущей статье про коинтеграцию, так что здесь я просто продублирую картинку:

18b23ca85f2e4cb4b427e54aa427e3e9.jpg


Итак, все мы хотим покупать дёшево и продавать дорого. Если спред уходит ниже нуля, акция $Y$ (VSYDP) дешевле, чем акция $X$ (NKHP). И, наоборот, если спред поднимается выше нуля, акция $Y$ дороже, чем акция $X$ (ну или $X$ дешевле по сравнению с $Y$).

Таким образом, по сути, торговая стратегия состоит в том, чтобы купить акцию $Y$ и продать акцию $X$ в соотношении $1 : \beta$, если спред находится несколько ниже нуля (ниже линии $G$ на рисунке). Когда же спред возвращается обратно к нулю, нам нужно закрыть позицию, продав $Y$ и купив $X$ в том же соотношении. В этом случае мы получаем прибыль размера $G$:

sstfl5n_26zzdu7rxbwcokn1ptw.jpeg


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

В общем, мы составляем портфель, который содержит одну длинную позицию (лонг) и одну короткую позицию (шорт), если спред пересекает некоторую линию $G$ или уходит за неё в противоположную от нуля сторону. И мы закрываем все позиции, когда спред возвращается обратно к нулю.

Следующий вопрос, который возникает, — это «как найти значение $G$»?

Как найти значение $G$


Помимо математики, я сразу буду приводить здесь реализацию в матлабе. Приведённый ниже код является естественным продолжением кода из предыдущей статьи про коинтеграцию. Нам понадобится первая половина наблюдений, $t = 0,...,T/2$, которую мы будем рассматривать как «историю».

T = length(testPrices);
half = round(T/2); 


Для начала найдём среднее отношение $Y$ и $X$ для первой половины наблюдений.

$\bar{r} = \frac{1}{T/2 + 1} \sum_{t=0}^{T/2}\frac{y_t}{x_t}.$


sumRatio = 0;
for i = 1 : half
    sumRatio = sumRatio + testPrices(i,1) / testPrices(i,2);
end
r = sumRatio / half;


Для пары акций с тикерами (VSYDP, NKHP) расчётное значение $\bar{r} = 34,3927$. Затем вычислим максимум абсолютного значения спреда для первой половины наблюдений:

$m = \max_t |y_t - \bar{r} x_t|, t=0,...,T/2.$


clear absspread
for i = 1 : half
    absspread(i,1) = abs(testPrices(i,1) - r * testPrices(i,2));
end
m = max(absspread);


Для пары акций с тикерами (VSYDP, NKHP) расчётное значение $m = 3204,4$. Теперь мы можем определить значение $G$ путём перебора: возьмём некоторый процент от $m$ и попробуем поторговать на «истории» при различных значениях этого процента, а затем выберем то значение, которое даст наибольшую прибыль. Это и будет искомое значение для линии $G$.

Перебор различных значений $G$ для поиска наилучшего


Для начала нам нужно посчитать количество трейдов. Первый трейд, обозначим его $t_1$, мы делаем, когда впервые встаём в позицию. В этом случае мы ещё не получаем прибыль:

$t_1 = \min t: |s_t| \geq g,$


где $s_t$ — спред. Далее успешными моментами трейдов будут:

  1. если $s_{t_n} \geq g$: $t_{n+1} = \min t: t > t_n, s_t \leq — g$» />; </li>
<li>если <img src=: $t_{n+1} = \min t: t > t_n, s_t \geq g$» />.</li>
</ol>

<p><br />
Затем прибыль будет рассчитываться как <img src=, где $d$ — количество трейдов, $g$ — некоторый процент от $m$. Если трейдов не было, тогда прибыль равна нулю. $(d-1) \cdot 2g$ — это минимальная прибыль, если всегда торговать одним соотношением $Y$ и $\beta X$.

    clear profit
    for h = 1:10
        g = 0.05 * h * m;
        profit(h,1) = 0.05 * h * 100;
        profit(h,2) = g;
        clear trade
        k = 1;
        for i = 1:half - 1
            if abs(spread(i)) >= g
                trade(1,1) = i;
                trade(1,2) = spread(i);
                trade(1,3) = -1;
                trade(1,4) = beta;
                trade(1,5) = testPrices(i,1);
                trade(1,6) = testPrices(i,2);
                trade(1,7) = 0;
                startIndex = i;
                k = 2;
                break
            end
        end
        if k == 1
            break
        end
        for i = startIndex:half - 1
            if (trade(k-1,2) <= -g) && (spread(i) <= g) && (spread(i+1) >= g)
                trade(k,1) = i+1;
                trade(k,2) = spread(i+1);
                trade(k,3) = -1;
                trade(k,4) = beta;
                trade(k,5) = testPrices(i+1,1);
                trade(k,6) = testPrices(i+1,2);
                trade(k,7) = 0;
                k = k + 1;
            end
            if (trade(k-1,2) >= g) && (spread(i) > -g) && (spread(i+1) <= -g)
                trade(k,1) = i+1;
                trade(k,2) = spread(i+1);
                trade(k,3) = 1;
                trade(k,4) = -beta;
                trade(k,5) = testPrices(i+1,1);
                trade(k,6) = testPrices(i+1,2);
                trade(k,7) = 0;
                k = k + 1;
            end
        end
        if exist('trade', 'var')
            tradesNumber = size(trade,1);
            profit(h,3) = tradesNumber;
            profit(h,4) = (tradesNumber - 1) * 2 * g;
        else
            profit(h,3) = 0;
            profit(h,4) = 0;
        end
    end 
    


    В таблице ниже показаны проценты и исходы для пары акций с тикерами (VSYDP, NKHP).

    Процент $G$ Трейды Прибыль
    5 160,2224 7 1922,7
    10 320,4449 5 2563,6
    15 480,6673 5 3845,3
    20 640,8898 5 5127,1
    25 801,1122 5 6408,9
    30 961,3347 5 7690,7
    35 1121,6 5 8972,5
    40 1281,8 3 5127,1
    45 1442 3 5768
    50 1602,2 3 6408,9
    55 1762,4 3 7049,8
    60 1922,7 3 7690,7
    65 2082,9 3 8331,6
    70 2243,1 3 8972,5
    75 2403,3 3 9613,3
    80 2563,6 3 10254
    85 2723,8 1 0
    90 2884 0 0


    Чтобы определить значение $G$, мы просто выбираем то значение, которое даёт наибольшую прибыль, основанную на «истории».

    [M, I] = max(profit(:,4));
    bestG = profit(I,2);
    


    Однако, хотя $G=2563,6$ (при 80% от $m$) даёт наибольшую прибыль, на практике мы не выбираем $G$ больше, чем $0,5m$, из-за трудностей, связанных с дальнейшим извлечением прибыли, поэтому возьмём $G=1121,6$ (при 35% от $m$).

    Тестирование стратегии


    После определения $G$ торговая стратегия применяется ко второй половине наблюдений.

    clear strategy
    for i = half + 1:T
        if abs(spread(i)) >= bestG
            strategy(1,1) = i;
            strategy(1,2) = spread(i);
            if spread(i) > 0
                strategy(1,3) = -1;
                strategy(1,4) = beta;
            else
                strategy(1,3) = 1;
                strategy(1,4) = -beta;
            end
            strategy(1,5) = testPrices(i,1);
            strategy(1,6) = testPrices(i,2);
            strategy(1,7) = 0;
            startIndex = i;
            break
        end
    end
    if exist('strategy', 'var')
        k = 2;
        for i = startIndex:T-1
            if (strategy(k-1,3) ~= 0)
                if (spread(i) >= 0) && (spread(i+1) <= 0)
                    strategy(k,1) = i+1;
                    strategy(k,2) = spread(i+1);
                    strategy(k,3) = 0;
                    strategy(k,4) = 0;
                    strategy(k,5) = testPrices(i+1,1);
                    strategy(k,6) = testPrices(i+1,2);
                    strategy(k,7) = strategy(k-1,2) - spread(i+1);
                    k = k + 1;
                end
                if (spread(i) <= 0) && (spread(i+1) >= 0)
                    strategy(k,1) = i+1;
                    strategy(k,2) = spread(i+1);
                    strategy(k,3) = 0;
                    strategy(k,4) = 0;
                    strategy(k,5) = testPrices(i+1,1);
                    strategy(k,6) = testPrices(i+1,2);
                    strategy(k,7) = spread(i+1) - strategy(k-1,2);
                    k = k + 1;
                end
            else
                if (spread(i) <= bestG) && (spread(i+1) >= bestG)
                    strategy(k,1) = i+1;
                    strategy(k,2) = spread(i+1);
                    strategy(k,3) = -1;
                    strategy(k,4) = beta;
                    strategy(k,5) = testPrices(i+1,1);
                    strategy(k,6) = testPrices(i+1,2);
                    strategy(k,7) = 0;
                    k = k + 1;
                end
                if (spread(i) >= -bestG) && (spread(i+1) <= -bestG)
                    strategy(k,1) = i+1;
                    strategy(k,2) = spread(i+1);
                    strategy(k,3) = 1;
                    strategy(k,4) = -beta;
                    strategy(k,5) = testPrices(i+1,1);
                    strategy(k,6) = testPrices(i+1,2);
                    strategy(k,7) = 0;
                    k = k + 1;
                end
            end
        end
    end
    if exist('strategy', 'var')
        totalProfit = sum(strategy(:,7));
    else
        totalProfit = 0;
    end
    


    При $G=1121,6$ прибыль извлекается 3 раза. Другими словами, разность 3 раза перемещается от 0 до $G$ и обратно. Обратите внимание, что реализация такой стратегии включает 6 трейдов, так как для того, чтобы встать в позицию и выйти из неё, требуется совершить два трейда.

    На рисунке и в таблице ниже показаны все 6 моментов торговли.

    wroazqck32bgpzbicb5u9we0nyc.jpeg


    Трейд $t$ $s_t$ Позиция $(Y,X)$ Цена $Y$ Цена $X$ Прибыль
    1 108 3145,9 (-1; +35,6527) 13200 282 -
    2 128 -211,447 Ликвидация 9700 278 3357,4
    3 134 -1161,9 (+1; -35,6527) 8500 271 -
    4 171 14,6605 Ликвидация 8500 238 1176,5
    5 205 -1184,5 (+1; -35,6527) 7800 252 -
    6 212 185,1035 Ликвидация 8100 222 1369,6
      Итого 5903,5


    Прибыль, полученная здесь, составляет не менее $3G = 3 \cdot 1121,6 = 3364,8$. Здесь используются цены закрытия вместо данных внутри дня, поэтому мы не торгуем в точках $-G$, 0 и $G$. Как видно из таблицы, доходность за 107 торговых дней составила 25,39% без учёта комиссий, объёма и пр. То есть это примерно 60,74% годовых по очень грубым прикидкам.

    Также были проведены тесты для 58 пар на NYSE. Там, если не считать нулевой прибыли, приблизительная годовая доходность при данной стратегии колебалась от 23,34% до 208% без учёта комиссий, объёма и пр.

    Тестирование альтернативной стратегии


    Вместо того, чтобы закрывать позицию, когда спред приближается к нулю, можно перевернуть позицию, когда спред достигает $G$ на противоположной стороне от нуля. Предположим, что мы продали $1Y$ и купили $35,6527X$, так как разность была больше $G$.

    Теперь можно дождаться того момента, когда разность достигнет $-G$, купить $2Y$ и продать $2 \cdot 35,6527X$. В результате мы останемся с портфелем из длинной позиции размера $1Y$ и короткий позиции размера $35,6527X$.

    clear strategyAlt
    for i = half + 1:T
        if abs(spread(i)) >= bestG
            strategyAlt(1,1) = i;
            strategyAlt(1,2) = spread(i);
            if spread(i) > 0
                strategyAlt(1,3) = -1;
                strategyAlt(1,4) = beta;
            else
                strategyAlt(1,3) = 1;
                strategyAlt(1,4) = -beta;
            end
            strategyAlt(1,5) = testPrices(i,1);
            strategyAlt(1,6) = testPrices(i,2);
            strategyAlt(1,7) = 0;
            startIndex = i;
            break
        end
    end
    if exist('strategyAlt', 'var')
        d = 2;
        for i = startIndex:T-1
            if (strategyAlt(d-1,2) >= bestG) && (spread(i) >= -bestG) && (spread(i+1) <= -bestG)
                strategyAlt(d,1) = i+1;
                strategyAlt(d,2) = spread(i+1);
                strategyAlt(d,3) = 1;
                strategyAlt(d,4) = -beta;
                strategyAlt(d,5) = testPrices(i+1,1);
                strategyAlt(d,6) = testPrices(i+1,2);
                strategyAlt(d,7) = strategyAlt(d-1,2) - spread(i+1);
                d = d + 1;
            end
            if (strategyAlt(d-1,2) <= -bestG) && (spread(i) <= bestG) && (spread(i+1) >= bestG)
                strategyAlt(d,1) = i+1;
                strategyAlt(d,2) = spread(i+1);
                strategyAlt(d,3) = -1;
                strategyAlt(d,4) = beta;
                strategyAlt(d,5) = testPrices(i+1,1);
                strategyAlt(d,6) = testPrices(i+1,2);
                strategyAlt(d,7) = spread(i+1) - strategyAlt(d-1,2);
                d = d + 1;
            end
        end
    end
    if exist('strategyAlt', 'var')
        totalAltProfit = sum(strategyAlt(:,7));
    else
        totalAltProfit = 0;
    end
    


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

    qsee3v9rqiiy_svr3q7ggx1lyay.jpeg


    Трейд $t$ $s_t$ Позиция $(Y,X)$ Цена $Y$ Цена $X$ Прибыль
    1 108 3145,9 (-1; +35,6527) 13200 282 -
    2 134 -1161,9 (+1; -35,6527) 8500 271 4307,8
      Итого 4307,8


    Обратите внимание, что прибыль от перевертывания позиции равна $2G = 2 \cdot 1121,6 = 2243,2$, поэтому общая прибыль в данном случае составляет не менее $1 \cdot 2G = 2243,2$. Как видно из таблицы, доходность за 107 торговых дней составила 18,53% без учёта комиссий, объёма и пр. То есть это примерно 44,32% годовых по очень грубым прикидкам.

    Также были проведены тесты для 58 пар на NYSE. Там, если не считать нулевой прибыли, приблизительная годовая доходность при данной стратегии колебалась от 17,63% до 201,53% без учёта комиссий, объёма и пр.

    Данное изменение в торговой стратегии уменьшает количество трейдов в среднем в 2 раза. При этом сокращаются торговые издержки. Если бы разность двигалась вверх и вниз около 0, альтернативная стратегия была бы более прибыльной. Однако в рассмотренном случае, когда у пары есть тенденция двигаться между 0 и $-G$, позиция вообще ни разу не переворачивается, тогда как основная стратегия создаёт и ликвидирует позицию снова и снова… и делает деньги.

    Выводы


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

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

    Что почитать по теме


    Терри Дж. Уотшем, Кейт Паррамоу. Количественные методы в финансах / Пер. с англ. под ред. М.Р. Ефимовой. — М.: Финансы, ЮНИТИ, 1999. — 527 с.

    По этому учебнику в своё время преподавали для магистров количественный анализ в Вышке. Там есть раздел, посвящённый коинтеграции.

    © Habrahabr.ru