Исследуем реакцию SPY на сильные снижения

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

Неоднократно видел стратегии ловли отскоков на S&P 500 и продуктах, обычно сигналом на покупку считается пробитие N-дневного LOW рынка и закрытие близкое к минимуму дня. Я решил для начала посмотреть более общую картинку — как зависит изменение будущего дня от изменения текущего дня.

Изменением считаем логарифм закрытия к закрытию, оба изменения — прошлое и будущее — нормированы на волатильность, посчитанную в окне 21 день (число торговых дней в месяце). Данные — дневки SPY с 2003 года.

Опасаясь влияния выбросов, я разделил все данные на 20 примерно равных по числу дней групп и усреднил данные внутри каждой группы.

Вот что получилось:

По оси X — изменение за день, нормированное на месячную волатильность. По оси Y — будущее изменение, тоже нормированное. Красная линия — аппроксимация квадратичным полиномом.

Видно, что в левом хвосте получаем матожидание в районе 0.01 от нормировки. Что такое 0.01 по оси Y, если пересчитать в проценты? Волатильность приведена к году, значит в среднем составляет где-то в районе 0.2, то есть 0.01 для Y в процентах будет в среднем 0.2%. Транзакционные издержки на SPY вполне перепрыгиваем.

Для сравнения посчитал то же самое, но прошлые и будущие изменения берутся за 5 дней (торговая неделя). Зависимость еще более выражена:

Если кому вдруг интересно, как это исследование у меня выглядит на языке Kotlin, которым я стал в последнее время активно пользоваться:


fun main(args: Array<String>) {

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

val normDays = 21

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

val changesPast = spy.getPastLogChanges(5)!!

val changesFuture = spy.getFutureLogChanges(5)!!

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

val norm = norms[d]!!

val past = changesPast[d]!!

val future = changesFuture[d]!!

doubleArray(past / norm, future / norm)

} sort comparator { a, b -> a[0].compareTo(b[0]) }

val sz = xyList.size

val nGroup = 20

val groupSize = sz.toDouble() / nGroup

val groupMap = (0..sz — 1).groupBy { Math.floor(it / groupSize) }.toSortedMap()

val groupedList = groupMap.values().map { indexes ->

val xs = indexes.map { xyList[it][0] }

val ys = indexes.map { xyList[it][1] }

val xMean = xs.statistics().getMean()

val yMean = ys.statistics().getMean()

doubleArray(xMean, yMean)

}

val groupPastChange = groupedList.map { it[0] }

val groupFutureChange = groupedList.map { it[1] }

val extrapolation = Util.fitPolynomial(groupPastChange, groupFutureChange)!!

val extrapolated = groupPastChange.map { extrapolation.value(it) }

Chart.showCharts(«», groupPastChange, groupFutureChange, extrapolated, 5.0)

}

Автор: mehanizator


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

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