ストックドッグ

KatoTakahiro。金融系の会社で働くSEが株やPython、その他諸々について書いています。サービスも運営してます→http://fmbrain.work

3ファクターモデルをPythonで実装して期待リターンを求める

3ファクターモデルとは

CAPMを発展させた理論です。

CAPMは、市場のリスクプレミアムのみで期待リターンを求めています。

#CAPM
個別株の期待リターン = β * (市場リスクプレミアム - 無リスク金利) + 無リスク金利


CAPMはとてもシンプルで金融市場の値動きの本質を表しているのですが、シンプルゆえときに値動きを説明できないタイミングあります。

そのため、もう少し変数を加えて細かく金融市場の値動きを説明してみよう、というのが3ファクターモデルです。

名前の通り、ファクター(変数)が3つです。

  • 市場リスクプレミアム
  • 小型株ファクター(SMB)
  • サイズファクター(HML)


それぞれのファクターのβを計算し、個別株の期待リターンを求めます。

3ファクターモデルの計算式

個別株の期待リターン = β * 市場リスクプレミアム + SMB'β * SMB + HML'β * HML + 無リスク金利


CAPMにでてこないファクターであるSMBとHMLについて説明します。

SMBとは

SMB : Small minus Big

小型株のパフォーマンスと大型株のパフォーマンスの差を表しています。

多くの分析から、小型株は大型株よりも高いリターンを出すことがわかっています。

小型株を保有したときの、期待リターンをSMBという指標によって考慮します。


SMBは時価総額によって、企業を2つに分類します。

企業規模(Small size,Big size)

HMLとは

HML : High minus Low

簿価時価比率が高い銘柄と低い銘柄のパフォーマンスの差です。

簿価時価比率 = 簿価 / 時価

簿価時価比率が高ければ、簿価が評価されておらず、時価がついてきていないということになります。

そのため、会社の価値はあるが市場が高い値をつけていないバリュー株ということになります。


簿価時価比率は、PBRの逆数で求めることができます。

PBR = 時価 / 一株当たり純資産

これの逆数なので...

簿価時価比率 = 一株当たり純資産 / 時価

一株当たり純資産は、帳簿上の解散価値です。つまり、1株あたりのの帳簿上の価値を表しています。

そのため、簿価として一株当たり純資産を使うってことですね。(たぶん)


簿価時価比率によって企業を3つに分類します。

簿価時価比率(Low BM,Medium BM,High BM)

SMBとHMLを計算式

企業規模(Small size,Big size)と簿価時価比率(Low BM,Medium BM,High BM)の組み合わせで,次の6つのポートフォリオに分割します。

簿価時価比率(低) 簿価時価比率(中) 簿価時価比率(大)
時価総額(小) (1).Small size-Low BM(S/LB) (2).Small size-Medium BM(S/MB) (3).Small size-High BM(S/HB)
時価総額(大) (4).Big size-Low BM(B/LB) (5).Big size-Medium BM(B/MB) (6).Big size-High BM(B/HB)
SMB =( (1) + (2) + (3) )/3 - ( (4) + (5) + (6) )/3
HML =( (3) + (6) )/2-( (1) + (4) )/2,


このSMBとHMLは市場全ての企業を対象に計算します。

日本市場であれば、市場全ての企業 = 東証1部 が適切かな、と思います。


これで3ファクターモデルの式に出てくる変数が全て出揃いました。

個別株の期待リターン = β * 市場リスクプレミアム + SMB'β * SMB + HML'β * HML + 無リスク金利


実際は、以下の線形回帰式を最小二乗法でそれぞれのファクターのベータを求めます。

y = ax + a2x2 + a3x3 + b

Pythonで実装

注意!!ここから適当なところあります

3ファクターモデルを計算するには、過去の株価だけではなく、時価総額と時価簿価比率のデータが必要です。

集めてくるのがとっても面倒です。


なので、アメリカの市場のリスクプレミアムとSMLとHMLのデータがあるので、それを使うことにしました...笑

以下のgithubソースコードで使われているデータを使用しています。

github.com

期待リターンを求める企業だけが日本企業というちぐはぐっぷりですが...

理論と実装方法がメインの記事なので気にしません。


以下から市場リスクプレミアム、HML、SMLのデータを取得します。

http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip


データを扱いやすい形に整形します。

データは2000年以降のものにフィルタリングします。

import pandas as pd
import numpy as np
import datetime
import os, sys

corrent_dir = os.getcwd()
os.chdir("../")
sys.path.append(os.getcwd())
import scraping_stock
os.chdir(corrent_dir)



FFdata = pd.read_csv("F-F_Research_Data_Factors.CSV", 
                     header = 0, names = ["Date","MKT-RF","SMB","HML","RF"], 
                     skiprows=3)


FFdata = FFdata[:1074]
for i in range(len(FFdata)):
	d = FFdata["Date"].iloc[i]
	FFdata["Date"].iloc[i] = datetime.datetime(int(d[0:4]), int(d[4:6]), 1, 0, 0, 0, 0)# print(FFdata.head(10))
# print(FFdata.tail(10))

