Это заключительная статья по автоматическому🤖 поиску пар🎏 для «Парного трейдинга» с помощью Python🐍. Способ самый быстрый🏎 и самый эффективный👍. Хотя эффективность достигается уже благодаря анализу полученного набора пар.
В основе данного способа лежит анализ скользящих средних каждого актива. Идея была взята здесь.
📽В предыдущих сериях
«Парный трейдинг» — стратегия торговли двух скоррелированных акций, двигающихся в одном направлении с разной скоростью. Надо одновременно купить отстающую и продать убежавшую. Рекомендовано к ознакомлению: Описание стратегии, Первый способ поиска пар, Второй способ поиска пар.
🎓Предположение
Экспоненциальные скользящие средние у пар, подходящих для «Парного трейдинга» будут находиться рядом и их можно отобрать по фильтру среднего значения спреда пары.
☝Выбор акций для поиска
- Американский рынок.
- История за предыдущие 360 календарных дней.
- Цена более $10.
- Средний объем более 500 тыс. акций в день.
- ATR за 13 дней более $0.40.
На 10 февраля 2017 таких всего 1287 штук, минимальная длина истории 245. После фильтрации на отсутствие коинтеграции осталось 1028 штук, что дает нам 0,63 млн. пар. Подготовка историй цен подробно рассмотрена здесь.
🔍3 из 3: спред EMA-50
Попробуем сравнивать спред между EMA-50 (средние за 50 дней). Фильтр по порогу среднего значения спреда давал много шлака, что склонило меня к использованию 70% перцентиля. То есть нам подойдут пары, если максимальное абсолютное значение 70% спреда меньше трех. Три — это произвольное число и для себя можете попробовать использовать любое другое.
Дополнительно необходимо проверять найденные пары на стационарность. Вспоминая прошлые наблюдения, эта проверка является крайне прожорливой к ресурсам, но в этот раз на нашей стороне предварительный фильтр по спреду EMA-50 и нам необходимо проверить только найденные пары, коих будет не много.
Стационарность проверяем методом Дики-Фуллера отсюда:
[code python]statsmodels.tsa.stattools.adfuller(X)[/code]
По порядку:
- EMA-50;
- 70% перцентиль abs(спреда) меньше 3;
- спред проверяем на стационарность.
Код на Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
def find_pairs_sma(symbol_prices, need_preparation=False): n = len(symbol_prices) symbols = list(symbol_prices.keys()) pairs = [] for i in range(n): for j in range(i+1, n): S1 = symbol_prices[symbols[i]] S2 = symbol_prices[symbols[j]] # подготавливаем ряды, если необходимо if need_preparation: S1, S2 = prepare_vectors(S1, S2, to_performance=True) # получаем скользящие средние period = 50 ema1 = talib.EMA(S1, timeperiod=period) ema2 = talib.EMA(S2, timeperiod=period) spread = (ema1[period-1:] - ema2[period-1:]) # среднее значение спреда MA spread_mean = abs(spread).mean() # медиана спреда MA spread_median = np.median(abs(spread)) # максимальное значение 70% значений по модулю спреда MA spread_percentile = np.percentile(abs(spread), 70) # добавляем пары со средним значением спреда EMA50 < 3 # ИЛИ медианой спреда EMA50 < 3 # ИЛИ процентилем 70% от спреда EMA50 < 3 if spread_percentile < 3: # проверяем стационарность result = adfuller(S1-S2) score, pvalue, crit = result[0], result[1], result[4] pairs.append((symbols[i], symbols[j], spread_median, spread_mean, spread_percentile, pvalue, score < crit['5%'])) return pairs |
На 10 февраля 2017 года метод нашел 2056 пар (~0.32%), включая не стационарные. Поиск загружал все ядра процессора, кроме одного и занял почти… 3 минуты, в отличие от второго способа, затянувшегося на 2 часа.
В этот раз, для исключения шлака, проведем сбор дополнительных данных и отфильтруем плохие пары. А искать будем следующее:
- Стандартное отклонение спреда за последние 2 месяца.
- Количество пересечений сигнальной z-оценкой -1, 0 и 1.
Отфильтруем резкое падение стандартного отклонения за последние 2 месяца, оставим только стационарные пары и упорядочим по количеству пересечений z-оценки нуля. Нам будет доступна всего 1731 пара. Это много и код ниже позволит вам отфильтровать пары по необходимым признакам.
Код на Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
results = [] for s1, s2, median, mean, percentile, pvalue, coint in pairs: res = { 'Symbol1': s1, 'Symbol2': s2, 'Median': median, 'Mean': mean, 'Percentile': percentile, 'pvalue': pvalue, 'coint': coint } P1, P2 = symbol_performance[s1], symbol_performance[s2] spread = P1 - P2 res['std'] = np.std(spread) res['std(-40)'] = np.std(spread[-40:]) z = ((spread - spread.mean()) / np.std(spread)) res['x(0)'] = len(z[abs(np.insert(np.diff(np.sign(z)), 0, 0)) == 2]) res['x(-1)'] = len(z[abs(np.insert(np.diff(np.sign(z+1)), 0, 0)) == 2]) res['x(+1)'] = len(z[abs(np.insert(np.diff(np.sign(z-1)), 0, 0)) == 2]) results.append(res) d = pd.DataFrame(results) # только стационарные fltr = d['coint'] == True # фильтр низкой сигмы за последние ~2 месяца fltr = (fltr) & (d['std(-40)'] > 0.55) print(len(d[fltr])) d[fltr].sort_values(['x(0)'], ascending=False).head(10) |
Вот пять пар с наибольшим количеством пересечений нуля z-оценкой:
- SNV, KBWB
- WEC, DUK
- ETFC, SCHW
- XLI, DIA
- FULT, KBE
- …
За время экспериментов заметил, что истории цен различаются в зависимости от источника. В частности, с этим связано добавление фильтра падения сигмы за последние два месяца.
🎏Проверка найденных пар
В проверке будут участвовать следующие пары:
- SNV, KBWB
- ETFC, SCHW
- XLI, DIA
👌SNV, KBWB
SNV — банк.
KBWB — ETF на банки.
Спред стационарен и дает сигналы для торговли.
👌ETFC, SCHW
ETFC — инвестиционный брокер.
SCHW — инвестиционный брокер.
Одна группа, стационарный спред, достаточное количество сигналов.
👌XLI, DIA
XLI — ETF на промышленный сектор.
DIA — ETF на промышленный индекс Доу Джонс.
Единая направленность фондов, достаточное количество сигналов и визуальное соответствие.
🏁Вывод
Анализ результатов поиска данным способом оставляет положительные впечатления. Все найденные пары, включая пары с минимальным количеством пересечений нуля сигнальной z-оценкой (мин. 8), удовлетворяют базовым требованиям стратегии «Парного трейдинга». Способ работает очень быстро и подходит для беглого обзора рынка.
В следующий раз вернемся к бэктестингу найденных пар на платформе Quantopian. Бэктестинг проведем, используя пары, найденные текущим способом.
💬В комментариях напишите, чего вам не хватает в этой статье или укажите на ошибки. Если нужен полный исходный код, также упомяните об этом в комментариях.
Автор Quantrum.me
Telegram-канал📣: @quantiki
Подбор и тестирование портфелей. Подключение стратегий к IB.