Как стать автором
Обновить

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

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

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

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


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

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

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

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

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

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

В общем, мы составляем портфель, который содержит одну длинную позицию (лонг) и одну короткую позицию (шорт), если спред пересекает некоторую линию $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$;
  2. если $s_{t_n} \leq -g$: $t_{n+1} = \min t: t > t_n, s_t \geq g$.

Затем прибыль будет рассчитываться как $(d-1) \cdot 2g$, где $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 моментов торговли.

Трейд $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

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

Трейд $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 с.

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

UPD. Результаты бэктестов за 2017 год на Московской бирже
Теги:
Хабы:
Всего голосов 16: ↑15 и ↓1+14
Комментарии11

Публикации