FFdata = FFdata.loc[FFdata["Date"] >= datetime.datetime(2000, 1, 1, 0, 0, 0, 0)]
FFdata = FFdata.sort_values("Date", ascending=True)


上記のデータが2000年01月から2015年12月までなので、同じ期間のNTTデータ(9613)の株価を取得します。

株価取得のコードはMPTを実装したときのコードを参考にしてください。

www.stockdog.work


ここでは、2000年01月から2015年12月の期間のデータが既にあるとして、データの整形にはいります。

stock = 9613
# scraping_stock.scraping(stock, days=5475 , term="m")
df = pd.read_csv("../data/code_{0}.csv".format(stock))
df = df.dropna(how="any")

# Datetime形式に変換
date_format = "%Y年%m月"
for i in range(len(df)):
	df["Date"].iloc[i] = datetime.datetime.strptime(df["Date"].iloc[i], date_format)

df = df.sort_values("Date", ascending=True)

# 必要なデータを切り出し
tmp_df = df.loc[:, ["Date", "Adj Close"]]


# リターンを計算する
# tmp_df["Return"] = np.log(tmp_df["Adj Close"]) - np.log(tmp_df["Adj Close"].shift(1))
tmp_df["Return"] = tmp_df["Adj Close"].pct_change()


2つのデータを1つのDataFrameにまとめます。

data = pd.merge(FFdata, tmp_df, on="Date")
data.index = data["Date"].values
data = data.drop("Date", axis=1)
data = data.astype(float)
data = data.dropna(how="any")

print(data.head())

# リターンから無リスク金利をひきます
data["XReturn"] = data["Return"] * 100 - data["RF"].astype(float)


3ファクターモデルに基づいて、期待リターンを計算していきます。

それぞれのファクターのβは、60ヶ月分のデータから求めます。

個別株の期待リターン(60ヶ月分) = β * 市場リスクプレミアム(60ヶ月分) + SMB'β * SMB(60ヶ月分) + HML'β * HML(60ヶ月分) + 無リスク金利


求めたβを使って、個別株の期待リターンを求めます。

市場のリスクプレミアムとSMBとHMLは、持ちうるデータの最大期間を使って個別株の期待リターンを求めます。

個別株の期待リターン = β * 市場リスクプレミアム(最大期間) + SMB'β * SMB(最大期間) + HML'β * HML(最大期間) + 無リスク金利
import statsmodels.api as sm


# 60ヶ月
t = 60
tf_list = []

# 2000 ~ 2015年まで計算
for i in range(11, len(data)-t, 12):

	# 最大期間を取得
	MKT_RF = data.iloc[0:i+t, [0]].mean().values[0]#"MKT-RF"
	SMB = data.iloc[0:i+t, [1]].mean().values[0]#"SMB"
	HML = data.iloc[0:i+t, [2]].mean().values[0]#"HML"
	RF = data.iloc[i+t, [3]].values[0]

	print("MKT-RF : ", MKT_RF, "SMB : ", SMB, "HML : ", HML)

	# 60ヶ月分
	tmp_data = data.ix[i:i+t]
	print(tmp_data)

	# 3ファクターモデルを計算
	y = tmp_data["XReturn"]
	X = tmp_data.loc[:, ["MKT-RF", "SMB", "HML"]]
	X = sm.add_constant(X)
	model = sm.OLS(y, X)
	results = model.fit()
	# print(results.summary())
	alpha, beta, smb_beta, hml_beta = results.params
	print(alpha, beta, smb_beta, hml_beta)

	# 求めたβで期待リターンを計算
	expected_return = (beta * MKT_RF) + (smb_beta *  SMB) + (hml_beta * HML) + alpha + RF

	# 求めた期待リターンは、月間でもデータなので年率に変換
	print(expected_return * 12)

いちおう結果

2006 ~ 2015年の期間でNTTデータの期待リターンを3ファクターモデルで求め、実際のリターンを比較しました。

3ファクターモデルで求めた期待リターン
期待リターン
2006 3.380415%
2007 15.638408%
2008 19.402995%
2009 3.269230%
2010 3.621298%
2011 -10.057815%
2012 -9.765555%
2013 -6.118449%
2014 5.041712%
2015 11.430143%
実際のNTTデータの期待リターン
期待リターン
2006 3.189316966221089%
2007 -15.716405993233739%
2008 -27.606051376861046%
2009 -14.094971477913244%
2010 0.8188833324962229%
2011 -10.280445385445681%
2012 13.43885732894585%
2013 39.04595333427926%
2014 17.567901291557355%
2015 27.7547208081126%


ちなみにアメリカの市場のリスクプレミアム、SMB、HMLを使っているで本当に結果に意味はありません。

考察するに値しないので、結果としてはこういった形で出る、というくらいです。






参考

計算式はこの論文を参考にしています。

http://www2.itc.kansai-u.ac.jp/~koji_ota/Paper/PublishedPaper/Ota_2012_Shogakuronshu.pdf


データはこのgithubのコードに使われているものを参考にしています。

https://github.com/nakulnayyar/FF3Factor/blob/master/FamaFrench3Factor.ipynb

個別株と市場のボラティリティの関係性を示すβをPythonで計算する

