HRPその3(HRP)

今回やること

HRPによる資産配分戦略の表現

参考コード

github.com

参考コード対応部分

特に関数の意味するところについて学ぶ

今回はHRPの部分だけ知りたいので、get_all_portfoliosの中の関係ないところは無効にした〜

portfolios = get_all_portfolios(returns)

用いている関数

def get_all_portfolios(returns):
    
    cov, corr = returns.cov(), returns.corr()
    hrp = getHRP(cov, corr)
---------------------今回はここまで----------------------

    ivp = getIVP(cov)
    ivp = pd.Series(ivp, index=cov.index)
    mvp = getMVP(cov)
    mvp = pd.Series(mvp, index=cov.index)
    
    portfolios = pd.DataFrame([mvp, ivp, hrp], index=['MVP', 'IVP', 'HRP']).T

    return portfolios

def getHRP(cov, corr):
    # Construct a hierarchical portfolio
    dist = correlDist(corr)
    link = sch.linkage(dist, 'single')
    #dn = sch.dendrogram(link, labels=cov.index.values, label_rotation=90)
    #plt.show()
    sortIx = getQuasiDiag(link)
    sortIx = corr.index[sortIx].tolist()
    hrp = getRecBipart(cov, sortIx)
    return hrp.sort_index()

def correlDist(corr):
    # A distance matrix based on correlation, where 0<=d[i,j]<=1
    # This is a proper distance metric
    dist = ((1 - corr) / 2.)**.5  # distance matrix
    return dist

def getQuasiDiag(link):
    # Sort clustered items by distance
    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
    return sortIx.tolist()

