Новые релизы okama

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

Сравнение точности следования SPY и VFINX

Сравним отклонение самого старого ETF на S&P500 SPDR c тикером SPY и первого индекса взаимного фонда от Vanguard (тикер VFINX).

x = ok.AssetList(['SP500TR.INDX', 'SPY.US', 'VFINX.US'])
x

image
Общая история у этих фонов доступна за последние 29 лет.
Можно посмотреть, кто из них отклонился больше за весь промежуток:

x.tracking_difference().plot()


Очевидное преимущество у взаимного фонда Vanguard.
Посмотрим тот же результат, но приведенный к готовым значениям:

x.tracking_difference_annualized().plot()


Видно, что в начале периода наблюдения фонд Vanguard несколько опережал индекс (как ему это удавалось - это отдельная история). SPY в среднем отставал от индекса на 1,5% за последние 29 лет.

При выборе между двумя этими фондами информации явно недостаточно. Инвестору важно знать, как фонд управлялся в последние 5-10 лет а не в начале 90х…
Для этого посмотрим скользящие отклонения, например, с окном в 5 лет:

x.tracking_difference_annualized(rolling_window=12*5).plot()


Мы видим, что в последние годы отклонения у индексных фондов не так сильно отличаются. Оба фонда отстают от SP500 TR менее чем на 1% ежегодно. У VFINX показатели более ровные и, начиная с 2020 года, несколько лучше чем у SPY. Секрет, как обычно, может быть в TER, но не только …

Версия 1.2.1
Для функции get_tangency_portfolio() в классе EfficentFrontier добавлен новый параметр cagr. При cagr=True функция рассчитывает точку с максимальный Коэффициентом Шарпа (MSR) для среднегодовой доходности, рассчитанной с помощью среднего геометрического (CAGR). По умолчанию, как и раньше, параметры MSR считаются для среднего арифметического доходности (для матожидания).

Исправления:

  • в классах AssetList и Portfolio теперь правильно считаются first_date и last_date для базовой валюты.

Версия 1.2.2 - последний релиз, поддерживающий Python 3.7

Исправления:

  • баг с совместимостью старой библиотеки importlib-metadata

Обновления:

  • новый формат данных FOREX потребовал обновления классов-наследников ListMaker (Portfolio, AssetList, EfficentFrontier и т.п.)

ВНИМАНИЕ: С новым форматом данных FOREX (он уже реализован в API) старые релизы библиотеки работать не будут. Требуется обновиться до 1.2.2

Разработка следующих версий библиотеки будет вестись с помощью Python 3.8

Версия 1.2.3 - релиз с минимальной версией Python 3.8

Новые версии okama больше не будут поддерживать легаси Python 3.7. Исключение старых версий Python было необходимо для корректной работы с новыми возможностями в Pandas, SciPy и Numpy. Старые версии питона постепенно перестают поддерживаться этими библиотеками.

Обновления:

  • метод EfficentFrontier().get_monte_carlo() теперь кроме риска и доходности возвращает веса сгенерированных портфелей

Исправления:

  • Portfolio().weights_ts возвращает правильный порядок активов, входящих в портфель

ВНИМАНИЕ: Google Colab все еще работает на Python 3.7. Пока они не перейдут на 3.8+ !pip install okama будет устанавливать версию 1.2.2 okama.

Версия 1.3.0 - Новые возможность для скользящих. Sequencies

Новые возможности библиотеки

  • параметр rolling_window доступен в функциях AssetList: index_corr(), index_beta(), tracking_error(). Теперь можно строить скользящие для Ошибки следования и для беты.
  • Две функции index_corr() и index_rolling_corr() слиты в одну в AssetList
  • AssetList, Prtfolio, EfficentFrontier и EfficentFrontierReb теперь являются sequences (последовательностями) и обладают методами __getitem__, __iter__.

Исправления ошибок

  • get_namspaces() и другие алиасы из init.py больше не совершают удаленных запросов по API при импорте (спасибо за помощь @alex)
  • EfficientFrontier.plot_pair_ef() падала при inflation=False
  • Можно использовать тикеры с точкой. Такие как BRK.B

