Python: Базовые показатели актива

21 сен 2018  Александр  Мыльцев  Все авторы

Большинство из финансистов, особенно из числа «старой школы», привыкло делать расчеты в EXCEL. Кто-то стал экспертом в этой области и автоматизировал всё или почти всё с помощью макросов и VBA. Но все-таки, каким бы ни был прекрасным старый добрый EXCEL, его возможности не безграничны, как в области работы с переменными и типами данными, так и по части быстродействия. Много и других проблем. Например, в плане приспособления к табличному «интерфейсу» ввода/вывода. Насколько хорош EXCEL для решения простейших математических задачек, настолько он и плох для более серьезных вычислений, где требуется повторяемость и автоматизация.

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

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


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

Для вычислений в СТП мы используем:

  • numpy - для векторных вычислений
  • pandas - для работы с табличными данными
  • cvxopt - для решения оптимизационных задач
  • flask и graphene - для предоставления GraphQL API (это важно, хотя и не относится напрямую к вычислениям)

Расчёт всех основных показателей портфеля чаще всего производится на основе месячных данных закрытия торгов. Можно использовать и дневные данные, но это приводит к некоторым сложностям, связанными с экстраполяцией результатов от масштабов дня к масштабу года (а именно в годовом измерении все привыкли видеть итоговые результаты). Годовые данные тоже можно использовать, но лишь в тех случаях, когда присутствует достаточный объем статистических данных. В реальности же мы имеем ситуацию, когда в большинстве случаев глубина данных для биржевых активов ограничена 10-20 годами в лучшем случае. Особенно такие ограничения характерны для российских активов.

Обозначим данные закрытия как   - от начального месяца с индексом 0 до месяца с индексом n. Например, для индекса S&P 500 за 13 месяцев с января 2016 года такими значениями будут:

> import numpy as np
> close = np.array([184.46203524, 184.30967886, 196.70350767, 197.47875798,
200.83817857, 201.54059247, 208.89103144, 209.14118692,
209.15747242, 205.5313027 , 213.10274701, 217.42514811,
221.31590369])

13 значений данных закрытия в этом примере нужны для того, чтобы получить из них 12 значений доходности.

Доходность

Доходность (Rate of Return) – это относительный прирост стоимости актива в каждом из периодов по отношению к предыдущему значению:

Для внутреннего представления временных рядов мы используем np.array, в котором нет встроенной операции, которая бы вычисляла относительный прирост, как например pct_change() в Pandas. Поэтому мы вычисляем так:

> ror = np.diff(close) / close[:-1]
> ror
array([-8.25949820e-04, 6.72445901e-02, 3.94121246e-03, 1.70115541e-02, 3.49741224e-03, 3.64712581e-02, 1.19754056e-03, 7.78684414e-05, -1.73370317e-02, 3.68383999e-02, 2.02831787e-02, 1.78946898e-02])

Обратим внимание на два важных момента:

  1. Деление применяется поэлементно для векторов
  2. В результате вычислений получилось 12 значений, а не 13. Выдумывать несуществующие значения, чтобы длина вектора close была равна длине вектора ror – чревато ошибками

Накопленная доходность

Накопленная доходность (Accumulated Rate of Return) – это итоговая доходность актива за определенный промежуток времени. С точки зрения математики накопленная доходность - кумулятивное произведение доходностей от начальной до текущей за каждый из периодов:

Вектор aror вычисляется в Python так:

> aror = (ror + 1.).cumprod() - 1.
> aror
array([-0.00082595, 0.0663631 , 0.07056586, 0.08877785, 0.09258576, 0.13243373, 0.13378987, 0.13387816, 0.11422007, 0.15526616, 0.17869863, 0.19979108])

Среднегодовая доходность

Среднегодовая доходность (Compound Annual Growth Rate) – это усреднённая доходность (среднее геометрическое) на выбранном периоде:

Обратим внимание на то, что  не обязательно целое число. Термин CAGR удобно использовать, так как он довольно часто используется в мире финансов - Compound annual growth rate и позволяет отличить среднегодовую доходность от, например, матожидания доходности.

Вычислим в Python значение CAGR для всего периода:

> years_total = aror.size / 12
> cagr = (aror[-1] + 1.) ** (1 / years_total) - 1.
> cagr
0.19979107573297994