def getRecBipart(cov, sortIx):
    # Compute HRP alloc
    w = pd.Series(1, 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, len(i) // 2), (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] *= alpha  # weight 1
            w[cItems1] *= 1 - alpha  # weight 2
    return w

hrpを出力した結果は、以下

Alphabet     0.164473
Amazon       0.162113
Apple        0.262221
Facebook     0.141686
Microsoft    0.269507
dtype: float64

何を出しているのかよくわからん、、、リターン?分散?シャープレシオ

やったこと

関数getHRPを上から1行ずつみていく

def getHRP(cov, corr):
    # Construct a hierarchical portfolio
----------------イマココ-----------------
    dist = correlDist(corr)
----------------イマココ-----------------

    link = sch.linkage(dist, 'single')
    #dn = sch.dendrogram(link, labels=cov.index.values, label_rotation=90)
    #plt.show()
    sortIx = getQuasiDiag(link)
    sortIx = corr.index[sortIx].tolist()
    hrp = getRecBipart(cov, sortIx)
    return hrp.sort_index()

correlDist

対応するコードは以下

def correlDist(corr):
    # A distance matrix based on correlation, where 0<=d[i,j]<=1
    # This is a proper distance metric
    dist = ((1 - corr) / 2.)**.5  # distance matrix
    
    return dist

distの出力結果は以下

           USDZAR    USDJPY    NZDUSD   Nifty50      N225    AUDJPY
USDZAR   0.000000  0.738111  0.881962  0.775393  0.743507  0.871771
USDJPY   0.738111  0.000000  0.704024  0.632215  0.602410  0.430278
NZDUSD   0.881962  0.704024  0.000000  0.623800  0.653769  0.438771
Nifty50  0.775393  0.632215  0.623800  0.000000  0.541428  0.573740
N225     0.743507  0.602410  0.653769  0.541428  0.000000  0.580813
AUDJPY   0.871771  0.430278  0.438771  0.573740  0.580813  0.000000

相関係数をもとに、各資産の距離?を出しているみたいだが、距離とは??

クラスタリングを行うにあたり、各資産間の距離?を出すのは必要だと思うけど、そもそも何を説明変数にするんだろう、、これをみると相関係数?まあ正直クラスタリングについてちゃんと理解はできていないんだけども、、なんか、もうわからん

クラスタリングするには、特徴量ベクトルを投入しないといけない

その特徴量ベクトル

わからなすぎて、、???わかめちゃんになったわね、、、、

わからないので、続きを見てみる

def getHRP(cov, corr):
    # Construct a hierarchical portfolio
    dist = correlDist(corr)

----------------イマココ-----------------
    link = sch.linkage(dist, 'single')
----------------イマココ-----------------

    #dn = sch.dendrogram(link, labels=cov.index.values, label_rotation=90)
    #plt.show()
    sortIx = getQuasiDiag(link)
    sortIx = corr.index[sortIx].tolist()
    hrp = getRecBipart(cov, sortIx)
    return hrp.sort_index()

sch.linkage

linkの出力結果は以下

[[1.         5.         0.67999173 2.        ]
 [2.         6.         0.68403821 3.        ]
 [3.         4.         0.76755537 2.        ]
 [7.         8.         0.86267539 5.        ]
 [0.         9.         1.14694683 6.        ]]

schはインポートしたライブラリscipy.cluster.hierarchy

クラスタリングしているのか??とりあえずこのライブラリについてググると、階層的クラスタリングをしていた

scipyで階層的クラスタリング | 分析ノート

まあ樹形図を見たいので、以下の通りコードを追加

def getHRP(cov, corr):
    # Construct a hierarchical portfolio
    dist = correlDist(corr)
    link = sch.linkage(dist, 'single')

----------------追加-----------------
    dendrogram(link, labels = cov.index.values)
    plt.show()



----------------まだ-----------------
    sortIx = getQuasiDiag(link)
    sortIx = corr.index[sortIx].tolist()
    hrp = getRecBipart(cov, sortIx)
    return hrp.sort_index()

表示されない、、、これを解決するのにめちゃくちゃ時間取られた

結論としては、最初に以下の一文を追加

%matplotlib inline

得られた樹形図がこれ

f:id:iiiiikamirin:20200408215343p:plain

長かった、、

結果を見ると、株式指標のN225とNifty50が近く、また通貨ペアのNZD/USD,USD/JPY,AUD/JPYは近いもののUSD/ZARは遠いのが意外

さ、次

def getHRP(cov : pd.DataFrame, corr : pd.DataFrame) -> pd.DataFrame:
    # Construct a hierarchical portfolio
    dist = correlDist(corr)
    link = sch.linkage(dist, method = 'single', metric = 'euclidean')
#     fig = dendrogram(link, labels = cov.index.values)
#     plt.show()

----------------イマココ-----------------
    sortIx = getQuasiDiag(link)
----------------イマココ-----------------

    sortIx = corr.index[sortIx].tolist()
    hrp = getRecBipart(cov, sortIx)
    
    return hrp.sort_index()

getQuasiDiag

対応するコードはこれ

def getQuasiDiag(link):
    # Sort clustered items by distance
    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
    return sortIx.tolist()

戻り値であるsortlxの出力結果が以下。いや、何のこと、、

[0, 2, 1, 5, 3, 4]

1行ずつみていく

def getQuasiDiag(link):
    # Sort clustered items by distance
    link = link.astype(int)
    print (link)

----------------まだ-----------------
    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
    return sortIx.tolist()

結果

[[1 5 0 2]
 [2 6 0 3]
 [3 4 0 2]
 [7 8 0 5]
 [0 9 1 6]]

整数にデータの型を変換したことで、ユークリッド距離を表していた列が整数になった(小数点以下切り捨て)

なぜ切り捨てた?? → 今回はユークリッド距離は関係ないので、どうでもよかった

途中の値を色々見たら、何となくこの関数でやっている処理がわかった

link内の1行目と2行目に格納されている元のデータフレームのindexを参照し、

元のデータフレームのindexの値のみを(今回は0から5)sortIxに格納したい(@)

だから、list4行目のグルーピングしたデータ数の項目(今回は6)を利用して、

listの中に6より大きい値があったら(@)を満たさないので、

その要素を他の行の1行目と2行目の値に置き換える

これをsortIxの中に6より大きい要素がなくなるまで繰り返すと、欲しい条件が満たされる

さ、次

def getHRP(cov : pd.DataFrame, corr : pd.DataFrame) -> pd.DataFrame:
    # Construct a hierarchical portfolio
    dist = correlDist(corr)
    link = sch.linkage(dist, method = 'single', metric = 'euclidean')    
#     fig = dendrogram(link, labels = cov.index.values)
#     plt.show()
    sortIx = getQuasiDiag(link)

----------------イマココ-----------------
    sortIx = corr.index[sortIx].tolist()
----------------イマココ-----------------

    hrp = getRecBipart(cov, sortIx)
    
    return hrp.sort_index()

下記の通りprintするとわかった

def getHRP(cov : pd.DataFrame, corr : pd.DataFrame) -> pd.DataFrame:
    # Construct a hierarchical portfolio
    dist = correlDist(corr)
    link = sch.linkage(dist, method = 'single', metric = 'euclidean')
    
#     fig = dendrogram(link, labels = cov.index.values)
#     plt.show()

    sortIx = getQuasiDiag(link)
    
----------------イマココ-----------------
    print (sortIx)
    print (corr.index)
    print (corr.index[sortIx])

    sortIx = corr.index[sortIx].tolist()
----------------イマココ-----------------
        
    hrp = getRecBipart(cov, sortIx)
    
    return hrp.sort_index()

結果

[0, 2, 1, 5, 3, 4]
Index(['USDZAR', 'USDJPY', 'NZDUSD', 'Nifty50', 'N225', 'AUDJPY'], dtype='object')
Index(['USDZAR', 'NZDUSD', 'USDJPY', 'AUDJPY', 'Nifty50', 'N225'], dtype='object')

さ、次

def getHRP(cov : pd.DataFrame, corr : pd.DataFrame) -> pd.DataFrame:
    # Construct a hierarchical portfolio
    dist = correlDist(corr)
    link = sch.linkage(dist, method = 'single', metric = 'euclidean')    
#     fig = dendrogram(link, labels = cov.index.values)
#     plt.show()
    sortIx = getQuasiDiag(link)
    sortIx = corr.index[sortIx].tolist()

----------------イマココ-----------------
    hrp = getRecBipart(cov, sortIx)
----------------イマココ-----------------
    
    return hrp.sort_index()

getRecBipart

def getRecBipart(cov, sortIx):
    # Compute HRP alloc
    w = pd.Series(1, 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, len(i) // 2), (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] *= alpha  # weight 1
            w[cItems1] *= 1 - alpha  # weight 2
    return w

ラスボス感あるなあ、、、今23:30、、寝たい

ただ、これ見て気づいた!!こいつぁHRP用いた時の最適な資産配分比率を示している!!!