Скользящая ошибка следования американских и европейских (UCITS) индексных ETF на S&P 500

Примеры использования новых возможностей версии 1.3.0.

Выбираем самые популярные индексные ETF на NYSE и LSE:
image

Строим 2-х летнюю скользящую для Ошибки следования (Tracking Error):

Разница впечатляет … не так ли?
0,2% ошибка следования американских ETF и около 5% для европейского фонда CSPX на Лондонской бирже.

Недавно опубликовал статью Методика сравнения индексных фондов. Tracking Difference и Tracking Error. Там подробно объясняются, откуда берутся подобные ошибки следования.

Использование итерируемых объектов

Теперь AssetList, Prtfolio, EfficentFrontier и EfficentFrontierReb - это последовательности (sequence).

Последовательность формируется из объектов класса ok.Asset.

Можно обращаться к активу по его порядковому номеру. Например, для того чтобы посмотреть текущую цену бумаги:
image

Или использовать в циклах:
image

Версия 1.3.1 - Поддержка Python 3.11. Исправление ошибок

Библиотека теперь работает с Python 3.11.

Исправление ошибок

  • в __init__.py добавлен импорта важных алиасов: symbols_in_namespace, no_dividends_namespaces

Улучшения в документации

В документации классов AssetList, Portfolio, EfficientFrontier, EfficientFrontierReb появились новые методы:

Раньше эти методы не присутствовали на okama.readthedocs.io, т.к. документация классов не показывала наследуемые методы.

Версия 1.3.2 - Поддержка Pandas 2.0.0+

okama теперь работает с Pandas 2.0.0 и более новыми версиями. К сожалению, обратной совместимости нет. okama не будет работать с Pandas 1.5.3 и более ранними релизами.

Новые возможности

  • новые периоды ребалансировки для портфелей (класс Portfolio и классы EfficienFrontier, EfficientFrontierReb): “half-year” and “quarter”. Теперь можно ребалансировать портфели по следующими стратегиям: без ребалансировки, ежемесячная ребалансировка, раз в квартал, раз в полгода, ежегодно
  • новый метод set_values_monthly() для класса Inflation и других макроэкономических классов. Теперь в анализе инфляции можно использовать прогнозные данные, изменять произвольно предыдущие значения
  • dividend_yield_annual новое свойство класса AssetList (ежегодные дивидендные доходности сравниваемых активов)
  • get_dividend_mean_yield() новые метод класса AssetList (средняя дивидендная доходность за выбранный период)
  • метод 'plot_cml() в EfficientFrontier получил аргументy_axe` для переключением между средним геометрическим (CAGR) и средним арифметическим доходности в графиках
  • asset_dividend_yield в in AssetList переименовано в dividend_yield

Подробности о новых методах и свойствах классов можно посмотреть в документации okama.

Версия 1.4.0 - Портфели с изъятиями и пополнениями (DCF)

В версии 1.4.0 сделаны первые шаги по поддержке дисконтированных денежных потоков в инвестиционных портфелях (класс Portfolio). Появилась возможность тестировать инвестиционные стратегии с изъятиями и пополнениями. Метод Монте-Карло позволяет прогнозировать “срок дожития” для портфеля с изъятиями.

Новые возможности

DCF методы для изъятий и пополнений в классе Portfolio

Новые атрибуты класса Portfolio (все новые атрибуты необязательны):

  • initial_amount - размер стартовых инвестиций в Portfolio. Значение FV (на дату last_date)

  • cashflow - размер ежемесячных изъятий/пополнений портфеля. Размер приводится как FV (на дату last_date). Негативные значения - это изъятия из портфеля. Положительные значения - пополнения. Значения денежных потоков дисконтируются ежемесячно на значение discount_rate.

  • discount_rate - значение ставки дисконтирования для расчета PV значений. По умолчания discount_rate равна None. Если ставка дисконтирования не определена, то значения дисконтируются на размер инфляции. Если данных по инфляции нет, то используется ставка по умолчанию, равная 5% годовых.

