Хабрахабр

Python в помощь тестированию структурных продуктов

Воодушевлённый рекламой структурных продуктов на Хабре, адаптировал python-скрипт для их самостоятельного тестирования. Основная идея в том, что подобные продукты предлагают 100% защиту капитала.  А учитывая 10 лет бычьего рынка, исторические показатели подобных продуктов одурманивают безрисковым раем.

Ну а кому-то данный инструмент может пригодиться для самостоятельного построения подобных стратегий. Данная статья будет интересна начинающим python-программистам, которые интересуются управлением своим капиталом. Но будьте аккуратны, брокеры пишут, что это не каждому под силу.

Поехали!
Код выложен в GitHub в виде Jupyter-блокнота.

Пара слов, для введения

Тестировать буду на американских акциях и там доходность будет ниже, чем в рублях. Российский рынок в абсолютных значениях на графиках поинтересней, но и рисков в нём побольше. Суть тестов от этого не меняется.

Краткая инструкция в блокноте. Данные берём из бесплатного Alpha Advantages, где предварительно нужно получить ключ, поделившись email-адресом. Котировки российских бумаг вы можете взять на Финаме.

Обаяние структурного продукта

Кратко, ваш капитал в сохранности, а доходность выше банковского депозита (гособлигаций). Вот только пропущено несколько элементов уравнения:

  • По банковскому депозиту доход есть всегда, а здесь есть риск сыграть в ноль;
  • Вы получите прибыль, но на весомый кусок пирога претендует брокер;
  • Накладывается ограничение на использование вложенных денег;
  • Брокер практически не несёт никаких рисков, а участвует только в прибыли.

Стратегия

Рассмотрим самую простую стратегию:

  • Покупаем на 90% капитала краткосрочные казначейские облигации;
  • На остаток покупаем высокорискованный актив;
  • Ставим стоп на 10% от цены на старте периода.

В основе стратегии: казначейские облигации дают 1-3% годовых практически исключая просадку (если доходность есть). 10% от просадки актива, купленного на 10% капитала, как раз будут тем самым риском, который покроют облигации. В периоды бычьего рынка некоторые акции могут вырасти в несколько раз, что и подарит нам счастье.

Для ручного повторения данной стратегии необходимо выполнить следующие действия:

  • Купить облигации. Например, в виде ETF.
  • Купить акции.
  • Поставить стоп-приказ.

Как тестируем

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

Расписание

Производить ребалансировку можно в следующие периоды: неделя, месяц, год. А также в любой день внутри периода: первый, N-ый, последний. За это отвечает класс `Schedule()`:

# датафрейм с индексом из рабочих дней за период
df = pd.DataFrame([], index=pd.date_range(start, end, freq='B')) # ...
# фильтруем на даты наличия истории цен, при желании
df = df[df.index.isin(dates)].copy() # ...
# выбираем столбцы группировки
# ...
elif freq == 'week': groupby = ['year', 'week']
elif freq == 'month': groupby = ['year', 'month']
elif freq == 'year': groupby = ['year'] # группировка и пометка дней ребалансировки
grouped = df.groupby(groupby)
for idx, grp in grouped: if len(grp) >= abs(day): df.loc[grp.iloc[day].name, 'allow'] = True

Цикл по данным

StructuredProductMill().run()

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

Ребалансировка

StructuredProductMill().rebalance()

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

# получаем капитал: свободный кэш и рыночную стоимость позиций
balance = self._cash + self.position_balance(day) # объединяем позиции с текущим днём из истории цен
df = day.merge(self._positions[['quantity']], how='left', left_index=True, right_index=True) # ... # объём в процентах от исходной доли в портфеле относительно всего объема доступных активов
day.loc[is_allow, 'size_order'] = day[is_allow]['size'] / day[is_allow]['size'].sum()
# распределяем капитал по активам по цене открытия
day['position_to'] = (balance * day['size_order']) // day['open']
# формируем приказы изменения позиций
day['order'] = day['position_to'] - day['position'] # ... # исполняем сделки
for symbol, row in day[fltr].iterrows(): self.trade(row['dt'], symbol, row.order, row.open, 'O' if row.order > 0 else 'C')

Сделки

StructuredProductMill().trade()

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

Запуск

Для запуска необходимо указать набор активов с долями и параметры теста. Мы же будем тестировать структурные продукты за календарный год:

# состав портфеля
portfolio = # получение цен
SYMBOLS = list(portfolio.keys())
df = prices(SYMBOLS) params = { 'benchmark': 'SPY', # актив для сравнения доходности 'balance': 100_000, # начальный кэш 'portfolio': portfolio, 'rebalance_day': -1, # ребаланс в последний день периода 'freq': 'year', # ребаланс каждый год 'stop_loss': 0.1, # стоп-приказ в 10% # обнулять цены открытия позиций при ребалансе для корректных стопов 'reset_position_prices': True, 'allow_method': allow_default, 'start': pd.to_datetime('2011-01-01'), # дата начала
} # создаем объект, проверяем настройки и готовим данные
pm = StructuredProductMill(params, prices=prices(SYMBOLS + [params['benchmark']]), show_progress=True)
pm.check_params().prepare() # запускаем тестирование
pm.run()
# показываем результаты
pm.print_results();
# показываем графики
pm.charts()

Внизу блокнота есть графики с доходностью и просадками в даты ребаланса (в конце года), что подтверждает крайне низкие просадки капитала в моменты отчёта и постоянно растущую доходность. Хоть эта доходность и проигрывает широкому индексу американских компаний S&P 500.

Результаты

В тестах участвовали свободно торгующихся американские инструменты с 2011 года:

  • BIL — ETF на краткосрочные казначейские облигации с доходностью 2% годовых на момент написания статьи. Помним, что в период с 2009 до 2017 ставки были рядом с нулём. Альтернативой можно использовать MINT (фонд на краткосрочные инструменты с фиксированной доходностью).
  • AAPL — акции компании Apple.
  • MSFT — акции компании Microsoft.
  • TSLA — акции компании Tesla.

AAPL

Данная конструкция принесла за 8 лет доход в 24% (среднегодовая 2.6%) с просадкой между ребалансировками -6%. Но на стыке лет просадка около нуля. Стопа не коснулись, рынку со 180% дохода порядком проиграли.

Доходность и просадка за каждый день

Доходность и просадка за каждый день (слева доходность, справа просадка).

Доходность и просадка на стыке лет

Доходность и просадка на стыке лет (слева доходность, справа просадка).

MSFT

Данная конструкция принесла за 8 лет доход в 26% (среднегодовая 2.75%) с просадкой между ребалансировками -2%. На стыке лет просадка отсутствует.

Доходность и просадка за каждый день

Доходность и просадка на стыке лет

TSLA

Данная конструкция принесла за 8 лет доход в 45% (среднегодовая 4.6%) с просадкой между ребалансировками аж -15%. Но всё это в 2013 году, когда Тесла выросла почти в 5 раз. На стыке лет просадка до -2%. Самый беспокойный, но и прибыльный пассажир.

Доходность и просадка за каждый день

Доходность и просадка на стыке лет

Заключение

Блокнот позволяет тестировать любые составы портфелей. Это могут быть плечевые фонды или несколько компаний. Хоть вообще без защитного актива.

Репозиторий на GitHub.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть