Торговая стратегия для SPY, использующее восстановления после сильного падения

Автор: Александр Кургузкин (mehanizator).

В недавней статье я показал, что изменение SPY на следующий день после сильных падений в среднем имеет положительное матожидание. То есть — сильные падения в основном выкупаются. Попробуем собрать торговую стратегию, которая будет использовать эту рыночную закономерность.

Как обычно, будем рассматривать ценовые изменения в нормированном виде, где в качестве нормы возьмем волатильность предыдущего месяца, т.е. в скользящем окне 21 торговых дней.

Если сегодня SPY упал, будем заходить в позицию, пропорциональную этому падению, нормированному на волатильность. Коэффициент пропорциональности определим из формулы для коэффициента Келли, с понижающим коэффициентом 0.33.

Смотрим что получилось, шкала логарифмическая:

Результат 40 за 11 лет это 3.6 в год в среднем, то есть +260% без реинвестирования. Просадки, конечно, будут нечеловеческими, поэтому как это обычно бывает, вряд ли кто решится торговать с «плечами по келли».

Шарп 1.03

Оборот стратегии за все время 50000, если оценить торговые издержки на SPY 0.01% в одну сторону, получается 5, т.е. где-то 8% доходности будут уходить в издержки.

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

Код рисеча на Котлине:


fun main(args: Array<String>) {

val spy = Sec(«spy», Date(103, 0, 1))

val normDays = 21

val norms = spy.getNormalization(normDays, NormalizationType.ABS)!!

val changesPast = spy.getPastLogChanges(1)!!

val changesFuture = spy.getFutureLogChanges(1)!!

// KELLY

val normalizedChangesFuture = Util.commonDates(norms, changesFuture)!!.map { date ->

date to changesFuture[date]!! / norms[date]!!

}.toSortedMap()

val variation = normalizedChangesFuture.values().fold(0.0, { v, x -> v + x * x }) / normalizedChangesFuture.size

val kelly = 0.33 / variation

// POSITIONS

val positions = Util.commonDates(norms, changesFuture, changesPast)!!.map { date ->

val normalizedPastChange = changesPast[date]!! / norms[date]!!

val position = if (normalizedPastChange < 0) Math.abs(kelly * normalizedPastChange) else 0.0

date to position

}.toSortedMap()

// TURNOVER

var lastPosition = 0.0

var turnover = 0.0

for ((d, p) in positions) {

val norm = norms[d]!!

val realPosition = p / norm

turnover += Math.abs(realPosition — lastPosition)

lastPosition = realPosition

}

// EQUITY RETURNS

val equityLogReturns = positions.mapValues { e ->

val (d, p) = e

p * changesFuture[d]!! / norms[d]!!

}.toSortedMap()

val eq = Util.cumulativeLogChanges(equityLogReturns)

val sharpe = Equity.getSharpe(equityLogReturns, 0.0)

«turnover: %7.4f, sharpe: %7.4f».f(turnover, sharpe).p()

Chart.showCharts(«», eq, 2.0)

}

Комментарии:

Andrew Kartashov: Отличная стратегия. И без добавок. А локальная волатильность … ну если смотреть по дневкам — не знаю. А для внутри дня мне ничего не давала — хватало и грубой прикидки, более точные и локальные — давали только ухудшение.
Так что и на дневках, имхо, будет хуже. Хотя не ручаюсь.

Конв: Код рисеча на Котлине:
fun main(args: Array) {

я правильно понимаю, что главный исполняемый метод программы возвращает fun? )))

mehanizator: fun это обозначение функции. функция main ничего не возвращает.

Салимжан Бижанов: ___Результат 40 за 11 лет это 3.6 в год в среднем, то есть +260% без реинвестирования. Просадки, конечно, будут нечеловеческими, поэтому как это обычно бывает, вряд ли кто решится торговать с «плечами по келли». ___

Александр, подскажи, пожалуйста, почему 260% без реинвестирования?? Как я понял 40 общая доходность — это логарифм доходности? то есть 3,6 в год — это не 260% годовых. нужно обратное преобразование сделать из логарифмической шкалы в обычную?

mehanizator: если с реинвестирование идет торговля, то нужно делать. а если без реинвестирования, то капитал без реинвестирования он как раз и будет равен логарифму капитала с реинвестированием.

Салимжан Бижанов: ещё вопрос: так как каждое значение исходного ряда логарифмов вы нормируете на волатильность, то единица измерения у вас становится именно волатильность. те доходность измеряется теперь не в логарифмах, не в %, а в волатильностях.

тогда почему вы говорите, что доходность 260%? ведь доходность получается 3,6 волатильностей в год? или я что-то не так понял?

mehanizator: здесь она уже переведена обратно из волатильностей в проценты/логарифмы


Подпишитесь на уведомления о новых постах

И получите доступ к специальным материалам сайта