Новые методы dcf. в классе Portfolio

  • dcf.plot_forecast_monte_carlo() метод для отображения на графике прогнозных портфелей с изъятиями/пополнения, сгенерированных методом Монте-Карло. Прогнозные портфели опционально могут отображаться вместе или без тестирования стратегии на исторических данных (см. изображение).

  • dcf.monte_carlo_survival_period() метод для определения “срока дожития” портфеля. Срок дожития – период, когда баланс портфеля остается выше нуля (с учетом изъятий).

  • dcf.wealth_index свойство класса для расчета wealth index (история баланса портфеля). В отличие от Portfolio.wealth_index новое свойство рассчитывает временной ряд балансов с учетом изъятий и пополнений.

  • dcf.survival_period свойство класса для расчета “срока дожития” стратегии с учетом изъятий на исторических данных.

  • dcf.survival_date свойство класса для расчет даты “обнуления” баланса портфеля после серии изъятий.

  • dcf.cashflow_pv свойство класса для расчета дисконтированной величины ежемесячных изъятий (PV) на момент начала исторического периода (first_date).

  • dcf.initial_amount_pv свойство класс для расчета дисконтированной величины стартовых инвестиций на момент начала исторического периода (first_date).

Новые свойства класса Portfolio

  • assets_dividend_yield свойство класса для расчета дивидендной доходности за последние 12 месяцев для каждого из активов. Возвращается временной ряд доходностей за рассматриваемый промежуток дат.

  • dividends_annual свойство класса, возвращающее временной ряд суммы дивидендов за календарный год каждого из активов.

Новые методы и свойства класса AssetList

  • get_rolling_risk_annual() метод для расчета скользящего риска (стандартного отклонения доходности) для заданной длины окна в месяцах.

  • get_dividend_mean_yield()метод для расчета среднего арифметического годовой дивидендной доходности (LTM) за указанный период.

  • dividend_yield_annual свойство класса для расчета дивидендной доходности (LTM) на конец каждого календарного года.

Изменения в существующих методах и свойствах

  • Asset_List.risk_annual возвращает временной ряд риска, приведенного к годовым значениям (раньше возвращало значение на конец периода).

  • Portfolio.recovery_period возвращает временной ряд периодов восстановления активов после падания (раньше возвращало самый длинный период).

  • describe() метод дополнительно показывает среднее арифметическое доходности актива, приведенное к годовым значениям (классы Portfolio и AssetList).

  • новый аргумент xy_text в методе plot_assets() позволяет настроить смещение текстовых меток относительно отображаемой на графике точки с доходностью и риском актива (классы Portofolio, AssetList).

Новые примеры в формате Jupyter Notebooks

  • 04 investment portfolios with DCF.ipynb примеры инвестиционных портфелей с пополнениями / изъятиями (класс Portofolio). Применение метода Монте-Карло для прогнозирования сроков дожития портфелей с изъятиями.

  • 05 macroeconomics - inflation rates.ipynb примеры работы с классами макроэкономических данных: Inflation, Rate и Ratio. Инфляция, ставки центробанков стран мира, CAPE 10 Шиллера.

Исправление ошибок

  • Дубликаты тикеров больше не разрешены в списках активов и автоматически убираются, если это возможно. Актуально для классов: AssetList, Portfolio, EfficientFrontier, EfficientFrontierReb

Подробности о новых методах и свойствах классов можно посмотреть в документации okama.

Самое любопытное из релиза okama 1.4.0

В релизе 1.4.0 собрано довольно много нового. Всего не покажешь, да и зачем, если есть новые блокноты Jupyter Notebook с примерами. Но самое интересно, всё-таки показать стоит.

Изъятия и пополнения портфеля. Дисконтирование (DCF)

Самое прогрессивное изменение – включение в инвестиционные стратегии (класс Portfolio) возможности изъятия и пополнения. Другие метрики портфеля, такие как риск и доходность предполагают, что промежуточных денежных потоков нет. Поэтому новые методы и свойства класса пришлось изолировать. Теперь они доступны через конструкцию типа Portfolio.dcf.wealth_index.

Пример

Создаём консервативную инвестиционную стратегию из облигаций, акций и золота (60/30/10). Пенсионные накопления равны 10 млн. руб. (пусть это будет пенсионный портфель). Будущий пенсионер планирует ежемесячно снимать 40 тыс. руб. в качестве прибавки к пенсии. На сколько хватит сбережений? Иными словами – какой “срок дожития” в этой стратегии?

