Если мы возьмём пару коинтегрированных акций, то у нас есть возможность захеджироваться и построить рыночно-нейтральную стратегию, когда убытки по одной бумаге будут компенсироваться прибылями по другой. Как это выглядит на практике?
Торговая стратегия
Допустим, у нас есть коинтегрированная пара акций, и , а также цены этих акций за некий период времени . Для примера возьмём пару акций с тикерами (VSYDP,NKHP) из моей предыдущей статьи про коинтеграцию. Для неё у нас есть данные о ценах за 215 торговых дней.
Первую половину наблюдений мы будем использовать, чтобы определить параметры торговой стратегии. Затем, основываясь на найденных параметрах, мы возьмём вторую половину наблюдений и проведём бэктесты, то есть протестируем, принесёт ли нам такая стратегия деньги.
Для дальнейших рассуждений мне понадобится спред двух акций, . В нашем примере с парой (VSYDP,NKHP) мы уже находили спред в предыдущей статье про коинтеграцию, так что здесь я просто продублирую картинку:
Итак, все мы хотим покупать дёшево и продавать дорого. Если спред уходит ниже нуля, акция (VSYDP) дешевле, чем акция (NKHP). И, наоборот, если спред поднимается выше нуля, акция дороже, чем акция (ну или дешевле по сравнению с ).
Таким образом, по сути, торговая стратегия состоит в том, чтобы купить акцию и продать акцию в соотношении , если спред находится несколько ниже нуля (ниже линии на рисунке). Когда же спред возвращается обратно к нулю, нам нужно закрыть позицию, продав и купив в том же соотношении. В этом случае мы получаем прибыль размера :
Здесь важно понимать, что у нас должна быть возможность продавать акции, которыми мы не владеем, — это называется короткой продажей (шорт). Также необходимо отметить, что не все брокеры включают возможность короткой продажи в стандартный пакет услуг, так что, возможно, вам придётся дополнительно обратиться к нему для расширения своих инвестиционных возможностей.
В общем, мы составляем портфель, который содержит одну длинную позицию (лонг) и одну короткую позицию (шорт), если спред пересекает некоторую линию или уходит за неё в противоположную от нуля сторону. И мы закрываем все позиции, когда спред возвращается обратно к нулю.
Следующий вопрос, который возникает, — это «как найти значение »?
Как найти значение
Помимо математики, я сразу буду приводить здесь реализацию в матлабе. Приведённый ниже код является естественным продолжением кода из предыдущей статьи про коинтеграцию. Нам понадобится первая половина наблюдений, , которую мы будем рассматривать как «историю».
T = length(testPrices);
half = round(T/2);
Для начала найдём среднее отношение и для первой половины наблюдений.
sumRatio = 0;
for i = 1 : half
sumRatio = sumRatio + testPrices(i,1) / testPrices(i,2);
end
r = sumRatio / half;
Для пары акций с тикерами (VSYDP,NKHP) расчётное значение . Затем вычислим максимум абсолютного значения спреда для первой половины наблюдений:
clear absspread
for i = 1 : half
absspread(i,1) = abs(testPrices(i,1) - r * testPrices(i,2));
end
m = max(absspread);
Для пары акций с тикерами (VSYDP,NKHP) расчётное значение . Теперь мы можем определить значение путём перебора: возьмём некоторый процент от и попробуем поторговать на «истории» при различных значениях этого процента, а затем выберем то значение, которое даст наибольшую прибыль. Это и будет искомое значение для линии .
Перебор различных значений для поиска наилучшего
Для начала нам нужно посчитать количество трейдов. Первый трейд, обозначим его , мы делаем, когда впервые встаём в позицию. В этом случае мы ещё не получаем прибыль:
где — спред. Далее успешными моментами трейдов будут:
- если : ;
- если : .
Затем прибыль будет рассчитываться как , где — количество трейдов, — некоторый процент от . Если трейдов не было, тогда прибыль равна нулю. — это минимальная прибыль, если всегда торговать одним соотношением и .
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).
Процент | Трейды | Прибыль | |
---|---|---|---|
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 |
Чтобы определить значение , мы просто выбираем то значение, которое даёт наибольшую прибыль, основанную на «истории».
[M, I] = max(profit(:,4));
bestG = profit(I,2);
Однако, хотя (при 80% от ) даёт наибольшую прибыль, на практике мы не выбираем больше, чем , из-за трудностей, связанных с дальнейшим извлечением прибыли, поэтому возьмём (при 35% от ).
Тестирование стратегии
После определения торговая стратегия применяется ко второй половине наблюдений.
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
При прибыль извлекается 3 раза. Другими словами, разность 3 раза перемещается от 0 до и обратно. Обратите внимание, что реализация такой стратегии включает 6 трейдов, так как для того, чтобы встать в позицию и выйти из неё, требуется совершить два трейда.
На рисунке и в таблице ниже показаны все 6 моментов торговли.
Трейд | Позиция | Цена | Цена | Прибыль | ||
---|---|---|---|---|---|---|
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 |
Прибыль, полученная здесь, составляет не менее . Здесь используются цены закрытия вместо данных внутри дня, поэтому мы не торгуем в точках , 0 и . Как видно из таблицы, доходность за 107 торговых дней составила 25,39% без учёта комиссий, объёма и пр. То есть это примерно 60,74% годовых по очень грубым прикидкам.
Также были проведены тесты для 58 пар на NYSE. Там, если не считать нулевой прибыли, приблизительная годовая доходность при данной стратегии колебалась от 23,34% до 208% без учёта комиссий, объёма и пр.
Тестирование альтернативной стратегии
Вместо того, чтобы закрывать позицию, когда спред приближается к нулю, можно перевернуть позицию, когда спред достигает на противоположной стороне от нуля. Предположим, что мы продали и купили , так как разность была больше .
Теперь можно дождаться того момента, когда разность достигнет , купить и продать . В результате мы останемся с портфелем из длинной позиции размера и короткий позиции размера .
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
Такая стратегия приводит к одному начальному трейду и одному трейду, который переворачивает позицию. Эти трейды представлены на рисунке и в таблице ниже.
Трейд | Позиция | Цена | Цена | Прибыль | ||
---|---|---|---|---|---|---|
1 | 108 | 3145,9 | (-1; +35,6527) | 13200 | 282 | - |
2 | 134 | -1161,9 | (+1; -35,6527) | 8500 | 271 | 4307,8 |
Итого | 4307,8 |
Обратите внимание, что прибыль от перевертывания позиции равна , поэтому общая прибыль в данном случае составляет не менее . Как видно из таблицы, доходность за 107 торговых дней составила 18,53% без учёта комиссий, объёма и пр. То есть это примерно 44,32% годовых по очень грубым прикидкам.
Также были проведены тесты для 58 пар на NYSE. Там, если не считать нулевой прибыли, приблизительная годовая доходность при данной стратегии колебалась от 17,63% до 201,53% без учёта комиссий, объёма и пр.
Данное изменение в торговой стратегии уменьшает количество трейдов в среднем в 2 раза. При этом сокращаются торговые издержки. Если бы разность двигалась вверх и вниз около 0, альтернативная стратегия была бы более прибыльной. Однако в рассмотренном случае, когда у пары есть тенденция двигаться между 0 и , позиция вообще ни разу не переворачивается, тогда как основная стратегия создаёт и ликвидирует позицию снова и снова… и делает деньги.
Выводы
При торговле коинтегрированными парами, теоретически, можно извлекать стабильную прибыль, в частности, с помощью способов, описанных выше в виде двух торговых стратегий. В случае с рассмотренной парой акций (VSYDP, NKHP) первый способ оказался более эффективным из-за наличия в их разности тенденции колебаться ниже нуля.
Возможность извлечения стабильной прибыли при торговле коинтегрированными парами выглядит оптимистично, однако требует дальнейшего анализа на демо-счетах, о чём мы и поговорим в следующий раз.
Что почитать по теме
Терри Дж. Уотшем, Кейт Паррамоу. Количественные методы в финансах / Пер. с англ. под ред. М.Р. Ефимовой. — М.: Финансы, ЮНИТИ, 1999. — 527 с.
По этому учебнику в своё время преподавали для магистров количественный анализ в Вышке. Там есть раздел, посвящённый коинтеграции.
Комментарии (11)
win16
31.01.2018 18:29Спасибо за статью. Было интересно читать. Но тут есть принципиальная ошибка: как известно, коинтегрированность акций — это понятие врЕменное, но вы проводите форвард тестирование на интервале, про который точно знаете, что там акции коинтегрированы. То есть заглядываете в будущее. Вот если бы взяли набор акций, которые коинтегрированы первые полгода (конечно в них бы попали VSYDP и NKHP, но и еще несколько других), а потом проверили систему на второй половине года, тогда результат был бы другим: VSYDP и NKHP дали бы прибыль, а остальные скорей всего нет.
charypopper
31.01.2018 18:37А ведь может получиться что за период [t1;t2] пара не коинтегрирована, а за [t1;t3] — да (t3 > t2) и наоборот.
win16
31.01.2018 18:42Тоже верно :-) Так что не факт, что VSYDP и NKHP вообще попали бы в выборку коинтегрированных.
AdrenaLeen Автор
31.01.2018 18:58В режиме реального времени наличие коинтеграции проверяется каждый день. Соответственно, если в какой-то день пара перестаёт быть коинтегрированной, позиция закрывается. Это просто нужно учесть в алгоритме бэктестинга, если не хочется «заглядывать в будущее».
win16
31.01.2018 19:01Но ведь коинтегрированность не может в один день просто исчезнуть? Она же считается на каком-то периоде и затухать будет тоже постепенно. То есть будет запаздывание (как у скользящих средних)
AdrenaLeen Автор
31.01.2018 19:20Почему нет? Может. Коинтеграция — это, по сути, зависимость одного нестационарного временного ряда от другого нестационарного временного ряда. На каком-то периоде времени она может наблюдаться, при увеличении этого периода она может исчезать.
И там даже дело может быть не только в зависимости, а ещё и в интегрируемости самих временных рядов, то есть является ли ряд нестационарным со стационарными приращениями, I(1). Это свойство, о котором я говорила в статье про стационарность, со временем может теряться у той или иной акции в паре.
charypopper
Здравствуйте.
1) Соотношение в позиции не меняется со временем, и как я понял из предыдущей статьи высчитывалось по данным за весь период, не получается ли так, что вы берете данные для модели, чтобы торговать в момент времени t в котором эти данные еще не сформированы?
2) Если мы говорим про акции, то покупка возможна только целого числа актива. В ваших расчетах позиция выглядит так= (+1; -35,6527), но можно открыть либо -35 либо -36, и тогда вы будете торговать немного не по своей стратегии, а еще и активом которого больше, что отразится на прибыли и рисках.
AdrenaLeen Автор
1) Если вы про бету (коэффициент коинтеграции), то да, она высчитывалась по данным за весь период. Сейчас пересчитала за половину, она получилась равной 34,5806 (вместо 35,6527). На конечный результат по прибыли от двух стратегий в данном примере это никак не повлияло, однако в будущем для большей точности бэктеста, соглашусь с вами, стоит пересчитывать бету для половины наблюдений. Остальные данные, используемые для модели, я нахожу адекватными.
2) Вы заблуждаетесь. На Московской бирже есть режим торгов неполными лотами:
moex.com/s1433
Аналогичные условия предоставляет и Нью-Йоркская биржа.
charypopper
Спасибо за ссылку и перерасчет. И статьи (эта и предыдущая) хорошие, спасибо за предоставленные результаты о проделанной работе.
rumatavz
Про Нью-Йорк ничего не скажу, а на Мосбирже это скорее всего не возможно на практике. Во первых по ссылке посмотрите на время торгов. Во вторых наличие режима не означает наличие ликвидности там. Скорее всего там пустые стаканы. А в третьих нужно узнать можно ли частнику получить туда доступ, по умолчанию дают доступ к основным торгам.
AdrenaLeen Автор
1) Время торгов видела.
2) Насчёт ликвидности всё относительно. Глядя на Московскую Биржу можно сказать, что наличие биржи не означает наличие ликвидности там. Её и в режиме основных торгов может и не быть.
3) Насчёт доступа… зависит от брокера. Мой брокер, например, такую возможность предоставляет автоматически. В Квике это можно посмотреть через пункт меню Система — Заказ данных — Поток котировок. Выбираете там «Неполные лоты», и можно торговать параллельно с основной сессией.