Оптимизация рисков для портфеля криптовалют

Введение в оптимизацию рисков для портфеля криптовалют с использованием случайного коэффициента дисконтирования

#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

  1. BNBUSDT
  2. 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 нуждается в двух векторах для выполнения оптимизации:

  1. Вектор признаков I
  2. Целевой вектор 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 достиг лучших показателей, чем индекс, при сохранении более высокого коэффициента Шарпа.

Ответить