βとは

βは、CAPMというポートフォリオ理論に出てくる概念であり、個別株と市場のボラティリティの関係性を示します。

日本経済新聞のサイトでは、銘柄のβランキングが公表されています。

www.nikkei.com



個別株とは、TOYOTAであったりNTTデータであったり、ひとつひとつの会社のことです。

市場とは、株式市場全体のことです。日本では、TOPIX = 市場 として扱われることが多いと思います。


βは高ければ、高いほど市場より高いボラティリティがあり、低ければ低いほど市場より低いボラティリティということになります。

ボラティリティ = リスク であるため、言い換えるとβが高い株は、リスクの高い株、ということにもなります。

β = 1.0 : 株式が市場と同じボラティリティ
β = 2.0 : 株式が市場より高いボラティリティ
β = 0.5 : 株式が市場より低いボラティリティ


TOYOTAとさくらネットの株価の日々のリターンをビジュアル化してみます。



それぞれのβは以下になります。

銘柄名 β
さくらネット(3778) 1.54
TOYOTA(7203) 1.02

βがさくらネットのほうが高く、ボラティリティが高いことがわかると思います。

言うても、0.5ほどしか差がないので、微妙な差ですが....


βの求め方

β = 個別株と市場ポートフォリオの相関 × 個別株の標準偏差 / 市場ポートフォリオの標準偏差		

この式が何をしているかと言うと...

個別株の値動きのうち、市場と連動している部分のみをβとしています。


個別株の値動きは、大きく2つにわけることができます。

1つは、市場の動き。もう1つは、個別株特有の動きです。

TOPIXが上昇すれば、個別株もそれに応じて上昇します。

これが市場の動きです。


逆に個別株特有の動きとは、決算の結果が良かったや、粉飾決算がバレた、などです。

これらのイベントによって個別株は、特有の値動きをするので、この値動きは市場の動きとは別です。


市場の動きと個別株特有の動きが合わさって、個別株の値動きになります。

個別株の値動きの中から個別株特有の動きを控除したものを、個別株のβとしているのです。

どうやって個別株特有の動きを控除するかと言うと、個別株と市場の相関係数を使います。

個別株と市場ポートフォリオの相関 × 個別株の標準偏差


個別株と市場の値動きの相関係数が0.7だった場合は、0.7は市場の動き、残りの0.3は個別株特有の動きであると。

そういった解釈になります。


個別株の標準偏差に市場との相関係数を乗算することで、市場と連動している部分のみ、を抜き取っています。


Pythonで実装する

株価を取得するプログラミングは、現代ポートフォリオ理論をPythonで実装したときと同じものを利用しるので割愛します。

www.stockdog.work



βの計算対象は、さくらネット(3778)にします。

市場は、TOPIX(998405)にしています。

とりあえず、pandasのDataFrameでさくらネットとTOPIXを統合したものを作成します。

Close_998405  Close_3778
1800.44         850
1783.49         852
1778.87         843
1744.01         811
1761.71         816
1763.76         817
1759.65         823
1771.13         831
1777.08         839
1780.56         820
1776.73         831
1772.07         816
1786.15         820
1792.08         833
1792.66         854


上記のデータが master_df に入っています。


returns_daily = master_df.pct_change()

# β = 証券iと市場ポートフォリオの相関 × 証券i の標準偏差 / 市場ポートフォリオの標準偏差			
tmp_df = returns_daily.loc[:, ["Close_998405", "Close_3778"]]

# 相関係数を計算
corr = tmp_df.corr().iloc[0, 1]

# それぞれの標準偏差を計算
std_i = returns_daily["Close_3778"].std()
std_m = returns_daily["Close_998405"].std()

# βの計算
beta = std_i * corr / std_m
# print("beta : ", beta)

プログラミング全体は、gitlabにおいています。

gitlab.com


上のコードでは、愚直にpandasで式を再現しています。

scipyの単回帰でもβを求めることができますし、こっちのほうがコードが短くてスマートです。

tmp_df = returns_daily.loc[:, ["Close_998405", "Close_3774"]]
x = [i[0] for i in tmp_df.iloc[:, [0]].values]
y = [i[0] for i in tmp_df.iloc[:, [1]].values]

slope, intercept, r_value, p_value, std_err = stats.linregress(x[1:len(x)], y[1:len(y)])
print("β : ", slope)


計算の結果は、どちらも以下になります。

β :  1.54465802366


計算期間は、2017/11/04 ~ 2018/07/12にしています。

まとめ

特になし!

以上おわり!

現代ポートフォリオ理論(MPT)をPythonで実装しよう

はじめに ~ 現代ポートフォリオ理論って何? ~

Modern Portfolio Theory(MPT) = 現代ポートフォリオ理論。

ハリー・マーコウィッツというおじさんが提唱した理論で、ノーベル賞を受賞しています。

ファイナンス理論の下敷きというか、基礎でとても大事な考え方!


この記事は、現代ポートフォリオ理論をpythonで実装します。

理論とともに、リアルワールドの株価データを使ってビジュアライズしながら見きましょう。


今日つかうコードはgitlabにおいています。

