Last active
April 8, 2019 18:52
-
-
Save alexander-myltsev/4ae791088cf94504006d55cac5033a19 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import yapo\n", | |
"import numpy as np" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## >>> Инициализация данных. Не будет в конечной статье" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"TimeSeries(start_period=2015-01, end_period=2016-03, kind=TimeSeriesKind.VALUES, values=[183.30505169 193.60763852 190.55948884 192.43343373 194.90738578\n", | |
" 190.95253351 195.26600501 183.36451696 178.70115422 193.90141823\n", | |
" 194.61015112 191.26732302 181.74477321 181.59466115 193.80591971]\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"array([183.30505169, 193.60763852, 190.55948884, 192.43343373,\n", | |
" 194.90738578, 190.95253351, 195.26600501, 183.36451696,\n", | |
" 178.70115422, 193.90141823, 194.61015112, 191.26732302,\n", | |
" 181.74477321, 181.59466115, 193.80591971])" | |
] | |
}, | |
"execution_count": 2, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"snp_asset = yapo.portfolio_asset(name='ny/SPY', \n", | |
" start_period='2015-1', end_period='2016-3', currency='usd')\n", | |
"print(snp_asset.close())\n", | |
"snp_values = snp_asset.close().values\n", | |
"snp_values" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"TimeSeries(start_period=2015-01, end_period=2016-03, kind=TimeSeriesKind.DIFF, values=[-4.706e-03 4.343e-03 5.952e-03 2.033e-03 5.097e-03 3.503e-03\n", | |
" 6.700e-05 -1.416e-03 -1.557e-03 -4.500e-04 -2.111e-03 -3.417e-03\n", | |
" 1.653e-03 8.230e-04 4.306e-03]\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"array([-4.706e-03, 4.343e-03, 5.952e-03, 2.033e-03, 5.097e-03,\n", | |
" 3.503e-03, 6.700e-05, -1.416e-03, -1.557e-03, -4.500e-04,\n", | |
" -2.111e-03, -3.417e-03, 1.653e-03, 8.230e-04, 4.306e-03])" | |
] | |
}, | |
"execution_count": 3, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"infl_usd = yapo.inflation(currency='usd', kind='values', start_period='2015-1', end_period='2016-3')\n", | |
"print(infl_usd)\n", | |
"infl_usd_values = infl_usd.values\n", | |
"infl_usd_values" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## ??? ЗАГОЛОВОК СТАТЬИ ???" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Ошибки при написании программ неизбежны. В этой статье будет рассказан один из способов, которым можно контролировать правильность выполнения операций над временными рядами.\n", | |
"\n", | |
"Начнём с создания данных для дввх временных рядов: \n", | |
"- значения индекса S&P с января 2015 года по март 2016\n", | |
"- значения инфляции США с января 2015 года по март 2016" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([183.30505169, 193.60763852, 190.55948884, 192.43343373,\n", | |
" 194.90738578, 190.95253351, 195.26600501, 183.36451696,\n", | |
" 178.70115422, 193.90141823, 194.61015112, 191.26732302,\n", | |
" 181.74477321, 181.59466115, 193.80591971])" | |
] | |
}, | |
"execution_count": 4, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"snp_values" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([-4.706e-03, 4.343e-03, 5.952e-03, 2.033e-03, 5.097e-03,\n", | |
" 3.503e-03, 6.700e-05, -1.416e-03, -1.557e-03, -4.500e-04,\n", | |
" -2.111e-03, -3.417e-03, 1.653e-03, 8.230e-04, 4.306e-03])" | |
] | |
}, | |
"execution_count": 5, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"infl_usd_values" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Посчитаем реальное значение индекса S&P:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([184.17649226, 192.76611229, 189.42607285, 192.04098141,\n", | |
" 193.91390959, 190.28247101, 195.25285607, 183.6259473 ,\n", | |
" 178.98138524, 193.98916336, 195.02395769, 191.92655305,\n", | |
" 181.44319461, 181.44450932, 192.97068195])" | |
] | |
}, | |
"execution_count": 6, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(snp_values + 1.) / (infl_usd_values + 1.) - 1." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Несмотря на успешность выполнения, в этом подсчёте нет никакого смысла: реальные значения считаются от относительных изменений индекса, а не от его абсолютных значений. Ни `numpy.array`, ни встроенные средства `Python` не подсказывают об ошибке.\n", | |
"\n", | |
"Как говорилось в [предыдущей статье](https://rostsber.ru/publish/stocks/python_asset.html), накопленная доходность считается так:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([ 0.0562046 , -0.01574395, 0.00983391, 0.01285614, -0.02029093,\n", | |
" 0.02258923, -0.06095013, -0.0254322 , 0.08505969, 0.00365512,\n", | |
" -0.01717705, -0.0497866 , -0.00082595, 0.06724459])" | |
] | |
}, | |
"execution_count": 7, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"snp_ror = np.diff(snp_values) / snp_values[:-1]\n", | |
"# check:\n", | |
"# print(snp_asset.rate_of_return().values - snp_ror)\n", | |
"snp_ror" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Посчитаем **реальные значения** накопленной доходности. Какой из следующих двух вариантов подсчёта реальной доходности правильный?" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([ 0.05163734, -0.02156758, 0.00778508, 0.0077198 , -0.02371087,\n", | |
" 0.02252073, -0.05961855, -0.02391243, 0.08554818, 0.00577832,\n", | |
" -0.01380723, -0.05135471, -0.00164759, 0.06266874])" | |
] | |
}, | |
"execution_count": 8, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"snp_ror_real_1 = (snp_ror + 1.) / (infl_usd_values[1:] + 1.) - 1.\n", | |
"snp_ror_real_1" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([ 0.0611986 , -0.02000009, 0.00385894, 0.01080119, -0.02525918,\n", | |
" 0.01901961, -0.06101304, -0.02405025, 0.08675176, 0.00410697,\n", | |
" -0.01509792, -0.04652859, -0.00247486, 0.06636697])" | |
] | |
}, | |
"execution_count": 9, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"snp_ror_real_2 = (snp_ror + 1.) / (infl_usd_values[:-1] + 1.) - 1.\n", | |
"snp_ror_real_2" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# check\n", | |
"snp_asset.rate_of_return(real=True).values - snp_ror_real_1" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Правильный ответ: первый. Проблема была в неправильной проекции `infl_usd_values`: во втором варианте временной ряд доходности с февраля 2015 по март 2016 была поделена на временной ряд инфляции с января 2015 по февраль 2016 года. Такие ошибки возможно детектировать во время исполнения программы.\n", | |
"\n", | |
"Теперь посчитаем реальное значение CAGR за 1 год:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"0.0\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"0.017036311812289373" | |
] | |
}, | |
"execution_count": 11, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"years_ago = 1\n", | |
"snp_cagr = (snp_ror[-years_ago * 12:] + 1.).prod() ** (1 / years_ago) - 1.\n", | |
"# check:\n", | |
"print(snp_asset.compound_annual_growth_rate(years_ago=1).value - snp_cagr)\n", | |
"snp_cagr" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([ 2.033e-03, 5.097e-03, 3.503e-03, 6.700e-05, -1.416e-03,\n", | |
" -1.557e-03, -4.500e-04, -2.111e-03, -3.417e-03, 1.653e-03,\n", | |
" 8.230e-04, 4.306e-03])" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"infl_usd_values[-years_ago*12:]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"(12,)\n", | |
"(12,)\n" | |
] | |
} | |
], | |
"source": [ | |
"# check\n", | |
"print(snp_ror[-years_ago * 12:].shape)\n", | |
"print(infl_usd_values[-years_ago * 12:].shape)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"0.010489875626582323" | |
] | |
}, | |
"execution_count": 14, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"infl_usd_accumulated = (infl_usd_values[-years_ago * 12 + 1:] + 1.).prod() - 1.\n", | |
"snp_cagr_real = (snp_cagr + 1.) / (infl_usd_accumulated + 1.) - 1.\n", | |
"snp_cagr_real" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"В канве статьи, здесь также есть ошибка. Оказывается, по аналогии с расчётом `snp_ror_real` программист решил взять инфляцию без первого значения, т.е. за 11 месяцев вместо 12, затем посчитал аккумулированное значение, а по сути разделил одно число, равное произведению 12 чисел, на другое число, равное произведению 11 чисел!\n", | |
"\n", | |
"Правильный расчёт будет такой:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"0.017036311812289373\n", | |
"0.017036311812289373\n", | |
"0.00843971768053775\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"0.00843971768053775" | |
] | |
}, | |
"execution_count": 15, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"years_ago = 1\n", | |
"snp_cagr = (snp_ror[-years_ago * 12:] + 1.).prod() ** (1 / years_ago) - 1.\n", | |
"# check:\n", | |
"print(snp_asset.compound_annual_growth_rate(years_ago=1).value)\n", | |
"print(snp_cagr)\n", | |
"\n", | |
"infl_usd_accumulated = (infl_usd_values[-years_ago * 12:] + 1.).prod() - 1.\n", | |
"snp_cagr_real = (snp_cagr + 1.) / (infl_usd_accumulated + 1.) - 1.\n", | |
"# check:\n", | |
"print(snp_asset.compound_annual_growth_rate(years_ago=1, real=True).value)\n", | |
"snp_cagr_real" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Для отлова таких ошибок необходима \"расширенная\" версия `np.array`, которая бы:\n", | |
"\n", | |
"- имела мета-информацию над значениями `np.array`: начало и конец периода, уровень частичных значений (чтобы отличать значения от прироста, и \"прироста от прироста\" более высоких подярков, если такое понадобиться)\n", | |
" - например, уровень частичных значений для индекса S&P равен 0, а для инфляции и накопленной доходности -- 1 \n", | |
"- иметь такой же интерфейс методов как `np.array`, включая арифметические операции, достаточное для текущих задач множество, но расширяемое по мере необходимости\n", | |
"- валидировать каждый вызов метода: соответствующие начальный период, конечный период, и уровень частичных значений должны совпадать\n", | |
"\n", | |
"Мне известны два принципиальных способа:\n", | |
"- вести реестр (хеш-таблица), в которой записывать всю мета-информацию, но всё равно придётся перегружать стандартные алгебраические операции, чтобы провалидировать данные\n", | |
"- расширить класс `np.array` через композицию или наследование. Расширение через наследование, согласно [numpy API](https://docs.scipy.org/doc/numpy-1.15.0/user/basics.subclassing.html), особых преимуществ не даёт, а напротив может создать проблемы в случае изменения `numpy API`. Поэтому будет расширять через композицию:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import pandas as pd\n", | |
"\n", | |
"class TimeSeries:\n", | |
" def __init__(self, values, start_period: pd.Period, end_period: pd.Period, diff_level):\n", | |
" if not isinstance(values, np.ndarray):\n", | |
" raise ValueError('values should be numpy array')\n", | |
" if len(values) != end_period - start_period + 1:\n", | |
" raise ValueError('values and period range has different lengths')\n", | |
" self.values = values\n", | |
" self.start_period = start_period\n", | |
" self.end_period = end_period\n", | |
" self.diff_level = diff_level\n", | |
" \n", | |
" def __validate(self, time_series):\n", | |
" if self.start_period != time_series.start_period:\n", | |
" raise ValueError('start periods are incompatible')\n", | |
" if self.end_period != time_series.end_period:\n", | |
" raise ValueError('end periods are incompatible')\n", | |
" if self.diff_level != time_series.diff_level:\n", | |
" raise ValueError('diff levels are incompatible')\n", | |
" \n", | |
" def apply(self, fun, *args):\n", | |
" '''\n", | |
" Обобщённый метод для применения произвольной функции `fun` с аргументами `args` \n", | |
" к текущему экземпляру `TimeSeries`\n", | |
" '''\n", | |
" \n", | |
" # Сейчас TimeSeries поддерживает функции с 0 и 1 аргументом\n", | |
" \n", | |
" # Пример функции без аргументов: np.array([2, 4]).cumprod() ~> np.array([2, 8])\n", | |
" if len(args) == 0:\n", | |
" ts = TimeSeries(values=fun(self.values),\n", | |
" start_period=self.start_period, end_period=self.end_period,\n", | |
" diff_level=self.diff_level)\n", | |
" return ts\n", | |
" \n", | |
" # Сейчас TimeSeries в качестве второго аргумента поддерживает только TimeSeries или скаляр\n", | |
" else:\n", | |
" other = args[0]\n", | |
" if isinstance(other, TimeSeries):\n", | |
" self.__validate(other) # проверим, что TimeSeries совместимы\n", | |
" # для совместимых просто посчитаем функцию от значений\n", | |
" # мета-информация никак не меняется\n", | |
" ts = TimeSeries(values=fun(self.values, other.values), \n", | |
" start_period=self.start_period, end_period=self.end_period,\n", | |
" diff_level=self.diff_level)\n", | |
" return ts\n", | |
" \n", | |
" # скаляры применяются к значениям безусловно, при этом мета-информация никак не меняется\n", | |
" elif isinstance(other, (int, float)):\n", | |
" ts = TimeSeries(fun(self.values, other),\n", | |
" start_period=self.start_period, end_period=self.end_period,\n", | |
" diff_level=self.diff_level)\n", | |
" return ts\n", | |
" else:\n", | |
" raise ValueError('argument has incompatible type')\n", | |
" \n", | |
" # Все необходимые операции выражаются через apply\n", | |
" def __add__(self, other):\n", | |
" return self.apply(lambda x, y: x + y, other)\n", | |
" \n", | |
" def __sub__(self, other):\n", | |
" return self.apply(lambda x, y: x - y, other)\n", | |
" \n", | |
" def __truediv__(self, other):\n", | |
" return self.apply(lambda x, y: x / y, other)\n", | |
" \n", | |
" def cumprod(self):\n", | |
" return self.apply(lambda x: x.cumprod())\n", | |
" \n", | |
" def __repr__(self):\n", | |
" return 'TimeSeries(start_period={}, end_period={}, diff_level={}, values={}'.format(\n", | |
" self.start_period, self.end_period, self.diff_level, self.values\n", | |
" )" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Имея такую надстройку, следующая попытка ожидаемо привдёт к ошибке из-за несовместимости периодов (`start periods are incompatible`):" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"ename": "ValueError", | |
"evalue": "start periods are incompatible", | |
"output_type": "error", | |
"traceback": [ | |
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", | |
"\u001b[0;32m<ipython-input-17-6fcea537915f>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 9\u001b[0m diff_level=1)\n\u001b[1;32m 10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 11\u001b[0;31m \u001b[0mx\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", | |
"\u001b[0;32m<ipython-input-16-327c0cc9e750>\u001b[0m in \u001b[0;36m__truediv__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 65\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__truediv__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 66\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 68\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcumprod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", | |
"\u001b[0;32m<ipython-input-16-327c0cc9e750>\u001b[0m in \u001b[0;36mapply\u001b[0;34m(self, fun, *args)\u001b[0m\n\u001b[1;32m 39\u001b[0m \u001b[0mother\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mTimeSeries\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 41\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__validate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# проверим, что TimeSeries совместимы\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 42\u001b[0m \u001b[0;31m# для совместимых просто посчитаем функцию от значений\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 43\u001b[0m \u001b[0;31m# мета-информация никак не меняется\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", | |
"\u001b[0;32m<ipython-input-16-327c0cc9e750>\u001b[0m in \u001b[0;36m__validate\u001b[0;34m(self, time_series)\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__validate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtime_series\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstart_period\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mtime_series\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstart_period\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'start periods are incompatible'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 17\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mend_period\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mtime_series\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mend_period\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'end periods are incompatible'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", | |
"\u001b[0;31mValueError\u001b[0m: start periods are incompatible" | |
] | |
} | |
], | |
"source": [ | |
"x = TimeSeries(values=np.array([4, 2]), \n", | |
" start_period=pd.Period('2015-1', freq='M'), \n", | |
" end_period=pd.Period('2015-2', freq='M'), \n", | |
" diff_level=1)\n", | |
"\n", | |
"y = TimeSeries(values=np.array([1, 2]), \n", | |
" start_period=pd.Period('2015-2', freq='M'), \n", | |
" end_period=pd.Period('2015-3', freq='M'), \n", | |
" diff_level=1)\n", | |
"\n", | |
"x / y" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Теперь посчитаем реальную доходность для индекса S&P с помощью `TimeSeries`:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"TimeSeries(start_period=2015-02, end_period=2016-03, diff_level=1, values=[ 0.05163734 -0.02156758 0.00778508 0.0077198 -0.02371087 0.02252073\n", | |
" -0.05961855 -0.02391243 0.08554818 0.00577832 -0.01380723 -0.05135471\n", | |
" -0.00164759 0.06266874]" | |
] | |
}, | |
"execution_count": 18, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"snp_ror_ts = TimeSeries(\n", | |
" # Для простоты, выше не были введены операции `diff` и адресации массива, что позволило сделать так:\n", | |
" # snp_ts = TimeSeries(...)\n", | |
" # snp_ror_ts = snp_ts.diff() / snp_ts[:-1]\n", | |
" # Это можно проделать в качестве упражнения\n", | |
" values=np.diff(snp_values) / snp_values[:-1], \n", | |
" start_period=pd.Period('2015-2', freq='M'),\n", | |
" end_period=pd.Period('2016-3', freq='M'),\n", | |
" diff_level=1,\n", | |
")\n", | |
"\n", | |
"infl_usd_ts = TimeSeries(\n", | |
" values=infl_usd_values[1:],\n", | |
" start_period=pd.Period('2015-2', freq='M'),\n", | |
" end_period=pd.Period('2016-3', freq='M'),\n", | |
" diff_level=1,\n", | |
")\n", | |
"\n", | |
"snp_ror_real_ts = (snp_ror_ts + 1.) / (infl_usd_ts + 1.) - 1.\n", | |
"snp_ror_real_ts" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])" | |
] | |
}, | |
"execution_count": 19, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"# check:\n", | |
"snp_asset.rate_of_return(real=True).values - snp_ror_real_ts.values" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Возможны случаи, когда временной ряд после применения операции преобразуется в одно число. Например, `CAGR`. `CAGR` не не вписывается в понятие временного ряда, `TimeSeries`. Поэтому лучше ввести дополнительный класс `TimeValue`, с идентичной метаинформацией, а далее при необходимости расширить список типов аргументов для второго параметра в функции `TimeSeries.apply`.\n", | |
"\n", | |
"\n", | |
"В следующей статье мы рассмотрим пример вычисления портфеля из 2х активов." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.6.7" | |
}, | |
"varInspector": { | |
"cols": { | |
"lenName": 16, | |
"lenType": 16, | |
"lenVar": 40 | |
}, | |
"kernels_config": { | |
"python": { | |
"delete_cmd_postfix": "", | |
"delete_cmd_prefix": "del ", | |
"library": "var_list.py", | |
"varRefreshCmd": "print(var_dic_list())" | |
}, | |
"r": { | |
"delete_cmd_postfix": ") ", | |
"delete_cmd_prefix": "rm(", | |
"library": "var_list.r", | |
"varRefreshCmd": "cat(var_dic_list()) " | |
} | |
}, | |
"types_to_exclude": [ | |
"module", | |
"function", | |
"builtin_function_or_method", | |
"instance", | |
"_Feature" | |
], | |
"window_display": false | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment