CAPMにおけるベータを自分で算出してみた

# はじめに
みなさん、こんばんは。M&Aクラウドの尾村です。[以前投稿した記事](https://qiita.com/homura99/items/2154a76abfb60982b79a)で、WACC（加重平均資本コスト）の算出式は天下り的に利用しました。
$$
EC = RFR + \beta(ERR - RFR)
$$
この式がなぜ用いられるのか、ベータは何者かというのを学習していこうと思います。なお、本記事の内容は概ね、[財務会計・ファイナンス Rによる財務・株式データの分析](https://www.amazon.co.jp/%E5%AE%9F%E8%A8%BC%E4%BC%9A%E8%A8%88%E3%83%BB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%8A%E3%83%B3%E3%82%B9-R%E3%81%AB%E3%82%88%E3%82%8B%E8%B2%A1%E5%8B%99%E3%83%BB%E6%A0%AA%E5%BC%8F%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%88%86%E6%9E%90-%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA-%E3%83%87%E3%83%BC%E3%82%BF%E5%88%86%E6%9E%90%E3%81%B8%E3%81%AE%E6%8B%9B%E5%BE%85-5/dp/4883843491)を参考に書いています。
主なステップとして、以下の３段階を想定しています。
1. 自分の言葉で理論を説明する
1. 株価データを使って、ベータを算出し、APIから取ってきたベータと比較する
1. ベータを機械学習で予測してみる

# CAPM（Capital Asset Pricing Model)
## 仮定
各投資家の最適ポートフォリオ問題を所与として（よって、各投資家が最適な戦略を知っていてそれを行なっている）、金融市場全体の均衡に関して考える。
1. 選好： 全ての投資家はポートフォリオを期待値と標準偏差の基準で評価する
1. 取引コスト： 取引に際して、コストが発生せず、ショートが可能である
1. 流動性： 証券価格は売買量に依存する
1. 情報集合： 全ての投資家は同じ情報を有する

## CAPMの第一命題
「市場ポートフォリオは接点ポートフォリオと一致し、効率的フロンティア（資本市場線）上に位置する。」

市場ポートフォリオ：市場に存在する全ての危険資産を時価総額比率で保有するポートフォリオ（実用的には、TOPIX等を用いる）
接点ポートフォリオ：安全資産を保有する（危険資産の割合0）の点から、効率的フロンティアと接するように引いた直線（接線）の接点
効率的フロンティア：資産を分散投資する場合に最低限、受け入れるべきリスクの水準を表した曲線

投資家は危険資産を持つことで得られる期待収益μmから、安全資産を持つことで得られる収益（リスクフリープレミアムRF）を引いた分（μm - RF）だけ、追加の報酬が得られることを期待する。この時、資本市場線μpは、以下のように表すことができる。
$$
\mu p = RF + \frac{\mu m - RF}{\sigma m}\sigma p
$$

## CAPMの第二命題
「各証券のリスクプレミアムは、その証券のマーケットベータに比例する。つまり、

$$
E[Ri] = RF + \beta i(E[RM] - RF)
$$
$$
\beta i = \frac{Cov(Ri,RM)}{Var[RM]}
$$

のように表現できる。」

ベータの分子は、市場のリターンRMとその証券のリターンRiとの共分散なので、市場との連動性を表した式になる。仮に、ベータ自体の分散が大きい場合でも、市場と負に相関している場合、適切に組み合わせることで、リスクの低減が可能になる。

# マーケットベータの算出
マーケットベータの算出は、google colaboratory上で行いました。yahooqueryが使いやすそうだったので、こちらを使います。
まずは、yahooqueryをインストールしましょう。
```
!pip install yahooquery
```
エラーは適宜直してください。
次にモジュールをインポートします。今回は、yahooquery, numpy, pandas, scikit-learnを使います。
```python

import numpy as np
import pandas as pd
from yahooquery import Ticker
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import LinearRegression
```

株価データをインポートします。今回は、私に馴染みがある「TOYO TIRE」の株価を例に算出してみました。

```python
calc_period = '3y'
calc_interval = '1mo'
ticker = Ticker('5105.T')
df = ticker.history(period=calc_period, interval=calc_interval).reset_index()
```

株価は株式分割等により、変化するのでその影響を無視するため、調整後（adjclose）の値を利用します。リターン割合は（該当の月 - 前の月）　/ 該当月とします。

```python
price = df['adjclose']
return_price = [(price[i]-price[i-1])/price[i] for i in range(1, len(price))]
```

ここで、matplotlibを使って、リターンの分散を見てみます。ピークは0より大きいので一定程度のリターンは期待できそうです。

```python
import matplotlib.pyplot as plt
plt.hist(return_price)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/a243aa1e-f7c2-bfcc-123b-9e9606e78b24.png)

同様にTOPIXの株価も見てみます。
```python
ticker_tpx = Ticker('TPX')
df_tpx = ticker_tpx.history(period=calc_period, interval=calc_interval).reset_index()
tpx_price = df_tpx['adjclose']
return_tpx = [(tpx_price[i] - tpx_price[i - 1])/tpx_price[i] for i in range(1, len(tpx_price))]
plt.hist(return_tpx)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/28117dc3-1fe1-5cc2-fdca-29a1665eff9b.png)

ここから、ベータを算出するには、numpyのcov関数を使います。この関数は分散・共分散行列を算出します。２行２列の行列になり、対角成分が自己分散、非対角成分が共分散です。

```python
cov_matrix = np.cov(return_price, return_tpx)
beta = cov_matrix[1,0] / cov_matrix[0,0]
```

実際、どのようの分布になっているか確認するには分布図を使います。

```python
plt.scatter(return_price, return_tpx)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/914ad9cb-c489-763e-3a42-afb94aa3ce12.png)

かなり、分散していますが、一定程度、正の相関をしていそうです。
これで算出した結果が以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/b36a0278-2f0a-a5be-8ab3-276b877593d3.png)

yahooqueryが算出した値は、下記のようになります。微妙に数値が違っている理由はわかりませんでした。ベータは調整のための式等も提案されているようなので、そのせいかもしれません。[参考](https://ja.pharoskc.com/2489-what-is-adjusted-beta)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/4dd869a8-31ff-3599-2c57-fc1de282935d.png)

# 機械学習を使って、ベータを予測する

まずは、上場企業のticker（=証券コード）の一覧が必要です。JPXのサイトから一覧をダウンロードできるので、それを使います。（pandasは、URLを指定すると、それをダウンロードして読み込んでくれるので、楽です。

```python
listed_company_url = 'https://www.jpx.co.jp/markets/statistics-equities/misc/tvdivq0000001vg2-att/data_j.xls'
df_listed = pd.read_excel(listed_company_url, index_col=None)
```
yahooqueryを使って、ベータと財務諸表（BS）を収集します。
```python
df_total = pd.DataFrame([])
for index, row in df_listed[0:1000].iterrows():
    time.sleep(0.5)
    ticker = row["コード"]
    ticker_num = str(ticker) + '.T'
    ticker_data = Ticker(ticker_num)
    if type(ticker_data.balance_sheet()) != str:
      balance_sheet = ticker_data.balance_sheet().reset_index()
      balance_sheet['ticker_name'] = row["銘柄名"]
      balance_sheet['market_product_category'] = row["市場・商品区分"]
      balance_sheet['type_33'] = row["33業種区分"]
      balance_sheet['type_17'] = row["17業種区分"]
      for k,v in ticker_data.summary_detail.items():
        if 'beta' in v.keys():
          balance_sheet['beta'] = v['beta']
        else:
          print(row["銘柄名"])
          balance_sheet['beta'] = None
      df_total = pd.concat([df_total, balance_sheet], axis = 0)
```
特定の条件で絞ります。

```python
df_total = df_total.query('"2023-01-01" < asOfDate < "2023-10-31"').query("not beta.isnull()").query('currencyCode == "JPY"')
```

列も多すぎるとよくわからなくなるので、主観的に主要そうな項目に絞ります。
```python
df_total = df_total[['CurrentAssets',
          'TotalNonCurrentAssets',
          'CurrentLiabilities',
          'LongTermDebtAndCapitalLeaseObligation',
          'TotalDebt', 'market_product_category',
          'type_33',
          'type_17',
          'CommonStockEquity',
          'beta']]
```

また、業種をonehotエンコーディングし、nanが含まれる行を削除します。drop_first=Trueがないと、今回使用する線形回帰では、多重共線性の問題にあたるので気をつけましょう。

```python
df_total = pd.get_dummies(df_total, drop_first=True)
df_total = df_total.dropna(axis=0)
```

このあとはよくあるtrain/testスプリットです。

```python
from sklearn.model_selection import train_test_split
y_cols = 'beta'
df_train, df_test = train_test_split(df_total)
X_train = df_train.drop(y_cols, axis=1)
y_train = df_train[y_cols]
X_test = df_test.drop(y_cols, axis=1)
y_test = df_test[y_cols]
```

BSに乗っている数値は10の何乗といった数値になるため、標準化の後、線形回帰を利用します。
```python
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.transform(X_test)
lr = LinearRegression()
lr.fit(X_train_std, y_train)  
y_pred = lr.predict(X_test_std)
```

MSEを算出すると、0.40になりました。ほとんどのベータの値が-1から1の間に収まることを考えるとこれでも、結構大きなエラーです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/d479c017-68fe-10fa-90f5-384602465cd8.png)

予測値x軸に、正解をy軸にして、散布図を描くと以下のようになりました。少し正の相関はありそうですが、正解で負になっている数値がほとんど正に予測されているため、ここからもあまり良い結果ではなさそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/028c3fe9-4538-b951-6c32-07c3a8ac3cfe.png)

最後に各係数を求めてみると、BSの項目は一定程度、予測に寄与していそうですが、業種についてはほぼ意味がなさそうでした。
```python
coef = lr2.coef_
df_coef = pd.DataFrame(coef).T
df_coef.columns = df_total.drop('beta',axis=1).columns
df_coef.index = ["Estimated coefficients"]
df_coef = df_coef.round(2)
df_coef
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/48d39d4c-a573-1ba0-c847-92e018693f51.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3091446/052b3edd-b9d8-f841-a3ef-0aea405c6788.png)

# 最後に
今回は、CAPMのベータの理解を深めるために、自分でベータの値を計算してみました。yahooqueryの中でベータが取得できない企業があるみたいなので、自己流の算出でも、一定程度利用用途はありそうです。また、yahooqueryからすでに求めらているベータをBSの項目を使って計算してみました。実用的に使えそうではありませんでしたが、一定、ベータの理解が深まり、（＆久々に機械学習に触れて）良かったと思います。