gitlab.com

現代ポートフォリオ理論の仮定

現代ポートフォリオ理論は、いくつかの仮定の上に成り立っています。

  • 投資家は合理的であり、できるだけリスクを避ける
  • 投資家はリターンを最大化することが目的である
  • 投資家は期待リターンを最大化する
  • 手数料と税は、とりあえず考えない
  • 投資家は全ての情報を取得することができ、取得できる情報のレベルも同じである
  • 投資家は、無限に無利子でお金を借りる


これらの仮定を理解した上で、現代ポートフォリオ理論とは...


投資家がポートフォリオを決めるときに、取れるリスクの中で最大のリターンの期待値を求める理論です。


現代ポートフォリオ理論の目の付け所がシャープだったのは、リスクとリターンの関係がポートフォリオにどう影響を与えているか、を分析した点です。

リターンをとればリスクが増える、というのは真実です。(ハイリスク・ハイリターン)

しかし、同じリスクでも期待できるリターンには差があります。


現代ポートフォリオ理論によって計算されたリターンとリスクの関係は、ビジュアライズすることができます。

2つの銘柄を組み合わせてポートフォリオを作成し、2つの銘柄の比率を変化させていきます。

比率を変化させていくごとに現代ポートフォリオ理論に則って、リターンとリスクを計算していくと、こんなグラフができあがります。

X軸がリスク、Y軸がリターンを表しています。

f:id:doz13189:20180707174856p:plain

これを効率的フロンティアと呼びます。

投資家が許容できるリスクの中で最大のリターンを期待できるポイントがこのグラフからひと目でわかります。

f:id:doz13189:20180707175236p:plain

この青部分と赤部分のリスクは同じです。

しかし、期待できるリターンには差があります。

これが同じリスクでも期待できるリターンには差がある、と言った意味です。


2つの銘柄の組み入れる比率を変えることで、期待できるリターンをコントロールすることができます。

これが現代ポートフォリオ理論において一番大事なことです。

Pythonで実装しよう

2つ以上の株価データを用意しましょう。

2つのデータは時系列で、かつ、終値があれば良しです。

pandasのDataFrame形式で扱います。

    Unnamed: 0       Date  Open  High   Low  Close   Volume  Adj Close
24           4  2018年6月4日  1234  1246  1233   1244  2091700       1244
23           3  2018年6月5日  1253  1267  1251   1266  3545600       1266
22           2  2018年6月6日  1264  1268  1258   1266  2834800       1266
21           1  2018年6月7日  1270  1279  1262   1276  3662600       1276
20           0  2018年6月8日  1276  1287  1266   1280  5072300       1280
4            4  2018年7月2日  1275  1283  1248   1251  2375800       1251
3            3  2018年7月3日  1255  1262  1226   1241  3435500       1241
2            2  2018年7月4日  1230  1270  1228   1265  2100400       1265
1            1  2018年7月5日  1269  1281  1250   1262  3467900       1262
0            0  2018年7月6日  1266  1267  1240   1248  3424000       1248


基本的には関数は使わず、べた書きでコードは貼っていきますが、株価データの取得だけはべた書きだと冗長になるので関数化しておきます。

import pandas as pd
import numpy as np
import matplotlib
import datetime
import time

matplotlib.use("Agg")
import matplotlib.pyplot as plt


def scraping(stock, days):

	end = datetime.date.today()
	start = end - datetime.timedelta(days=days)

	base = "http://info.finance.yahoo.co.jp/history/?code={0}.T&{1}&{2}&tm={3}&p={4}"

	start_g = str(start)
	start_g = start_g.split("-")
	start_g = "sy={0}&sm={1}&sd={2}".format(start_g[0], start_g[1], start_g[2])
	end = str(end)
	end = end.split("-")
	end = "ey={0}&em={1}&ed={2}".format(end[0], end[1], end[2])
	page = 1
	term = "d"
	#term = "m"

	result = []
	while True:
		url = base.format(stock, start_g, end, term, page)
		# print(url)
		try:
			df = pd.read_html(url, header=0)
		except ValueError:
			break

		if len(df[1]) == 0:
			break

		result.append(df[1])
		page += 1
		time.sleep(1.0)


	try:
		result = pd.concat(result)
		result.columns = ["Date", "Open", "High", "Low", "Close", "Volume", "Adj Close"]
		
		yahoo_df = pd.DataFrame(result)
		yahoo_df.to_csv("./data/code_" + str(stock) + ".csv", encoding="utf-8")

	except ValueError:
		pass


NTTデータ(9613)とSCSK(9719)のデータを使います。

今日(2018年7月7日)から180営業日前までのデータを取得します。

リターンとリスクを計算するときは、私はできるだけ長期間のデータを使用するべきだと考えています。

その理由は、長期間でなければその銘柄の本当のリターンとリスクが見えないからです。

短期的には経済など外部要因が大きく株価に影響を与えることは多いですから。

このコードは、スクレイピングした株価データをdataというフォルダに格納するので、dataフォルダを作成します。

stock_list = [9613, 9719]
days = 180
for s in stock_list:
	scraping(s, days)

