Skip to content

Instantly share code, notes, and snippets.

@alexander-myltsev
Last active April 8, 2019 18:52
Show Gist options
  • Save alexander-myltsev/4ae791088cf94504006d55cac5033a19 to your computer and use it in GitHub Desktop.
Save alexander-myltsev/4ae791088cf94504006d55cac5033a19 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"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