Создаем портфель:


discount_rate здесь умышлено равно None. Это значит, что мы соглашаемся дисконтировать изъятия на среднюю инфляцию. Для большинства это ОК. Если кто-то хорошо разбирается в DCF, то можно поставить свою ставку дисконтирования.

Ребалансировка портфеля делает один раз в год.

Для удобства портфелю присвоен символ symbol="retirement_portf.PF". Теперь на графиках портфель будет обозначаться этим символом.

Теперь можно протестировать стратегию вместе с изъятиями на исторических данных.


На графике видно, что баланс портфеля продержался выше нуля весь срок (21 год). Портфелю не удалось опередить инфляцию, хотя он держался долго - примерно до 2020 года. Но потом изъятия во время просадки его “подкосили”.

Прежде чем принимать какие-то инвестиционные решения, важно протестировать стратегию не только на исторических данных, но и спрогнозировать… На сколько денег хватит!? Тот факт, что портфель в прошлом продержался 21 год, не значит, что во всех случаях ему это удастся в будущем.

Дальше стоит побаловаться с методом Монте-Карло. Посмотрим, как портфель “выживает” на случайно сгенерированных данных доходности. Параметры генерации доходностей тем не менее совпадают с историческими параметрами стратегии.


Здесь мы сгенерировали 50 случайных сценариев. Прогноз сделан на 30 лет. Старт инвестиций произошел в начале периода (backtest=True) в 2003 году.
По графику видно, что большинство случайных сценариев “обнулили” портфель где-то между 2034 и 2050 годами. Но были и оптимистичные сценарии, когда через 30 лет баланс портфеля всё еще не был равен нулю. Не стоить забывать, что каждый месяц размер изъятий из портфеля индексируется на инфляцию.

Итак, всё выглядит довольно оптимистично… Но хорошо бы получить количественные оценки. На графике это сделать сложно.

Для этого будем генерировать не 100 сценариев а 1000. И прогноз будет не на 30 лет а на 50.


(Google Colab считал это 6 минут. Не ждите быстрых результатов)

Метод dcf.monte_carlo_survival_period() тоже генерирует случайные сценарии (выбрано нормальное распределение, но можно использовать логнормальное). Но в данном случае ничего рисовать не нужно. Это просто распределение “периодов дожития” во всех случайных сценариях.
Свойства этого распределения замечательно отображает функция Pandas describe().

Как видно, в среднем “срок дожития” стратегии составляет 29 лет.
25% перцентиль (пессимистичные сценарии) - 20 лет.
25 перцентиль (оптимистичные сценарии) - 37 лет.

Что очень неплохо для пенсионного портфеля.

П.С. Это не инвестиционная рекомендация. Не надо применять такой портфель :slight_smile: При помощи okama можно подобрать веса лучше и диверсифицировать портфель внутри каждого из классов.

Продолжаем знакомиться с новшествами версии 1.4.0…

Смотрим внимательней на риск

Инвесторов традиционно больше интересует потенциальная доходность. Риск тоже… но только во вторую очередь. Такого “крена” не удалось избежать и в окаме. По доходности есть масса метрик. Многие из них - с возможностью построения скользящих для наблюдением доходности на разных промежутках времени. А риск… он и есть риск. В окаме существует несколько метрик риска (стандартное отклонение, VaR, CVaR, полудисперсия и т.п.). Но все они за полный промежуток исторических данных. Не было никаких скользящих. В качестве эксперимента в версии 1.4.0 мы начали исправлять этот “перекос”. Пока только для класса AssetList

Теперь Asset_List.risk_annual возвращает не число, а временной ряд стандартного отклонения доходности, приведенного к годовым значениям.

Кроме того появился новый метод get_rolling_risk_annual() для получения скользящего риска с настраиваемым размером окна.

Пример

Создаём список активов из акций и облигаций США. У этого списка история с 1995 года.

al = ok.AssetList(["DJI.INDX", "SP500BDT.INDX"], inflation=True)

Теперь посмотрим, как менялся риск со временем:

