Оптимизация рисков для портфеля криптовалют
Введение в оптимизацию рисков для портфеля криптовалют с использованием случайного коэффициента дисконтирования
#install requirements if needed
!pip install cvxpy==1.0.31 pandas_datareader pandas matplotlib seaborn -q
#import modules
import cvxpy as cp
import pandas_datareader as pdr
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import math
import os.path
import time
#select plotting style
import matplotlib
import seaborn as sb
matplotlib.style.use('fivethirtyeight')
#seaborn-white fivethirtyeight
import sys
#Python versiond
print(sys.version)
print(cp.__version__)
#read input dataset that was downladed from the binance
fuldf = pd.read_pickle('data.pickle')
y_label = fuldf.columns[0]
factors = (fuldf.columns[1:]
.tolist()
)
total_size = fuldf.shape[0]
# print('Combined DF size:', total_size,'\nFull range: ', fuldf.iloc[0].name,fuldf.iloc[-1].name)
train_set = .8
train_id = int( total_size * train_set)
df_train = fuldf.iloc[:train_id]
df_test = fuldf.iloc[train_id:]
split_index = fuldf.iloc[train_id].name
# print('Training period: ', df_train.iloc[0].name,'-',df_train.iloc[-1].name,'shape:',df_train.shape[0],'\nTesing period:',df_test.iloc[0].name,df_test.iloc[-1].name,'shape:',df_test.shape[0],)
Сбор Данных
Данные для этого упражнения были получены из API Binance с использованием клиента API Python python-binance
Пример кода сбора данных:
Примечание: При необходимости установите модуль с помощью pip:
- #pip install python-binance
from binance.client import Client
binance_api_key = 'YOUR-API-KEY'
binance_api_secret = 'YOUR-API-SECRET'
binance_client = Client(api_key=binance_api_key
,api_secret=binance_api_secret)
klines = binance_client.get_historical_klines(symbol
,kline_size, date_from, date_to)
data = pd.DataFrame(klines, columns = [COLUMNS])
Следующие ежедневные пары были загружены и отформатированы в pd DataFrame: 1. BTCUSDT2.ETHUSDT
- BNBUSDT
- LTCUSDT
Для каждого момента времени Binance предоставляет обычные данные OHLC (Open (открытие), High (максимум), Low (минимум), Close (закрытие)) и данные об объеме. В этом упражнении мы использовали только столбец Close
. Можно, однако, рассмотреть комбинацию всех пяти значений и придумать более надежную метрику, например: средневзвешенную цену.
Совместный временной ряд четырех крипто активов выглядит следующим образом:
df_train.head()
Каждая торговая пара имеет различный объем доступных данных: самыми старыми торгуемыми криптоинструментами на Binance являются BTCUSDT и ETHUSDT – точки данных доступны с 2017-08-17. Для BNBUSDT и LTCUSDT первые торговые дни – 2017-11-06 и 2017-12-13 соответственно.
Мы объединили эти временные ряды вместе в самую раннюю общую торговую дату: 2017-12-13 до 2021-06-14. Полный размер совместного набора данных равен 1280 образцам.
Чтобы правильно вывести SDF, мы разделили набор данных на обучающие и тестовые наборы в 2020-10-02.
Интервал обучения: с 2017-12-13 по 2020-10-02 (1024 точки данных).
Ниже приведен совместный график полного набора данных в логарифмическом масштабе. Пунктирная линия представляет разделение на 2020-10-02
plot_data =np.log(fuldf)
fig , ax = plt.subplots(figsize=(12,7))
# ax1 = ax.twinx()
# plot_data.drop(y_label,axis=1).plot(ax=ax)
plot_data.plot(ax=ax,alpha=0.8)
ax.legend(loc=0)
ax.set(title='Assets',xlabel='Date',ylabel='price, log scale');
ax.axvline(split_index,color='grey', linestyle='--', lw=2);
#Transform raw data to log-return format:
lret_data = np.log1p(df_train.pct_change()).dropna(axis=0,how='any')
II. Преобразование данных
Чтобы быть должным образом обученным, вводимые временные ряды должны быть преобразованы в формат возврата журнала:
rt=ln(Pt/Pt-1)
Где Pt является ценой актива в момент времени
Модель SDF нуждается в двух векторах для выполнения оптимизации:
- Вектор признаков I
- Целевой вектор R
Вектор R содержит значения, сдвинутые на t + 1 по сравнению с вектором I:
Где l – количество функций в модели.
R = lret_data[y_label].shift(1).iloc[1:].values.reshape((-1,1))
I = lret_data[factors].iloc[1:].values
# assert R.shape[0] == I.shape[0] and I.shape[1]==3
print('R:',R.shape,'\n',R[:10]
,'\n I:',I.shape,'\n',I[:10,:])
#correlation matrix
F= pd.concat([pd.DataFrame(R),pd.DataFrame(I)],axis=1)
F.columns = [y_label]+factors
F.cov()
f = plt.figure(figsize=(10, 10))
cov_data = F.cov()
mask = np.triu(np.ones_like(cov_data))
dataplot = sb.heatmap(cov_data.corr(), cmap="YlGnBu", annot=True, mask=mask)
plt.title('Covariance Matrix', fontsize=16);
Модель
Наша цель состоит в том, чтобы объяснить различия в поперечном сечении возвратов
R для отдельных акций.
Пусть Rt+1; обозначает возврат BTCUSD в момент времени t + 1. Фундаментальное предположение об отсутствии арбитража эквивалентно существованию случайного коэффициента дисконтирования (SDF) Mt+1 таким образом, что при любой доходности, превышающей безрисковую ставку
Ret+1= Rt+1-Rft+1 это содержит:
Et[Mt+1Ret+1]=0
В демонстрационных целях и для простоты мы предполагаем, что Ret=Rt:превышение доходности BTCUSD совпадает с доходностью рынка.
Мы рассматриваем формулировку SDF как:
Обобщенный метод моментов (GMM) был использован для SDF вектора М. Уравнение условия первого момента может быть выражено следующим образом:
Поскольку описанная выше проблема выдающаяся, мы можем оценить SDF для BTC, используя фреймворк cvxpy
.
#4 - Solve GMM problem
n = R.shape[0]
#define weights for the SDF
w = cp.Variable(shape=(I.shape[1]
,1),nonneg=True)
#define SDF
M = I @ w
#Define expression
Sigma = M @ R.T
#Construct the problem
prob = cp.Problem(cp.Minimize( cp.norm(Sigma) )
# , [cp.geo_mean(M) >= 1 , ]
, [cp.sum(w) == 1
,cp.sum(M) == 1 ]
)
prob.solve(verbose=True
#, max_iters = 300
)
print('SDF weights from BTC:\n',dict(zip(factors,w.value)))
def sharpe(x):
return (x.mean() / x.std() * np.sqrt(365))
def plot_return(data,title):
datac = data.copy()
fig , ax = plt.subplots(figsize=(13,5))
y_return = (datac[y_label].pct_change().fillna(0)+1)
p_return = ((datac[factors].pct_change().fillna(0) + 1)
.apply(lambda x: (x @ w.value)[0] ,axis =1)
.rename('SDF Portfolio')
)
p_return_c = p_return.cumprod()
y_return_c = y_return.cumprod()
portfolio_benchmark = pd.concat([y_return_c,p_return_c],axis=1)
portfolio_benchmark.plot(ax=ax)
ax.legend(loc=0)
ax.set(title=title,ylabel='growth factor',xlabel='date')
return portfolio_benchmark
def calc_metrics(data):
#y_return = data[y_label].pct_change().fillna(0)
p_return = ((data[factors].pct_change().fillna(0))
.apply(lambda x: (x @ w.value)[0] ,axis =1)
.rename('SDF Portfolio')
)
y_return = data.pct_change().fillna(0)
y_return['SDF Portfolio'] = p_return
#portfolio_ret = pd.concat([y_return,p_return],axis=1)
return '\nSharpe ration: '+str(portfolio_ret.apply(sharpe).round(3).to_dict())
Результаты
Ниже приведен результат вычисления портфолио SDF по набору обучающих данных и его сопоставления с портфолио индексов (BTCUSDT):
portfolio_benchmark = plot_return(df_train,'In-sample: Performance of the SDF Portfolio')
print('Portfolio growth (x times):\n'+\
str((portfolio_benchmark.iloc[-1] - 1).round(2).to_dict())+\
calc_metrics(df_train))
Portfolio growth (x times):
{'BTCUSDT': -0.34, 'SDF Portfolio': 2.92}
Sharpe ration: {'BTCUSDT': 0.222, 'SDF Portfolio': 0.997}
Результирующий график и показатели для набора данных с задержкой:
portfolio_benchmark_ho = plot_return(df_test,'Out-of-sample: Performance of the SDF Portfolio')
print('Portfolio growth factor:\n'+\
str((portfolio_benchmark_ho.iloc[-1] - 1).round(2).to_dict())+\
calc_metrics(df_test))
Portfolio growth factor:
{'BTCUSDT': 2.84, 'SDF Portfolio': 11.08}
Sharpe ration: {'BTCUSDT': 2.704, 'SDF Portfolio': 3.394}
Вывод
В этом упражнении мы оценили модель SDF для BTCUSDT с использованием метода GMM и рассчитали производительность выборочного портфолио на интервалах времени в выборе и ожидания.
Результаты модели можно интерпретировать следующим образом:
В течение всего 2018 года BTCUSD демонстрировал низкие показатели (-34 %), что привело к почти нулевому коэффициенту Шарпа. На том же интервале модельный портфолио показал рост на 300 %, но не без высокой волатильности (Шарп все еще меньше 1).
В наборе данных “Удержание” как индекс, так и портфолио моделей показали значительный рост на 284 % и 1108% соответственно. Коэффициент Шарпа как для индекса, так и для модельного портфолио превышает 2: 2,7 и 3,4 соответственно.
Учитывая результаты запуска модели, мы можем с уверенностью заключить, что наш портфолио моделей SDF достиг лучших показателей, чем индекс, при сохранении более высокого коэффициента Шарпа.