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 = {'MINT': 0.9, 'AAPL': 0.1,}
    
    # получение цен
    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.

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

      0
      100% защиту капитала в структурных продуктах, как правило, делают через покупку опционов PUT. Стопы использовать бесполезно, т.к. почти каждый день есть гэпы на открытии рынка, да и от «проскальзывания» стопы не защищают. В момент, когда рынок валится вниз, гэпы вообще гигантские.
        +1
        Тогда уж выгодней вместо актива купить Call на размер допустимой потери. И доход выше, и риск под 100% контролем. Но статья о простой стратегии, чтобы любой мог её протестировать. Также код стратегии проводит стоп приказ по цене Low, чтобы учесть всю боль.

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

      Самое читаемое