al.risk_annual.plot()


Каждая точка графика отображает суммарную историю с начала периода наблюдений. Невооруженным глазом видно, что риск - довольно постоянная величина. У акций он менялся в пределах 15-17%. У облигаций от 5 до 6%. Это очень узкие рамки! Попробуйте посмотреть, как прыгала доходность… ничего подобного вы не увидите. Риск (стандартное отклонение доходности) - довольно мало меняется со временем.

Но может быть это правило “сломалось” в последние годы? Полная история наблюдений не поможет проверить. Если в последние годы и были изменения, они “утонут” в статистике предыдущих десятилетий.

Посмотрим долгосрочные 10-летние скользящие риска:

al.get_rolling_risk_annual(window=12 * 10).plot()


Теперь в каждой точке графика мы видим только статистику за предыдущие 10 лет (а не за весь период).
Но вывод всё тот же… риск меняется в очень узких (по сравнению с доходностью) промежутках.

П.С. Portfolio.risk_annual тоже возвращает временной ряд. А вот get_rolling_risk_annual еще не прикрутили. Это задача ближайшего релиза.

Ещё один пример из полезностей версии 1.4.0… видимо, уже последний

Как быстро восстанавливается портфель после просадок?

Когда работаешь с реальными инвестиционными стратегиями, рано или поздно возникает важный вопрос: Как быстро восстанавливается портфель после просадок? Можно получить с виду неплохую стратегию (например, консервативную). У неё будут приемлемые показатели риска/доходности. Но портфель будет долго восстанавливаться после просадок. Что не очень хорошо… особенно для пенсионных портфелей.

С этим помогает разобраться усовершенствованное Portfolio.recovery_period, которое теперь вместо одного числа (максимальный срок) возвращает историю периодов восстановления.

Пример

Возьмем пример консервативного портфеля 60/30/10, который я приводил выше.

История просадок портфеля:


portf.drawdowns.plot()


Было две крупных просадки. Одна во время кризиса недвижимости США. Другая – в последнее время. Визуально можно отметить, что во время кризиса недвижимости портфель восстановился быстрее.

Более точные данные по величине просадок:


portf.drawdowns.nsmallest()

image

Максимальная по глубине просадка произошла в 2008 году. Тогда портфель упал на 23%. В 2022 году размер просадки составил -22%. Это чувствительные снижения, но более или менее приемлемые для портфеля. Особенно, если портфель быстро “отскакивает”.

Проверяем периоды восстановления:


portf.recovery_period.plot(kind='bar')

Сразу посмотрим на данные с цифрами:

portf.recovery_period.nlargest()

image
В 2008 году портфель упал на 23% и восстановился за11 месяцев. В 2022 году история была хуже… Падение составило 22%, но баланс отрос обратно только за 20 месяцев. Это почти 2 года! Для пенсионного портфеля это больше, чем хотелось бы. Обычно во время таких длинных просадок пенсионер чувствует себя очень некомфортно. Деньги снимать необходимо, но делать это очень не хочется. Портфель чувствует себя еще хуже. Когда деньги снимаются во время глубоких просадок, приходится распродавать бумаги по довольно низким ценам. Баланс портфеля с учетом снятий сильно уходит вниз. И это может стать рубежом, после которого восстановить баланс уже не получится. На графике бэктестинга портфеля хорошо заметна эта неприятность в 2022 – 2023.

Вывод

Крайне важно смотреть не только на глубину просадок портфелей. Но и на сроки восстановления (recovery periods). Это особенно важно для стратегий, где предусмотрены изъятия.

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

Этот условный портфель требует дальнейшей модернизации. Прежде всего – более глубокой диверсификации облигаций. Индекс ОФЗ RGBITR, который занимает 60% портфеля, имеет длинную дюрацию (в состав индекса входят довольно длинные бумаги). Поэтому RGBITR и очень волатилен. Его лучше “разбавить” другими видами облигаций.

Кроме того, пропорция 60/30/20 может рассматриваться только как “учебная”. Она далека от оптимальной для консервативного портфеля.

3 сообщения были перемещены в эту тему: Современна теория портфеля. Распределение активов