Риск

Мы различаем два типа риска: месячный и приведённый к году. Приведенное к году значение наиболее важно, так как зачастую является главным итогом вычислений и важным параметром для конкретного актива. Приведенным этот параметр называется потому, что является расчётным. Его значение вычисляется и приводится к году, исходя из месячных показателей портфеля (риск и доходность).

Месячный риск равен стандартному отклонению месячных значений доходности:

где

Риск, приведённый к году вычисляется по формуле:

С выводом этой формулы можно ознакомиться в нашей статье Приведение месячных данных к годовым. Как избежать ошибки.

Важно отметить, что формула не ограничивает длину вектора Но на практике Global Investment Performance Standards (GIPS) не рекомендует приводить к году данные для периодов меньше года. Именно поэтому мы в начале статьи указали на необходимость загрузки минимально 13 значений данных закрытия. Это дает возможность получить приведенные к году значения риска и доходности (CAGR). 

Соответствующие значения в Python вычисляются так:

> risk_monthly = ror.std()
> risk_monthly
0.021733982481594843 > ror_mean = (1. + ror).mean() > risk_yearly = np.sqrt((risk_monthly**2 + ror_mean**2)**12 - ror_mean**24) > risk_yearly 0.08930419487025891

Инфляция

Инфляция - это изменение показателя индекса потребительских цен (обозначаем как CPI_Rate) за промежуток времени в процентах.

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

Инфляция через индекс потребительских цен:

Формула верна для российской инфляции, так как в России индекс потребительских цен считается в процентах. В США и ЕС индекс считается в пунктах, а инфляция вычисляется через относительный прирост индекса.

Математическое ожидание инфляции:

Накопленная инфляция:

Средняя инфляция:

Вычисление:

# US inflation for period from 2016-02 to 2017-1
>
inflation = np.array([0.000823, 0.004306, 0.004741, 0.004046, 0.003284, -0.001618, 0.000918, 0.002404, 0.001247, -0.001555, 0.000327, 0.005828]) > inflation_arithmetic_mean = inflation.mean() > inflation_arithmetic_mean 0.0020625833333333334 > inflation_accumulated = (inflation + 1.).cumprod() - 1. > inflation_accumulated array([0.000823 , 0.00513254, 0.00989788, 0.01398392, 0.01731385, 0.01566783, 0.01660022, 0.01904412, 0.02031487, 0.01872828, 0.01906141, 0.0250005 ]) > years_total = inflation.size / 12 > inflation_geometric_mean = (inflation_accumulated + 1.)**(1 / years_total) - 1. > inflation_geometric_mean 0.025000495851904336

Реальные значения

Реальные значения  – значения, скорректированные на значение инфляции. Реальные значения обычно вычисляются для доходности, накопленной доходности, средних доходностей.

Реальная доходность (один месяц):

Реальная накопленная доходность:

Среднегодовая реальная доходность:

Вычисляются значения в Python так:

> ror_real = (ror + 1.) / (inflation + 1.) - 1.
> ror_real
array([-0.00164759, 0.06179488, -0.00589828, 0.00298588, -0.01358129,
0.02048251, -0.01515116, -0.01861181, -0.03690224, 0.01777718,
0.00119892, -0.00693249])
> aror_real = (aror + 1.) / (inflation_accumulated + 1.) - 1. array([-0.00164759, 0.06091789, 0.06007339, 0.07376244, 0.07399084,
0.11496465, 0.11527605, 0.11268799, 0.09203551, 0.13402777,
0.15665123, 0.17052731]) > cagr_real = (cagr + 1.) / (inflation_accumulated[-1] + 1.)**(1/years_total) - 1. > cagr_real 0.17052731251198328

Следующая статья цикла: Python: Базовые показатели портфеля

Понравилась статья?

Самое интересное и важное в нашей рассылке

Анонсы свежих статей Информация о вебинарах Советы экспертов

Нажимая на кнопку "Подписаться", я соглашаюсь с политикой конфиденциальности


Комментарии (8)

  1. Konstantin 22 сентября 2018, 19:38 # 0
    давно искал материалы с примерами расчетов характристик в рамках СТП, чтобы не для эксель, спасибо, очень хорошая статья
    наверх