株価データをconcatして、一つのDataFrameにします。

master_df = pd.DataFrame({})
for s in stock_list:
	df = pd.read_csv("./data/code_{0}.csv".format(s))
	df = df.sort_values("Date", ascending=True)
	print(df.tail(10))

	tmp_df = df.loc[:, ["Date", "Adj Close"]]
	tmp_df.columns = ["Date_{0}".format(s), "Close_{0}".format(s)]

	master_df = pd.concat([master_df, tmp_df["Close_{0}".format(s)]], axis=1)

データの加工は続きます。

リターンと共分散を計算します。

# Calculate annual average return # 年率を計算する前に日率を計算
# .pct_change:Rate of change # リターンを計算する便利な関数
print(master_df.tail(10))
returns_daily = master_df.pct_change()
print(returns_daily.tail(10))

# Convert to annual rate. working day is 250/365. # 年率に変換. 250は年間の市場が動いている日数.
returns_annual = returns_daily.mean() * 250
print("returns_annual")
print(returns_annual)

# Calculate covariance # 各々のリターンから共分散を計算
cov_daily = returns_daily.cov()
cov_annual = cov_daily * 250
# print("cov_annual")
# print(cov_annual)

DataFrameの加工は終了です。

さて、効率的フロンティアをえがきましょう!

NTTデータ(9613)とSCSK(9719)を様々な組み合わせ(50000パターン)でリターン・リスクを計算してきます。

※"Sharpe Ratio"の項目は今はムシしてください。後で使います。

# Define list of returns, volatilities, ratiosm, sharpe ratio # 諸々のリスト定義
port_returns = []
port_volatility = []
stock_weights = []
sharpe_ratio = []

# Choose a prime number. # seedは素数を選ぼう
np.random.seed(101)

# Number of stocks to be combined # 組み入れる株の数
num_assets = len(stock_list)

# Number of trials of portfolio creation pattern # ポートフォリオ作成パターンの試行回数
num_portfolios = 50000

# Randomly calculate portfolio risks and returns # 様々な銘柄の比率でのポートフォリオのリターンとリスクを計算
for single_portfolio in range(num_portfolios):

	# Determination of the ratio of stocks by random number # 銘柄の比率を乱数で決定
	weights = np.random.random(num_assets)
	weights /= np.sum(weights)

	# Calculate expected return on portfolio # ポートフォリオの期待リターンを計算
	returns = np.dot(weights, returns_annual)
	# print("returns : ", returns)

	# Calculate portfolio volatility # ポートフォリオのボラティリティを計算
	volatility = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights)))
	# print("volatility : ", volatility)

	# Calculate sharp ratio
	sharpe = returns / volatility
	sharpe_ratio.append(sharpe)

	# Store calculated values in list # 計算値をリストに格納
	port_returns.append(returns)
	port_volatility.append(volatility)
	stock_weights.append(weights)

# a dictionary for Returns and Risk values of each portfolio # 辞書型に格納
portfolio = {"Returns": port_returns,
			"Volatility": port_volatility,
			"Sharpe Ratio" : sharpe_ratio}

# Add ratio data # 計算したポートフォリオのリターンとリスクに、比率のデータを加える
for counter,symbol in enumerate(stock_list):
    portfolio[str(symbol) + " Weight"] = [Weight[counter] for Weight in stock_weights]

# Convert to DataFrame # PandasのDataFrameに変換
df = pd.DataFrame(portfolio)

# Done # データフレーム完成
column_order = ["Returns", "Volatility", "Sharpe Ratio"] + [str(stock)+" Weight" for stock in stock_list]
df = df[column_order]

print(df.head(10))

print(df.head(10)) で計算された様々なポートフォリオのパターンが格納されています。

seabornで可視化しましょう。

# look an efficient frontier # 50000パターンのポートフォリオを可視化して、効率的フロンティアを探る
plt.style.use("seaborn")
df.plot.scatter(x="Volatility", y="Returns", c="Sharpe Ratio", cmap="RdYlGn", edgecolors="black", figsize=(10, 8), grid=True)
plt.xlim([0,1])
plt.xlabel("Volatility (Std. Deviation)")
plt.ylabel("Expected Returns")
plt.title("Efficient Frontier")
plt.savefig("image.png")

f:id:doz13189:20180707175352p:plain

あまりきれいなグラフではありません。

なぜならNTTデータ(9613)とSCSK(9719)が同じ業種で同じような株価データだからです。

効率的フロンティアは、相関係数が低いほどきれいなカーブがえがけます。

SoftBank(9984)とTIS(3626)に入れ替えて、もう一度計算してみましょう。

stock_list = [9984, 3626]

f:id:doz13189:20180707174856p:plain

NTTデータ(9613)とSCSK(9719)の過去180営業日の相関係数は、0.866926です。

対し、SoftBank(9984)とTIS(3626)は、-0.380903です。

3つ以上の銘柄で効率的フロンティアをえがく

TIS(3626), NTTデータ(9613), Yahoo(4689), SoftBank(9984)でポートフォリオをつくってみましょう。

stock_list = [3626, 9613, 4689, 9984]

f:id:doz13189:20180707175448p:plain

