LoginSignup
3
1

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

Last updated at Posted at 2023-11-13

はじめに

みなさん、こんばんは。M&Aクラウドの尾村です。以前投稿した記事で、WACC(加重平均資本コスト)の算出式は天下り的に利用しました。
$$
EC = RFR + \beta(ERR - RFR)
$$
この式がなぜ用いられるのか、ベータは何者かというのを学習していこうと思います。なお、本記事の内容は概ね、財務会計・ファイナンス Rによる財務・株式データの分析を参考に書いています。
主なステップとして、以下の3段階を想定しています。

  1. 自分の言葉で理論を説明する
  2. 株価データを使って、ベータを算出し、APIから取ってきたベータと比較する
  3. ベータを機械学習で予測してみる

CAPM(Capital Asset Pricing Model)

仮定

各投資家の最適ポートフォリオ問題を所与として(よって、各投資家が最適な戦略を知っていてそれを行なっている)、金融市場全体の均衡に関して考える。

  1. 選好: 全ての投資家はポートフォリオを期待値と標準偏差の基準で評価する
  2. 取引コスト: 取引に際して、コストが発生せず、ショートが可能である
  3. 流動性: 証券価格は売買量に依存する
  4. 情報集合: 全ての投資家は同じ情報を有する

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を使います。


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」の株価を例に算出してみました。

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

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

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

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

import matplotlib.pyplot as plt
plt.hist(return_price)
plt.show()

image.png

同様にTOPIXの株価も見てみます。

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

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

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

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

plt.scatter(return_price, return_tpx)

image.png

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

image.png

yahooqueryが算出した値は、下記のようになります。微妙に数値が違っている理由はわかりませんでした。ベータは調整のための式等も提案されているようなので、そのせいかもしれません。参考
image.png

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

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

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)を収集します。

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)

特定の条件で絞ります。

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

列も多すぎるとよくわからなくなるので、主観的に主要そうな項目に絞ります。

df_total = df_total[['CurrentAssets',
          'TotalNonCurrentAssets',
          'CurrentLiabilities',
          'LongTermDebtAndCapitalLeaseObligation',
          'TotalDebt', 'market_product_category',
          'type_33',
          'type_17',
          'CommonStockEquity',
          'beta']]

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

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

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

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の何乗といった数値になるため、標準化の後、線形回帰を利用します。

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

予測値x軸に、正解をy軸にして、散布図を描くと以下のようになりました。少し正の相関はありそうですが、正解で負になっている数値がほとんど正に予測されているため、ここからもあまり良い結果ではなさそうです。
image.png

最後に各係数を求めてみると、BSの項目は一定程度、予測に寄与していそうですが、業種についてはほぼ意味がなさそうでした。

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
image.png

最後に

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

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1