ファイナンス機械学習:機械学習によるアロケーション HRP 数値例

$${10000 \times 10}$$の観測データから、HRPとIVP、CLAを使ってポートフォリオを構築する。

def generateData(nObs, size0, size1, sigma1):
   
    #1) generating some uncorrelated data
    np.random.seed(seed=12345)
    random.seed(12345)
    x = np.random.normal(0, 1, size=(nObs, size0))    # each row is a variable
    #2) creating correlation between the variables
    cols = [random.randint(0, size0 - 1) for i in range(size1)]
    y = x[:, cols] + np.random.normal(0, sigma1, size=(nObs, len(cols)))
    x = np.append(x, y, axis=1)
    x = pd.DataFrame(x, columns=range(1, x.shape[1] + 1))
    return x, cols

 10資産のうち後半の5資産は、前半の5資産と相関しているとし、共分散行列のカラーマップは以下の様になる。

資産の相関を示す共分散行列

これをクラスタリングしていく過程は、デンドログラムで明らかになる。

デンドログラム

相関のカラーマップからも分かるように、相関のない資産5はクラスタリングで類似性を認められていない。
 クラスタリングされた共分散行列は以下の様になる。

クラスタリング後の共分散行列

このリストを使い、準対角化と再帰的二分を行い得られるHRPによるポートフォリオの重みを、分散の逆数で重みを与えるIVPと、重みに$${0 \le \omega_i < 1, i=1,\cdots,10}$$の制限をつけたCLA(CLA01)と、重みの制限をはずし空売りを許したCLA(CLANN)でのポートフォリオの重みを比較したのが下の図である。

各資産のポートフォリオの重み

CLAによる重みの集中が見られる一方で、HRPは各クラスタに集中することなく重みを分散している。
 この数値例は以下のコードで実装されている。

import numpy as np
import scipy as sc
import pandas as pd
import random
from scipy.cluster.hierarchy import dendrogram, linkage
from scipy.spatial.distance import pdist, squareform
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns

def getIvp(cov):
    ivp = 1.0 / np.diag(cov)
    ivp /= ivp.sum()
    return ivp

def getClusterVar(cov, cItems):
    cov_ = cov.loc[cItems, cItems]    # matrix slice
    w_ = getIvp(cov_).reshape(-1, 1)
    cVar = np.dot(np.dot(w_.T, cov_), w_)[0, 0]
    return cVar

def getQuasiDiag(link):
    link = link.astype(int)
    sortIx = pd.Series([link[-1, 0], link[-1, 1]])
    numItems = link[-1, 3]    # number of original items
    while sortIx.max() >= numItems:
        sortIx.index = range(0, sortIx.shape[0] * 2, 2)    # make space
        df0 = sortIx[sortIx >= numItems]    # find clusters
        i = df0.index
        j = df0.values - numItems
        sortIx[i] = link[j, 0]    # item 1
        df0 = pd.Series(link[j, 1], index=i+1)
        sortIx = sortIx._append(df0)    # item 2
        sortIx = sortIx.sort_index()    # re-sort
        sortIx.index = range(sortIx.shape[0])    # re-index
    lst =  sortIx.tolist()
    return lst

def getRecBipart(cov, sortIx):
    w = pd.Series([1.0] * len(sortIx), index=sortIx)
    cItems = [sortIx]    # initialize all items in one cluster
    while len(cItems) > 0:
        cItems = [i[j: k] for i in cItems
                  for j, k in ((0, int(len(i) / 2)), (int(len(i) / 2), len(i))) if len(i) > 1]    # bi-section
        for i in range(0, len(cItems), 2):    # parse in pairs
            cItems0 = cItems[i]    # cluster 1
            cItems1 = cItems[i+1]    # cluster 2
            cVar0 = getClusterVar(cov, cItems0)
            cVar1 = getClusterVar(cov, cItems1)
            alpha = 1 - cVar0 / (cVar0 + cVar1)
            w[cItems0] *= np.float64(alpha)    # weight 1
            w[cItems1] *= np.float64(1 - alpha)    # weight 2
    return w

def correlDist(corr):
    dist = ((1 - corr) / 2.0) ** 0.5    # distance matrix
    return dist

def plotCorrMatrix(corr, labels= None,size=(7,7)):
    fig, ax = plt.subplots(figsize=size)
    if labels is None:
        labels = []
    ax = sns.heatmap(corr)
    ax.invert_yaxis()
    ax.set_yticks(np.arange(0.5, corr.shape[0] + 0.5), list(labels))
    ax.set_xticks(np.arange(0.5, corr.shape[0] + 0.5), list(labels))
    plt.show()

def generateData(nObs, size0, size1, sigma1):
    #1) generating some uncorrelated data
    np.random.seed(seed=12345)
    random.seed(12345)
    x = np.random.normal(0, 1, size=(nObs, size0))    # each row is a variable
    #2) creating correlation between the variables
    cols = [random.randint(0, size0 - 1) for i in range(size1)]
    y = x[:, cols] + np.random.normal(0, sigma1, size=(nObs, len(cols)))
    x = np.append(x, y, axis=1)
    x = pd.DataFrame(x, columns=range(1, x.shape[1] + 1))
    return x, cols

def EXP():
    import CLA
    
    #1) Generate correlated data
    nObs, size0, size1, sigma1 = 10000, 5, 5, 0.25
    x, cols = generateData(nObs, size0, size1, sigma1)
    print([(j + 1, size0 + i) for i, j in enumerate(cols, 1)])
    cov, corr = x.cov(), x.corr()
    #2) compute and plot correl matrix
    plotCorrMatrix(corr, labels=corr.columns, size=(8, 6.5))
    #3) cluster
    dist = correlDist(corr)
    link = linkage(dist, 'single')
    fig, ax = plt.subplots(figsize=(12, 7))
    dendrogram(link, ax=ax, labels=corr.columns)
    plt.show()
    sortIx = getQuasiDiag(link)
    sortIx = corr.index[sortIx].tolist()    # recover labels
    df0 = corr.loc[sortIx, sortIx]    # reorder
    plotCorrMatrix(df0, labels=df0.columns, size=(8, 6.5))
    #4) Capital allocation
    hrp = getRecBipart(cov, sortIx)
    hrp=hrp.sort_index()
    df = pd.DataFrame({'HRP':hrp})
    ivp=getIvp(cov)
    df['IVP']=ivp
    mu=np.array(x.mean())
    mu=mu.reshape(-1,1)
    lb=np.zeros((len(mu),1))
    ub=np.ones((len(mu),1))
    cla=CLA.CLA(mu,cov.values,lb,ub)
    cla.solve()
    df['CLA01']=cla.w[-1].flatten()
    cla=CLA.CLA(mu,cov.values)
    cla.solve()
    df['CLANN']=cla.w[-1].flatten()
    return df

df=EXP2()
ax = df.plot( y=["HRP", "IVP", "CLA01","CLANN"], kind="bar", rot=0)



この記事が気に入ったらサポートをしてみませんか?