もっとも効率のいいポートフォリオはどの組み合わせ?

効率のいいポートフォリオを見つけるためにシャープ・レシオを使います。

Sharp Ratio = (Expected return - Risk-free rate of return) / Standard deviation

シャープ・レシオ = (期待リターン - 無リスク資産) / 標準偏差

無リスク資産とは、債券などをさしていてい、今回はポートフォリオに入れていないので、考慮なしです。

そのため、実質の計算式は、

シャープ・レシオ = 期待リターン / 標準偏差

となります。

# Calculate sharp ratio
sharpe = returns / volatility
sharpe_ratio.append(sharpe)

この数値、さきほどポートフォリオを50000パターンの計算していたコードの中に既にあります。

DataFrameの中にも"Sharp Ratio"というカラムはあります。

そのため、最終的なアウトプットであるDataFrameをシャープ・レシオでソートすると、最も効率のいいポートフォリオを導くことができます。

df = df.sort_values("Sharpe Ratio", ascending=True)
print(df.tail(1))

# Returns  Volatility  Sharpe Ratio  3626 Weight  9613 Weight  \
# 0.691151    0.618261      1.117896     0.871933     0.008536

# 4689 Weight  9984 Weight
# 0.002589     0.116942

シャープ・レシオが1.117で、

資産の87.19%をTIS(3626)、0.8%をNTTデータ(9613)、0.2%をYahoo(4689)、11.69%をSoftBank(9984)に振り分けるポートフォリオが一番最適であるとなりました。

過去180営業日しかとっていないので、TIS買っとけ、みたいな分析結果になってしまいました。

ちなみに、シャープ・レシオに色をつけて、可視化するともっとわかりやすくなります。

df.plot.scatter(x="Volatility", y="Returns", c="Sharpe Ratio", cmap="RdYlGn", edgecolors="black", figsize=(10, 8), grid=True)
# df.plot.scatter(x='Volatility', y='Returns', figsize=(10, 8), grid=True)

f:id:doz13189:20180707175546p:plain

まとめ

久々にちゃんと記事書きました。褒めて。

以上、終わり!

非対称な収益率

収益率の計算方法についての話。(時間も時間なので最低限でまとめます)

収益率の計算は、だいたい2種類あります。

  • 収益率(ノーマル)
  • 対数収益率


よく使われる収益率は、これ↓

ほげほげ銘柄を2018/7/2、100円で買いました。

10日間持っていると、200円になりました。

このときのリターンは、

200円 / 100円 = 2.0 = 200%


この収益率の計算方法がめじゃーではあるのですが、問題点が一つあります。

それは非対称なところです。


例えば、ほげほげ銘柄がこんな値動きをしていたとします。

日付 2018/07/02 2018/07/03 2018/07/04 2018/07/05 2018/07/06 2018/07/07
ほげほげ銘柄 100円 99円 90円 100円 110円 100円

2018/07/02 ~ 2018/07/07まで、毎日ほげほげ銘柄を売買したとします。
100円で買い、次の日に99円で売る。 -1円の損失
その日のうちに99円で買い、次の日に90円で売る。 - 9円の損失
その日のうちに90円で買い、次の日に100円で売る。 +10円の利益
その日のうちに100円で買い、次の日に110円で売る。 +10円の利益
その日のうちに110円で買い、次の日に100円で売る。 -10円の損失

2018/07/07には、この取引の利益と損失はプラスマイナスゼロになります。


しかし、これを収益率で表すとそうはならないです。これが非対称性のことやねん。

収益率の計算式は、↓これです↓。

今日の株価 / 前日の株価

日付 2018/07/02 2018/07/03 2018/07/04 2018/07/05 2018/07/06 2018/07/07
ほげほげ銘柄 100円 99円 90円 100円 110円 100円
収益率 - 0.99 0.9090909090909091 1.1111111111111112 1.1 0.9090909090909091


この収益率をトータルすると、+1.003858585858586となります。

%に変換すると+0.3%になります。

取引の収益は、ゼロなのに収益率にするとプラスになってしまい、これが非対称性と呼ばれています。


そのため、収益率を計算するときは対数に変換して、収益率は計算されます。

日付 2018/07/02 2018/07/03 2018/07/04 2018/07/05 2018/07/06 2018/07/07
ほげほげ銘柄 100円 99円 90円 100円 110円 100円
ほげほげ銘柄(対数) 4.605170185988092 4.59511985013459 4.499809670330265 4.605170185988092 4.700480365792417 4.605170185988092
収益率 - 0.99 0.9090909090909091 1.1111111111111112 1.1 0.9090909090909091


対数で計算をするので、計算式は↓これです↓。

今日の株価(対数) - 前日の株価(対数)

日付 2018/07/02 2018/07/03 2018/07/04 2018/07/05 2018/07/06 2018/07/07
ほげほげ銘柄 100円 99円 90円 100円 110円 100円
ほげほげ銘柄(対数) 4.605170185988092 4.59511985013459 4.499809670330265 4.605170185988092 4.700480365792417 4.605170185988092
収益率 - 0.99 0.9090909090909091 1.1111111111111112 1.1 0.9090909090909091
対数収益率 -0.010050335853502013 -0.09531017980432477 0.10536051565782678 0.09531017980432477 -0.09531017980432477


