Как Python помогает заменить финконсультантов

В продолжение статьи о вреде избыточной диверсификации создадим полезный инструментарий🛠️ по подбору акций. После этого сделаем простую ребалансировку⚖️ и добавим уникальные условия технических индикаторов📈, которых так часто не хватает в популярных сервисах. А затем сравним доходность отдельных активов и различных портфелей💼.

Во всем этом задействуем Pandas и минимизируем количество циклов. Погруппируем времянные ряды и порисуем графиков. Познакомимся с мультииндексами и их поведением. И всё это в Jupyter на Python 3.6🐍.

Если хочешь сделать что-то хорошо, сделай это сам.
Фердинанд Порше

Описанный инструмент позволит подобрать оптимальные активы для портфеля и исключить инструменты, навязываемые консультантами. Но мы увидим лишь общую картину — без учёта ликвидности, времени набора позиций, комиссий брокера и стоимости одной акции. В целом, при ежемесячной или ежегодной ребалансировке у крупных брокеров это будут незначительные затраты. Однако перед применением выбранную стратегию всё же стоит проверить в event-driven бэктестере, например, Quantopian (QP), дабы исключить потенциальные ошибки.

Почему не сразу в QP? Время. Там самый простой тест длится около 5 минут. А текущее решение позволит вам за минуту проверить сотни разных стратегий с уникальными условиями.

📥Загрузка сырых данных

Для загрузки данных возьмем метод, описанный в этой статье. Для хранения дневных цен я использую PostgreSQL, но сейчас полно бесплатных источников, из которых можно сформировать необходимый DataFrame.

Код загрузки истории цен из БД доступен в репозитории. Ссылка будет в конце статьи.

📦Структура DataFrame

При работе с историей цен, для удобной группировки и доступа ко всем данным, лучшим решением является использование мильтииндекса (MultiIndex) с датой и тикерами.

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

Вот пример, как можно легко группировать историю по неделям, месяцам и годам. И всё это показать на графиках силами Pandas:

Для корректного отображения области с легендой графика мы переносим уровень индекса с тикерами на второй уровень над колонками, используя команду Series().unstack(1). С DataFrame() такой номер не пройдёт, но решение есть ниже.

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

Пример получения истории цен определённого актива (берём все даты, тикер QQQ и все колонки):

🎢Ежемесячная волатильность активов

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

📊Сравним доходность активов

Теперь воспользуемся оконным методом Series().rolling() и выведем доходность активов за определённый период:

💼Методы ребалансировки портфелей

Вот мы и подобрались к самому вкусному. В примерах мы посмотрим результаты портфлеля при распределении капитала по заранее определённым долям между несколькими активами. А также добавим уникальные условия, по которым будем отказываться от некоторых активов в момент распределения капитала. Если подходящих активов не будет, то будем считать, что капитал лежит у брокера в кэше.

Для того чтобы при ребалансировке использовать методы Pandas, нам необходимо хранить доли распределения и условия ребалансировки в DataFrame с группированными данными. Теперь рассмотрим функции ребалансировок, которые будем передавать в метод DataFrame().apply():

По порядку:

  • rebalance_simple — самая простая функция, которая будет распределять доходность каждого актива по долям.
  • rebalance_sma — функция, распределяющая капитал по активам, у которых скользящая средняя за 50 дней выше значения за 200 дней на момент ребалансировки.
  • rebalance_rsi — функция, распределяющая капитал по активам, у которых значение индикатора RSI за 100 дней выше 50.
  • rebalance_custom — самая медленная и самая универсальная функция, где мы будем высчитывать значения индикатора из дневной истории цен актива на момент ребалансировки. Здесь можно использовать любые условия и данные. Даже загружать каждый раз из внешних источников. Но без цикла уже не обойтись.
  • drawdown — вспомогательная фукция, показывающая максимальную просадку по портфелю.

В функциях ребалансировки нам необходим массив всех данных на дату в разрезе активов. Метод DataFrame().apply(), которым мы будем рассчитывать результаты портфелей, передаст в нашу функцию массив, где колонки станут индексом строк. А если мы сделаем мультииндекс, где нулевым уровнем будут тикеры, то к нам придёт мультииндекс. Этот мультииндекс мы сможем развернуть в двумерный массив и получить на каждой строке данные соответствующего актива.

⚖️Ребалансировка портфелей

Теперь достаточно подготовить необходимые условия и сделать в цикле расчёт для каждого портфеля. Первым делом рассчитаем индикаторы на дневной истории цен:

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

Опишем структуру портфелей и укажем нужную ребалансировку. Портфели рассчитаем в цикле, так как нам необходимо указывать уникальные доли и условия:

В этот раз нам потребуется провернуть хитрость с индексами колонок и строк, чтобы получить нужный мультииндекс в функции ребалансировки. Добьёмся этого, вызвав последовательно методы DataFrame().stack().unstack([1, 2]). Данный код перенесет колонки в строчный мультииндекс, а затем вернет обратно мультииндекс с тикерами и колонками в нужном порядке.

📈Готовые портфели на графики

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

🏁Заключение

Рассмотренный код позволяет подбирать различные структуры портфелей и условия ребалансировок. С его помощью можно быстро проверить, стоит ли, например, держать в портфеле золото (GLD) или развивающиеся рынки (EEM). Попробуйте его сами, добавьте свои условия индикаторов или подберите параметры уже описанных. (Но помните об ошибке выжившего и о том, что подгонка под прошлые данные, может не оправдать ожидания в будущем.) А после этого решите, кому вы доверите свой портфель — Python-у или финкосультантy?

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

☝Хотите инвестировать в акции 🇺🇸США? А может в IPO? Начните от $100 в United Traders👍.

Репозиторий: rebalance.portfolio

Александр Румянцев
Автор на Quantrum.me
Telegram-канал📣: @quantiki

Подбор и тестирование портфелей. Подключение роботов к IB.