対数収益率はトータルしても、プラスマイナスゼロとなり、取引の結果と一致します。

まとめ

収益率の計算は、必ず対数収益率を使いましょう。


検証に使ったコードです。

import math
import numpy as np


# logに変換
def calc_log(num):
	log = [math.log(i) for i in num]
	return log


# 対数収益率を計算
def calc_return(log):
	r_list = [log[i+1] - log[i] for i in range(len(log)-1)]
	print(r_list)
	r = sum(r_list) / len(r_list)
	return r

# 収益率を計算
def calc_return_int_ver(log):
	r_list = [log[i+1] / log[i] for i in range(len(log)-1)]
	print(r_list)
	r = sum(r_list) / len(r_list)
	return r


# データセット
x = [100, 99, 90, 100, 110, 100]

# logに変換したデータセット
log_x = calc_log(x)
print("log(x)")
print(log_x)

# 収益率
return_x_int_ver = calc_return_int_ver(x)
print("x return int ver : ", return_x_int_ver)

# 対数収益率
return_x = calc_return(log_x)
print("x return : ", return_x)

seleniumの2つの待機方法

seleniumの待機方法でsleep.time()を使っている人は、この記事を読んで、心を清めてほしい。


seleniumには、2つの待機方法がある。

  • Explicit Waits(明示的な待機)
  • Implicit Waits(暗黙的な待機)

この2つの待機方法の使い分けは、こんな感じ。

  • Explicit Waits = 複雑な条件で待機させたい
  • Implicit Waits = 単純な待機

Implicit Waitsは、要素が出現するまで待機する、という設定になる。

そのため、ごくごく単純なスクレイピング、例えば静的サイトなどであればImplicit Waitsで十分である。


しかし、非同期のWebサイトであればImplicit Waitsでは対応できないことが多い。

例えば、スクロールしてくことでページが読み込まれていくサイトのスクレイピングなど。


非同期のWebサイトからスクレイピングするときは、「このログインボタンがクリックできるようになるまで待つ」などの条件を指定したほうが、エラーは出にくいコードが書ける。


ま、使い分けと言いつつ、実際に使うときはImplicit Waitsに10秒くらい指定して、各ポイントでExplicit Waitsに30秒とかを指定することが多い。

implicitly_waitの使い方

driver = webdriver.Chrome()
driver.implicitly_wait(20)

これだけ。

とても簡単。書くのは1回だけ。

デフォルトは、0が設定されている。

上の例では、要素が読み込まれるまで20秒待機している。

例えば、Webページの読み込むスピードが単純に遅いときは、コードが実行された時点で要素が現れておらず、エラーとなることがある。

selenium.common.exceptions.ElementNotVisibleException: Message: element not visible: Element is not currently visible and may not be manipulated

seleniumを使っていれば、このエラーに100%エンカウントしているはず。

その時は、implicitly_wait(20)の待機時間を充分な時間を指定して、ページが読み込まれるのを待てばいい。


ただ、implicitly_waitは基本的に一律の設定である。

この処理は何秒待つ、とかの指定はできない。

できないことはないけど、絶対に人に見せたくないコードになる。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

driver.implicitly_wait(5)
driver.find_element_by_id("mul_t").send_keys("ログインしたい")

driver.implicitly_wait(20)
driver.find_element_by_xpath('//*[@id="control_object_class1"]/div/div[3]/div[1]/div').click()

driver.implicitly_wait(10)
driver.find_element_by_name("forward_sch").click()

Explicit Waitsの使い方

Explicit Waitsは複雑な条件を指定して、条件が満たされるまで待機するという設定ができる。

この例は、"sch"というIDを持つ要素がクリックできるようになるまで最大30待機する、という指示。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

WebDriverWait(driver, 30).until(EC.element_to_be_clickable((By.ID, "sch")))

# 探している要素↓ よくある検索ボタンのhtmlだと思う。
# <input type="button" value="検索" id="sch">


seleniumの公式ページにものっているし、定番はこれ。

IDが"myDynamicElement"の要素が読み込まれるまで待機。使いやすい。

WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.ID, "myDynamicElement"))


presence_of_element_located以外にも指定できる条件はある。

title_is
title_contains
presence_of_element_located
visibility_of_element_located
visibility_of
presence_of_all_elements_located
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable
staleness_of
element_to_be_selected
element_located_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
alert_is_present


また、By.はID以外も指定できる。

ID
XPATH
LINK_TEXT
PARTIAL_LINK_TEXT
NAME
TAG_NAME
CLASS_NAME
CSS_SELECTOR

まとめ

大抵のサイトは、非同期技術が使ってあるため、requestsでリクエストをWebサイトに送って、返ってきたレスポンスをbeautifulsoup4で読み込む、という定番のスクレイピング方法では対応できないことが多い。

ただ、seleniumでも非同期技術を考慮なしに、スクレイピングをしても、とても不安定なコードになってしまう。

面倒ではあるが、ある程度は考えて待機時間は設定したほうが、後々のメンテも楽になるので、がんばりましょう。


最後にtime.sleep()で待機させてから、スクレイピングをしている方へ。

time.sleep()は、要素が読み込まれていようといまいと待機することになるので、時間がもったいないからやめよう。

以上、終わり!!

seleniumで起きるエラー ~unknown error: unable to discover open pages~

発生エラー

selenium.common.exceptions.WebDriverException: Message: unknown error: unable to discover open pages

発生原因

stackoverflowにナイスな回答あり!

https://stackoverflow.com/questions/21001652/chrome-driver-error-using-selenium-unable-to-discover-open-pages


chromedriverのバージョン古くないか? ← まずこれを疑え

次は、google-chromeのバージョン古くないか? ← これも疑え

とのこと。

簡単な解決方法は、バージョンアップしよう、ってことになる。

バージョンアップしよう

chromedriver編

バージョンごとにインデックスされているので、目的のバージョンをインストールしよう。

http://chromedriver.storage.googleapis.com/index.html


最新版のバージョンはここから確認可能。

http://chromedriver.storage.googleapis.com/LATEST_RELEASE


2018/06/23時点での最新版は2.40なので、2.40をインストールする。

$ wget https://chromedriver.storage.googleapis.com/2.40/chromedriver_linux64.zip
$ unzip chromedriver_linux64.zip -d ~/bin/


起動するとバージョンが出てくるので、インストールできているかを確認する。

$ chromedriver

google-chrome

$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
$ sudo dpkg -i google-chrome-stable_current_amd64.deb
$ sudo apt-get update
$ sudo apt-get grade


最新版がインストールできているかを確認する。

$ google-chrome --version


最新版は、このサイトから確認できる。

https://www.whatismybrowser.com/guides/the-latest-version/chrome

なぜバージョンアップで解決できる?

seleniumがブラウザを自動制御するときには以下の構成になっている。

selenium >> chromedriver >> google-chrome

seleniumは直接は、ブラウザ(google-chrome)は制御できないので、chromedriverを介して制御する。

ユーザーによっては、googl-chromeFireFoxだったり。

このエラーは、chromedriverとgoogle-chromeのどちらかが古いことで起きる。

chromedriverのバージョンが古すぎると、google-chomeを制御できなし、その逆もしかり。

chrome-dirverには、サポートしているchromedriverのバージョンの幅も調べたら出てくるので、ちゃんと確認したければ、そこを確認する。

これでもまだ解決しないとき

chromedriverをダウングレードすると解決することが多い。

これに関しては、なぜかは不明。。。

現在最新版の2.40をインストールしてもエラーが解決できなかったため、順にダウングレードしていって2.38版でエラーなく動作するようになった。

$ wget https://chromedriver.storage.googleapis.com/2.39/chromedriver_linux64.zip

  ↓↓↓↓↓↓

$ wget https://chromedriver.storage.googleapis.com/2.38/chromedriver_linux64.zip

chromedriverは結構頻繁にバージョンが更新されているようで、こまめにバージョンを気にしないとバグのもとに...

まとめ

seleniumエラー多い。嫌い。

以上、終わり!

フォントサイズをレスポンシブ対応させるCSS

@media を使うと、CSSに条件を追加することができる。

これで状況によってCSSを変更することが可能に。

例えば、ブラウザのサイズによってフォントのサイズを変えたい時とか。つまり、レスポンシブ対応。

@mediaの使い方

@mediaのあとに条件を指定する。

なんとなく文法がjavascriptっぽい。

@media (min-width: 1200px) {

  html {
    font-size: 11px
  }

}

ブラウザのサイズが1200px以上であれば、フォントサイズを11pxに指定している。

CSS書いてみる

ブラウザはだいたい3タイプくらいを想定。

条件は以下に設定。

100px以上のブラウザサイズ:font-size:8px
768px以上のブラウザサイズ:font-size:10px
1200px以上のブラウザサイズ:font-size:11px

@media (min-width: 100px) {

  html {
    font-size: 8px
  }

}


@media (min-width: 768px) {

  html {
    font-size: 10px
  }

}


@media (min-width: 1200px) {

  html {
    font-size: 11px
  }

}

フォントをレスポンシブ対応させる意味

それは、単純に1200pxのPC画面と600pxのスマホ画面では、フォントサイズを変えたほうが見やすくなるから。

1200pxでちょうどいい大きさだと、600pxでは大抵大きすぎる。


試しにフォントサイズを変えてみる。


1200px以上のブラウザでは、フォントサイズが心地良い。

f:id:doz13189:20180519202349p:plain


しかし、このままのフォントサイズだとスマホでは、少々ブサイクになる。

f:id:doz13189:20180519202412p:plain


そのため、デフォルトのフォントサイズを12pxから8pxに。

f:id:doz13189:20180519202437p:plain


個人的にはこのサイズ感が心地良い。

まとめ

フォントサイズは、大きすぎると1つの画面に入る情報量が少なくなってしまうので、適切な大きさにしよう!

特にスマホは画面が小さいので、入れる情報の量はちゃんとコントロールしよう!


以上、終わり!!