【Python】株価のテクニカル分析指標から売買タイミングを自動計算
以前に「pandas-datareader」と「mplfinance」を用いて、テクニカル分析チャートを作成しました。その際に「MACD」や「RSI」などのテクニカル分析指標を算出しました。
今回は、これらの指標から売買のタイミングを計算します。
1銘柄ずつチャートを目視していくのは大変です。注目した方がよい銘柄を自動で割り出して、その銘柄のチャートだけを見るようにして手間を省きたいのです。
- [1] 株価の取得、テクニカル分析指標の算出
- [2] 今回使用するデータ
- [3] ゴールデンクロス・デッドクロス(MACD)
- [4] ボトムアウト・ピークアウト(ヒストグラム)
- [5] RSI
- 終わりに
- 出典
[1] 株価の取得、テクニカル分析指標の算出
以前の記事で紹介した内容で行います。
ここまで済めば、テクニカル分析チャートまでは作れます。
[2] 今回使用するデータ
ソフトバンク(9984.JP)の2021/04/01〜07/13の株価を使用します。
[3] ゴールデンクロス・デッドクロス(MACD)
MACDとシグナルが重なるときが、売買のタイミングと言われています。
詳しい説明は、下記サイト等を参考にしてみてください。
[3-1] ソースコード
MACDとシグナルが重なるのは差が0になるタイミングです。つまりヒストグラムが0を跨ぐタイミングです。
# ヒストグラムと日付を取り出す hists = df['Hist'] dates = df.index # 日数分ループ for i in range(1, hists.size): # 2日分取り出し h1 = hists.iloc[i-1] h2 = hists.iloc[i] # ゴールデンクロス・デッドクロスを判定 if h1 < 0 < h2: # ゴールデンクロス print(f'{dates[i]:%Y-%m-%d} {code} ゴールデンクロス {h1:.1f},{h2:.1f}') elif h1 > 0 > h2: # デッドクロス print(f'{dates[i]:%Y-%m-%d} {code} デッドクロス {h1:.1f},{h2:.1f}')
以下の出力結果が得られます。
# 2021-04-20 9984.JP デッドクロス 13.8,-12.3 # 2021-06-03 9984.JP ゴールデンクロス -0.1,15.5 # 2021-07-07 9984.JP デッドクロス 1.6,-10.4 # 2021-07-13 9984.JP ゴールデンクロス -6.9
[3-2] チャート
出力結果をチャート上に示すと、赤枠と青枠の部分になります。赤枠がゴールデンクロス、青枠がデッドクロスです。
[3-3] 計算通りに売買したら成功したか
04/20の売りは、5月に入り株価が下落したため成功と言えます。一方、06/03の売りは7月中旬時点では成功とは言えません。
また、07/07, 07/13と短期間にデッドクロス・ゴールデンクロスが発生しています。頻繁に発生するような場合は、売買タイミングと考えるのは難しそうです。
[4] ボトムアウト・ピークアウト(ヒストグラム)
ヒストグラムが天井・天底をうつときも売買の目安と言われています。
ゴールデンクロス・デッドクロスよりも反応が早いと言われています。詳しい説明は、下記サイト等を参考にしてみてください。
[4-1] ソースコード
ヒストグラムを用いて計算しますが、小さな変化を拾いすぎないように以下の条件としています。
- ボトムアウト:2日連続で減少後に2日連続で上昇 and 負値
- ピークアウト:2日連続で上昇後に2日連続で減少 and 正値
減少・上昇は日毎の差分をh5d.diff()
で取得し、差分の正負で判断しています。
# ヒストグラムと日付を取り出す hists = df['Hist'] dates = df.index # 日数分ループ for i in range(0, hists.size-5): # 5日分取り出し h5d = hists.iloc[i:i+5] # 日毎の差分を取得 hdiff = h5d.diff() # ボトムアウト・ピークアウトを判定 if (hdiff[1] < 0) and (hdiff[2] < 0) and \ (hdiff[3] > 0) and (hdiff[4] > 0) and \ (h5d[2] < 0): # ボトムアウト hist_values = f'{h5d[0]:.1f},{h5d[1]:.1f},{h5d[2]:.1f},{h5d[3]:.1f},{h5d[4]:.1f}' print(f'{dates[i+2]:%Y-%m-%d} {code} ボトムアウト {hist_values}') elif (hdiff[1] > 0) and (hdiff[2] > 0) and \ (hdiff[3] < 0) and (hdiff[4] < 0) and \ (h5d[2] > 0): # ピークアウト hist_values = f'{h5d[0]:.1f},{h5d[1]:.1f},{h5d[2]:.1f},{h5d[3]:.1f},{h5d[4]:.1f}' print(f'{dates[i+2]:%Y-%m-%d} {code} ピークアウト {hist_values}')```
以下の出力結果が得られます。
# 2021-04-30 9984.JP ボトムアウト -18.8,-30.0,-37.2,-30.4,-29.4 # 2021-05-17 9984.JP ボトムアウト -180.7,-221.7,-246.1,-234.2,-222.5 # 2021-06-16 9984.JP ピークアウト 41.9,45.6,47.1,40.6,32.8
参考までに、条件を2日連続ではなく1日とすると、ボトムアウト5回・ピークアウト5回という結果になります。
[4-2] チャート
出力結果をチャート上に示すと、赤枠と青枠の部分になります。
[4-3] 計算通りに売買したら成功したか
- 2021/04/30 ボトムアウトで買い:失敗
- 2021/05/17 ボトムアウトで買い:失敗
- 2021/06/16 ピークアウトで売り:成功
少なくとも今回の例では、良い結果が得られませんでした
[4-4] 条件を変えたらどうなるか
2日連続上昇後に減少の条件から「2日連続」を無くしてみます。
# 2021-04-21 9984.JP ボトムアウト -12.3,-23.9,-19.8 # 2021-04-23 9984.JP ボトムアウト -19.8,-23.8,-16.4 # 2021-04-30 9984.JP ボトムアウト -30.0,-37.2,-30.4 # 2021-05-17 9984.JP ボトムアウト -221.7,-246.1,-234.2 # 2021-06-07 9984.JP ピークアウト 20.9,30.9,29.9 # 2021-06-16 9984.JP ピークアウト 45.6,47.1,40.6 # 2021-06-28 9984.JP ピークアウト 33.4,47.1,45.9 # 2021-06-30 9984.JP ピークアウト 45.9,47.2,44.7 # 2021-07-02 9984.JP ピークアウト 44.7,46.8,17.1 # 2021-07-09 9984.JP ボトムアウト -17.9,-22.5,-6.9
売買のタイミングは判断できませんが、傾向を見るのには役立つかもしれません。ボトムアウト・ピークアウトは傾向を見るにとどめるのが良さそうです。
ソースコードは次の通りです。
# ヒストグラムと日付を取り出す hists = df['Hist'] dates = df.index # 日数分ループ for i in range(0, hists.size-3): # 3日分取り出し h3d = hists.iloc[i:i+3] # 日毎の差分を取得 hdiff = h3d.diff() # ボトムアウト・ピークアウトを判定 if (hdiff[1] < 0 < hdiff[2]) and (h3d[1] < 0): # ボトムアウト hist_values = f'{h3d[0]:.1f},{h3d[1]:.1f},{h3d[2]:.1f}' print(f'{dates[i+1]:%Y-%m-%d} {code} ボトムアウト {hist_values}') elif (hdiff[1] > 0 > hdiff[2]) and (h3d[1] > 0): # ピークアウト hist_values = f'{h3d[0]:.1f},{h3d[1]:.1f},{h3d[2]:.1f}' print(f'{dates[i+1]:%Y-%m-%d} {code} ピークアウト {hist_values}')
[5] RSI
RSIは70%以上だと買われすぎ、30%以下だと売られすぎと言われています。
- RSIが70%以上:買われすぎなので売りのタイミング
- RSIが30%以下:売られすぎなので買いのタイミング
詳しい説明は、下記サイト等を参考にしてみてください。
[5-1] ソースコード
単純にRSIの値で判断すれば良いので、これまで紹介した2つに比べてだいぶシンプルです。
# 日数分ループ for i, rsi in enumerate(df['RSI']): if rsi > 70: # RSI > 70% print(f'{df.index[i]} RSI>70% {rsi:.1f}') elif rsi < 30: # RSI < 30% print(f'{df.index[i]} RSI<30% {rsi:.1f}')
以下の出力結果が得られます。この例ではRSIが70%以上になりませんでした。
# 2021-05-13 00:00:00 RSI<30% 24.4 # 2021-05-14 00:00:00 RSI<30% 27.6 # 2021-05-17 00:00:00 RSI<30% 24.1 # 2021-05-18 00:00:00 RSI<30% 25.1 # 2021-05-19 00:00:00 RSI<30% 24.4 # 2021-05-20 00:00:00 RSI<30% 21.8 # 2021-05-21 00:00:00 RSI<30% 22.7 # 2021-05-24 00:00:00 RSI<30% 22.4 # 2021-05-25 00:00:00 RSI<30% 23.6 # 2021-05-26 00:00:00 RSI<30% 18.7 # 2021-05-27 00:00:00 RSI<30% 17.8 # 2021-05-28 00:00:00 RSI<30% 21.1 # 2021-05-31 00:00:00 RSI<30% 25.5 # 2021-06-17 00:00:00 RSI<30% 25.7 # 2021-06-18 00:00:00 RSI<30% 27.6 # 2021-06-21 00:00:00 RSI<30% 23.2 # 2021-06-23 00:00:00 RSI<30% 27.0
[5-2] チャート
出力結果をチャート上に示すと、赤線がRSI=30%のラインです。
[5-3] 計算通りに売買したら成功したか
- 2021/05/13〜05/31 RSI<30%で買い:失敗
- 2021/06/17〜06/23 RSI<30%で買い:失敗
売られすぎ(RSI<30%)の期間が二度ありました。このタイミングで買っていたら、いずれも失敗でした。
RSIは横ばいの相場で有効と言われています。この例では下降トレンドであったため効力を発揮しなかったものと思われます。
[5-4] 別銘柄の場合はどうか
JR東日本(9020.JP)なら、どういう結果になったのか見てみます。
- 2021/04/21〜04/22 RSI<30%で買い:成功
- 2021/06/02〜06/14 RSI>70%で売り:成功
- 2021/06/29〜06/30 RSI<30%で買い:成功
逆に上手くいき過ぎていて気持ちが悪いです。
MACDを用いた指標で売買しても成功していました。
終わりに
テクニカル分析指標だけで売買を判断するは難しいようです。ですが、判断材料としては十分に使えそうです。
売買のタイミングを感情で左右してまうと失敗します。とくに慣れない頃はそうでした。客観的な指標も用いて、冷静に判断できればと思いました。
出典
Python 100行以内で書ける株価のテクニカル分析チャート(pandas-datareader & mplfinance)
Pythonで株価を取得してローソク足チャートを作ったところ、驚くほど簡単に書けました。「pandas-datareader」と「mplfinance」を使用しましたが、ソースコードの行数は60行でした。以下のようなグラフが簡単に作れます。
[1] 株価の取得
以前の記事で紹介した、pandas-datareaderを用いて株価を取得します。
import datetime import pandas_datareader.data as web # ソフトバンクの2021年1月以降の株価 code = '9984.JP' start_date = datetime.datetime(2021, 1, 1) end_date = None # pandas-datareaderでStooqから株価取得 df = web.DataReader(code, 'stooq', start=start_date, end=end_date)
先頭をprintしてみると、次のようなデータが取得できています。
print(df.head()) # Open High Low Close Volume # Date # 2021-04-01 9480 9615 9372 9391 13843700 # 2021-03-31 9084 9362 8995 9330 12246400 # 2021-03-30 9146 9320 9081 9172 12463600 # 2021-03-29 9388 9389 8980 9080 16428700 # 2021-03-26 9202 9369 9131 9238 11754500
[2] ローソク足チャートの表示
「mplfinance」を使ってローソク足チャートを作成します。「mplfinance」が優秀すぎて数行で書けてしまいます。
import mplfinance as mpf # 日付で昇順ソース df = df.sort_index() # ローソク足チャートを表示 mpf.plot(df, type='candle', mav=(5, 25), volume=True, savefig='9984_JP.png')
ポイントは次の通りです。詳細な使用方法は「mplfinance」のGitHubに詳しく載っています。
type='candle'
でローソク足チャートを指定しています。typeを変えるとチャートの形状を変更できます。mav=(5, 25)
で移動平均線の期間を指定しています。今回は5, 25日間の移動平均線を描画します。volume=True
で出来高を描画します。savefig
で保存先のファイル名を指定しています。
GitHub - matplotlib/mplfinance: Financial Markets Data Visualization using Matplotlib
[3] MACDをチャートに追加
テクニカル分析指標の一つであるMACDをチャートに追加します。
[3-1] MACDの算出
MACDの算出方法には何パターンかあるようですが、次の通りにしました。
# MACD exp12 = df['Close'].ewm(span=12, adjust=False).mean() exp26 = df['Close'].ewm(span=26, adjust=False).mean() df['MACD'] = exp12 - exp26 # シグナル df['Signal'] = df['MACD'].rolling(window=9).mean() # ヒストグラム(MACD - シグナル) df['Hist'] = df['MACD'] - df['Signal']
[3-2] MACDをチャート表示
# MACDとシグナルのプロット作成 add_plot = [mpf.make_addplot(df['MACD'], color='m', panel=1, secondary_y=False), mpf.make_addplot(df['Signal'], color='c', panel=1, secondary_y=False), mpf.make_addplot(df['Hist'], type='bar', color='g', panel=1, secondary_y=True)] # ローソク足チャートを表示 mpf.plot(df, type='candle', mav=(5, 25), volume=True, addplot=add_plot, volume_panel=2, savefig='9984_JP.png')
mpf.make_addplot
で追加したいチャートを作り、mpf.plot
の引数addplot
にセットします。
[4] RSIをチャートに追加
テクニカル分析指標の一つであるRSIをチャートに追加します。
[4-1] RSIの算出
RSIは移動平均を取る期間を色々と変えれますが、今回は14日間としています。
# 終値の差分 df_diff = df['Close'].diff() # 値上がり幅と値下がり幅 df_up, df_down = df_diff.copy(), df_diff.copy() df_up[df_up < 0] = 0 df_down[df_down > 0] = 0 df_down = df_down * -1 # 14日間の単純移動平均 sim14_up = df_up.rolling(window=14).mean() sim14_down = df_down.rolling(window=14).mean() # RSI df['RSI'] = sim14_up / (sim14_up + sim14_down) * 100
[4-2] RSIをチャート表示
add_plot
にRSIのチャートを追加します。
# MACDとRSIのプロット作成 add_plot = [mpf.make_addplot(df['MACD'], color='m', panel=1, secondary_y=False), mpf.make_addplot(df['Signal'], color='c', panel=1, secondary_y=False), mpf.make_addplot(df['Hist'], type='bar', color='g', panel=1, secondary_y=True), mpf.make_addplot(df['RSI'], panel=2)] # ローソク足チャートを表示 mpf.plot(df_sort, type='candle', mav=(5, 25), volume=True, addplot=add_plot, volume_panel=3, savefig='9984_JP.png')
[5] チャートの見栄えを変える
「mplfinance」に用意された引数を変えると、チャートの見栄えを変えることも簡単にできます。
# MACDとRSIのプロット作成 add_plot = [mpf.make_addplot(df['MACD'], color='m', panel=1, secondary_y=False), mpf.make_addplot(df['Signal'], color='c', panel=1, secondary_y=False, ylabel='MACD'), mpf.make_addplot(df['Hist'], type='bar', color='g', panel=1, secondary_y=True, ylabel='Hist'), mpf.make_addplot(df['RSI'], panel=2, ylabel='RSI')] # ローソク足チャートを表示 mpf.plot(df, type='candle', mav=(5, 25), volume=True, addplot=add_plot, volume_panel=3, title='9984.JP', figratio=(5, 4), panel_ratios=(6, 3, 3, 2), style='nightclouds', savefig='9984_JP.png')
「mpfinanceのGitHub」と以下の記事を参考にさせてもらいました。
Python mplfinanceでローソク足を作る | novonovo
終わりに
「pandas-datareader」と「mplfinance」を使って、チャートを簡単に描くことができました。MACDやRSIの追加も数十行で行うことができました。
参考資料
移動平均線が進化!MACD(マックディー)の見方と活用法 | 俺たち株の初心者!
RSIの見方・使い方 | テクニカル分析指標 | 指標の見方・使い方 | 投資のノウハウ | 株の達人
GitHub - matplotlib/mplfinance: Financial Markets Data Visualization using Matplotlib
Python mplfinanceでローソク足を作る | novonovo
備考
インストール手順
環境によってインストール手順は異なる場合があります。pip3コマンドが使用できる環境では以下の手順でインストール可能でした。
pip3 install numpy --user pip3 install pandas --user pip3 install matplotlib --user pip3 install pandas-datareader --user pip3 install mplfinance --user
出典
- アイキャッチはGerd AltmannによるPixabayからの画像
pandas-datareaderでTOPIX500銘柄の株価上昇率を算出
<pandas-datareaderでTOPIX500銘柄の株価上昇率を算出>*1
前回はpandas-datareaderで米国株の株価を取得しました。
今回は、東証一部上場銘柄の株価を取得し、業種ごとの株価変動を可視化しました。
[1] 東証上場銘柄を取得する
[1-1] JPX(日本取引所グループ)から取得
東証上場銘柄の一覧は、JPX(日本取引所グループ)のページから取得します。
中身は次のようなデータになっています。
[1-2] CSVに変換して読み込み
ExcelをCSV形式に保存したのち、PythonからpandasのDataFrameとして読み込みます。
import pandas as pd tse_list = pd.read_csv('data_j.csv', header=0)
[2] TOPIX500銘柄の業種ごとの株価上昇率を算出
東証上場銘柄のうち、TOPIX500銘柄にターゲットを絞ります。ちなみに、全銘柄だと4,086銘柄あります*2。
[2-1] TOPIX500銘柄のデータを抽出
'市場・商品区分'で東証一部上場銘柄を抽出します。次に、'規模区分'でTOPIX500銘柄を抽出します。
# TOPIX500の銘柄を抽出する tse1 = tse_list[tse_list['市場・商品区分'] == '市場第一部(内国株)'] topix500 = tse1[(tse1['規模区分'] == 'TOPIX Core30') | (tse1['規模区分'] == 'TOPIX Large70') | (tse1['規模区分'] == 'TOPIX Mid400')]
いったん東証一部上場銘柄を抽出していますが、省略してTOPIX500銘柄を抽出することも可能です。
なお、TOPIXについては、下記ページに詳しく載っています。
[2-2] 業種ごとの銘柄数を確認
業種ごとの銘柄数を確認すると、電気機器の49銘柄が最多です。
# 33業種コード,33業種区分ごとの銘柄数を算出 category_count = topix500.groupby(['33業種コード','33業種区分']).size() print(category_count) # 33業種コード 33業種区分 # 1050 鉱業 1 # 2050 建設業 20 # 3050 食料品 28 # 3100 繊維製品 5 # 3150 パルプ・紙 3 # 3200 化学 46 # 3250 医薬品 22 # 3300 石油・石炭製品 3 # 3350 ゴム製品 4 # 3400 ガラス・土石製品 8 # 3450 鉄鋼 7 # 3500 非鉄金属 7 # 3550 金属製品 6 # 3600 機械 32 # 3650 電気機器 49 # 3700 輸送用機器 20 # 3750 精密機器 10 # 3800 その他製品 10 # 4050 電気・ガス業 13 # 50 水産・農林業 2 # 5050 陸運業 24 # 5100 海運業 2 # 5150 空運業 2 # 5200 倉庫・運輸関連業 2 # 5250 情報・通信業 32 # 6050 卸売業 23 # 6100 小売業 33 # 7050 銀行業 26 # 7100 証券、商品先物取引業 5 # 7150 保険業 6 # 7200 その他金融業 9 # 8050 不動産業 12 # 9050 サービス業 25 # dtype: int64
[2-3] 各銘柄の株価上昇率を計算
ここでは、2020/11/2〜2021/2/5の株価上昇率を計算します。
[2-3-1] 業種コード・業種区分を抽出
まずは、業種コード・業種区分を抽出します。
# 33業種コード,33業種区分を抽出 industry_category = topix500.groupby(['33業種コード','33業種区分']).groups.keys() print(industry_category) # dict_keys([('1050', '鉱業'), ('2050', '建設業'), ('3050', '食料品'), # ('3100', '繊維製品'), ('3150', 'パルプ・紙'), ('3200', '化学'), # ('3250', '医薬品'), ('3300', '石油・石炭製品'), ('3350', 'ゴム製品'), # ('3400', 'ガラス・土石製品'), ('3450', '鉄鋼'), ('3500', '非鉄金属'), # ('3550', '金属製品'), ('3600', '機械'), ('3650', '電気機器'), # ('3700', '輸送用機器'), ('3750', '精密機器'), ('3800', 'その他製品'), # ('4050', '電気・ガス業'), ('50', '水産・農林業'), ('5050', '陸運業'), # ('5100', '海運業'), ('5150', '空運業'), ('5200', '倉庫・運輸関連業'), # ('5250', '情報・通信業'), ('6050', '卸売業'), ('6100', '小売業'), # ('7050', '銀行業'), ('7100', '証券、商品先物取引業'), ('7150', '保険業'), # ('7200', 'その他金融業'), ('8050', '不動産業'), ('9050', 'サービス業')])
[2-3-2] 業種ごとに株価を取得
抽出した、業種コード・業種区分ごとに株価を取得します。期間は3ヶ月分よりも長く取得したいのですが、Stooqの株価取得には制限があります。具体的な基準は分かりませんが、一定回数か容量を超えると空のDataFrameが返ってくるので、3ヶ月分に留めています。
# 2020/11/2〜2021/2/5の株価を取得する base_date = datetime.datetime(2020, 1, 6) end_date=datetime.datetime(2021, 2, 5) # 業種単位の株価上昇率格納用のDataFrameを用意する category_df = None # 業種ごとに変動率を計算する for category in industry_category: category_code = category[0] # 33業種コード category_class = category[1] # 33業種区分 # 指定した業種の銘柄を抽出 brands = topix500[topix500['33業種区分'] == category_class] # 銘柄コードの末尾に.JPを付加する symbols = [] for code in brands['コード']: symbols.append('{0:d}.JP'.format(code)) # 指定銘柄コードの株価を取得する stock_price = web.DataReader(symbols, 'stooq', start=base_date, end=end_date) print(stock_price) # Attributes Close ... Volume ... # Symbols 1414.JP 1721.JP 1801.JP ... 1883.JP 1893.JP 1911.JP ... # Date ... ... # 2021-02-05 4600.0 3315.0 3545.0 ... 262000 877200 476600 ... # 2021-02-04 4630.0 3245.0 3510.0 ... 304100 864200 547700 ... # ... ... ... ... ... ... ... ... ... # 2020-11-04 5200.0 2789.0 3325.0 ... 85900 1494600 520500 ... # 2020-11-02 5130.0 2678.0 3280.0 ... 140500 647000 409200 ...
TOPIX500銘柄すべての株価を一度に取得し、業種単位に分割することも可能と思われます。しかし、一度に大量のデータは扱いたくないので、業種単位で取得しています。
[2-3-3] 各銘柄の株価上昇率を計算
株価を取得した銘柄ごとに株価の上昇率を計算します。計算結果はディショクナリにいったん格納し、最後にDataFrameに変換します。
# 銘柄ごとに上昇率を計算し、ディショクナリに格納する dict = {} for symbol in symbols: # 基準日付からの上昇率を計算する base_date_str = base_date.strftime('%04Y-%02m-%02d') base_price = stock_price.loc[base_date_str][('Close',symbol)] increase_rate = stock_price[('Close',symbol)] / base_price.iloc[0] # ディクショナリに格納する dict[symbol] = increase_rate # ディクショナリからDataFrame作成 df = pd.DataFrame(dict)
[2-4] 業種ごとの平均値, 標準偏差を算出
業種ごとの株価上昇率について、平均値と標準偏差を計算します。計算結果はDataFrameに追加していきます。
# 業種内銘柄の上昇率の平均値を計算する mean = df.mean(axis='columns') std = df.std(axis='columns') # DataFrameに業種単位の上昇率と、上昇率の標準偏差を格納する if category_df is None: category_df = pd.concat([mean, std], axis=1) category_df.columns = pd.MultiIndex.from_tuples( [(category_class, '上昇率'), (category_class, '標準偏差')]) else: category_df[(category_class, '上昇率')] = mean category_df[(category_class, '標準偏差')] = std print(category_df) # 鉱業 建設業 # 上昇率 標準偏差 上昇率 標準偏差 # Date # 2021-02-05 1.308617 NaN 1.170162 0.130083 # 2021-02-04 1.274549 NaN 1.157672 0.130904 # ... ... ... ... ... # 2020-11-04 1.054108 NaN 1.007299 0.013290 # 2020-11-02 1.000000 NaN 1.000000 0.000000 # # [65 rows x 4 columns]
鉱業はTOPIX500銘柄が「国際石油開発帝石」しかないため、標準偏差が計算できずNaNとなっています。複数銘柄ある場合には標準偏差が計算されます。
[2-5] 折れ線グラフで株価上昇率を可視化
まずは、業種ごとに株価上昇率を折れ線グラフで可視化します。2020年11月〜2021年2月は、バイデン大統領の当選や金融緩和の継続もあり、全体的に株価が上昇していました。
import matplotlib.pyplot as plt import matplotlib.dates as mdates # 上昇率のみを抽出し、業種の数を取得する increase_rate_df = category_df.loc[:, pd.IndexSlice[:, '上昇率']] industry_num = len(increase_rate_df.columns) # rows, colsを決定し、Figureを取得 rows, cols = (6, 6) fig = plt.figure(figsize=(20, 16)) # 上昇率の最大値と最小値を取得 min_y = increase_rate_df.min().min() max_y = increase_rate_df.max().max() # 業種ごとに折れ線グラフを表示する for i in range(industry_num): # Axesを取得 ax = fig.add_subplot(rows, cols, i+1) # 銘柄名 category_name = increase_rate_df.columns[i][0] # 上昇率を取得 increate_rate = increase_rate_df.loc[:, pd.IndexSlice[category_name, '上昇率']] # 折れ線グラフを表示 x = increase_rate_df.index y = increate_rate ax.plot(x, y, label=category_name) # 目盛り線を表示 ax.grid(color='gray', linestyle='--', linewidth=0.5) # X軸の目盛り位置を設定 ax.xaxis.set_major_locator(mdates.MonthLocator()) # Y軸の範囲を設定 ax.set_ylim(min_y, max_y) # グラフのタイトルを追加 ax.set_title(category_name) # 不要な余白を削る plt.tight_layout() # グラフを表示 fig.show()
[2-6] 棒グラフで株価上昇率を可視化
次は上昇率を棒グラフで可視化します。
棒グラフを表示する前に、一度DataFrameを作成し直しています。これは、上昇率でソートするためです。元のDataFrameはMultiIndexになっており、そのままの形ではソートが難しかったためです。
# 最新日付を取得する latest_date = category_df.index.max() latest_date_str = latest_date.strftime('%04Y-%02m-%02d') # 最新日付の上昇率, 標準偏差を抽出する increase_rate = category_df.loc[latest_date_str, pd.IndexSlice[:, '上昇率']] std = category_df.loc[latest_date_str, pd.IndexSlice[:, '標準偏差']] # 業種名をリストで取得 categories= [col[0] for col in increase_rate.columns] # 上昇率と標準偏差を列にもつDataFrameを作成し、上昇率で降順ソートする df = pd.DataFrame({'上昇率': increase_rate.values[0], '標準偏差': std.values[0]}, index=categories) df = df.sort_values('上昇率', ascending=False) print(df) # 上昇率 標準偏差 # 海運業 1.329285 0.148943 # 電気機器 1.313898 0.194788 # 非鉄金属 1.312967 0.141995 # 鉱業 1.308617 NaN # 石油・石炭製品 1.291704 0.1598 # ...(以下略)...
barh
で可視化します。xerr
を使用して、標準偏差をエラーバーとして表示しています。
# 図と座標軸を取得 fig = plt.figure(figsize=(10, 10)) ax = fig.add_subplot(1,1,1) # 棒グラフに表示するデータを準備 x = np.arange(len(categories)) y = df['上昇率'] yerr = df['標準偏差'] label = df.index # 棒グラフ表示 ax.barh(x, y, xerr=yerr, tick_label=label) # 目盛り線を表示 ax.grid(axis='x', color='gray', linestyle='--', linewidth=0.5) # 不要な余白を削る plt.tight_layout() # グラフを表示 fig.show()
[3] TOPIX500銘柄の銘柄ごとの株価上昇率を算出
[3-1] 銘柄ごとの株価上昇率を算出
業種ごとの平均値, 標準偏差を算出したのと同様の方法で行います。pandas-datareaderによる株価取得は業種単位で行い、そののち銘柄ごとの株価上昇率を計算します。
# 33業種コード,33業種区分を抽出 industry_category = topix500.groupby(['33業種コード','33業種区分']).groups.keys() # 銘柄単位の株価上昇率格納用のディクショナリを用意する brand_dict = {} brand_count = 0 # 業種ごとに変動率を計算する for category in industry_category: category_code = category[0] # 33業種コード category_class = category[1] # 33業種区分 # 指定した業種の銘柄を抽出 brands = topix500[topix500['33業種区分'] == category_class] # 銘柄コードの末尾に.JPを付加する symbols = [] for code in brands['コード']: symbols.append('{0:d}.JP'.format(code)) # 指定銘柄コードの株価を取得する stock_price = web.DataReader(symbols, 'stooq', start=base_date) # 銘柄ごとに上昇率を計算し、ディショクナリに格納する dict = {} for symbol in symbols: # 銘柄ごとに上昇率を計算し、ディショクナリに格納する # 基準日付からの上昇率を計算する base_date_str = base_date.strftime('%04Y-%02m-%02d') base_price = stock_price.loc[base_date_str][('Close',symbol)] increase_rate = stock_price[('Close',symbol)] / base_price.iloc[0] # ディクショナリに格納する dict[symbol] = increase_rate # ディクショナリからDataFrame作成 df = pd.DataFrame(dict) # 最新日付を取得する latest_date = stock_price.index.max() latest_date_str = latest_date.strftime('%04Y-%02m-%02d') # 銘柄単位の株価上昇率をディクショナリに格納する for i, symbol in enumerate(symbols): code = brands['コード'].iloc[i] name = brands['銘柄名'].iloc[i] increase_rate = df.loc[latest_date_str, symbol].iloc[0] # '33業種コード','33業種区分', 'コード', '銘柄名', '上昇率']) brand_dict[brand_count] = [category_code, category_class, code, name, increase_rate] brand_count += 1 # 銘柄単位の株価上昇率を格納したDataFrameを作成する brand_df = pd.DataFrame.from_dict( brand_dict, orient="index", columns=['33業種コード','33業種区分', 'コード', '銘柄名', '上昇率'])
業種ごとの株価上昇率は日単位で算出していましたが、銘柄については最新日付の株価上昇率のみとしています。
[3-2] 株価上昇率と上位10, 下位10銘柄を確認
[3-2-1] 上位10銘柄
まずは上位10銘柄を確認します。
# 上昇率で降順ソート df_sorted = brand_df.sort_values('上昇率', ascending=False) # 上位銘柄を抽出 df_top = df_sorted.head(10)
電気機器・輸送用機器銘柄の上昇が目立ちます。
33業種区分 | 銘柄名 | 上昇率 |
---|---|---|
電気機器 | コニカミノルタ | 1.854610 |
電気機器 | シャープ | 1.839968 |
電気機器 | ジーエス・ユアサ コーポレーション | 1.838553 |
輸送用機器 | 川崎重工業 | 1.784906 |
輸送用機器 | マツダ | 1.675393 |
情報・通信業 | コナミホールディングス | 1.661905 |
その他金融業 | 東京センチュリー | 1.648956 |
輸送用機器 | 日産自動車 | 1.634646 |
輸送用機器 | ヤマハ発動機 | 1.589777 |
機械 | IHI | 1.581 |
[3-2-2] 下位10銘柄
次に下位10銘柄を確認します。
# 上昇率で昇順ソート df_sorted = brand_df.sort_values('上昇率', ascending=True) # 下位銘柄を抽出 df_bottom = df_sorted.head(10)
銀行業が多めであり、最大で17%弱の減少です。2020年11月〜2021年2月は、TOPIXが17%近く上昇していることもあってか、極端に大きな下落にはなっていませんでした。
33業種区分 | 銘柄名 | 上昇率 |
---|---|---|
銀行業 | 滋賀銀行 | 0.835329 |
銀行業 | 九州フィナンシャルグループ | 0.857708 |
繊維製品 | ゴールドウイン | 0.859155 |
情報・通信業 | 光通信 | 0.867015 |
サービス業 | ベネッセホールディングス | 0.871878 |
食料品 | 東洋水産 | 0.872659 |
小売業 | ウエルシアホールディングス | 0.874092 |
銀行業 | 山口フィナンシャルグループ | 0.886364 |
食料品 | カゴメ | 0.888743 |
銀行業 | 中国銀行 | 0.894793 |
終わりに
pandas-datareaderでTOPIX500銘柄の株価を取得し、業種ごとの株価上昇率を可視化しました。以前にスクレイピングで株価を取得しましたが、pandas-datareaderの方が簡単に株価を取得できました。
ただ、取得できるデータ数が限られるという利用制限があります。一度取得したデータを保存しておく等の工夫は必要だとは感じました。過去データはpandas-datareaderで取得・保存しておいて、最新の株価はスクレイピングで取得するのがよいのかもしれません。
pandas-datareaderのインストール手順と米国株の株価取得方法
<pandas-datareaderで米国株の株価取得>*1
「pandas-datareader」で株価を取得します。以前、日本株の株価をスクレイピングで取得しましたが、「pandas-datareader」の方がより簡単に取得できました。
バンガード S&P500 ETF (VOO)の株価を取得し、グラフ表示するまでの手順を紹介します。
[1] インストール
[1-1] pipでインストール
公式の手順に従ってインストールします。pipコマンドでインストールが可能です。
pip3 install pandas-datareader
最新版をインストールする場合は、GitHubからダウンロードしインストールします。
pip3 install git+https://github.com/pydata/pandas-datareader.git
[1-2] 動作確認
正常に動作するか確認します。
import pandas_datareader as pdr # 米国国債10年を5年分取得 gs10 = pdr.get_data_fred('GS10') print(gs10) # GS10 # DATE # 2016-03-01 1.89 # 2016-04-01 1.81 # ...(途中省略)... # 2020-11-01 0.87 # 2020-12-01 0.93
ダウ平均株価が正常に取得できました。
[2] データソースを決める
[2-1] 使用可能なデータソースを確認する
pandas-datareaderでは複数のデータソースから、株価や為替の情報が取得できます。取得できる情報は下記リンクに掲載されています。
Remote Data Access — pandas-datareader 0.9.0rc1+2.g427f658 documentation
ユーザ登録しAPIキーの取得が必要なデータソースもあります。今回はAPIキー無しで株価が取得可能な「Stooq」を選択しました。
[2-2] Stooqでデータを取得してみる
公式に載っている、Stooqでダウ平均を取得するソースコードを実行してみます。
import pandas_datareader.data as web # ダウ平均をSqooqで取得 f = web.DataReader('^DJI', 'stooq') print(f.head()) # Open High Low Close Volume # Date # 2021-01-29 30553.91 30553.91 29856.30 29982.62 638488708 # 2021-01-28 30377.19 30951.41 30377.19 30603.36 528513252 # 2021-01-27 30893.78 30893.78 30206.91 30303.17 655432794 # 2021-01-26 30968.55 31121.42 30921.71 30937.04 441398941 # 2021-01-25 30989.85 30989.85 30564.06 30960.00 532855231
ダウ平均株価が正常に取得できました。
[3] 株価を取得する
[3-1] VOOの株価取得
バンガード S&P500 ETF (VOO)の株価を取得してみます。
import datetime # バンガード S&P500 ETF (VOO)の2020年以降の株価を取得 voo = web.DataReader( 'VOO', 'stooq', start=datetime.datetime(2020, 1, 1) ) print(voo) # Open High Low Close Volume # Date # 2021-01-29 345.31 346.2200 338.57 340.18 5803647 # 2021-01-28 345.97 351.0707 345.58 347.13 3432188 # ...(途中省略)... # 2020-01-03 291.08 293.1900 290.90 292.08 3294270 # 2020-01-02 293.12 294.2600 292.18 294.23 3194572 # # [272 rows x 5 columns]
DataReaderのAPI仕様は下記URLで参照できます。pandas_datareader.data.DataReader
はラッパーになっています。stooq
を指定するとStooqDailyReader
が実際には使われます。
Stooq.com — pandas-datareader 0.9.0rc1+2.g427f658 documentation
[3-2] 株価をグラフ表示
取得したVOOの株価をmatplotlibでグラフ表示します。
import matplotlib.pyplot as plt # 図と座標軸を取得 fig = plt.figure() ax = fig.add_subplot(1,1,1) # 折れ線グラフをセット ax.plot(voo.index, voo['Close'], label='VOO') ax.grid(axis='y', color='gray', ls=':') ax.legend() # 折れ線グラフを表示 fig.show()
終わりに
驚くべきほど簡単に株価が取得できました。取得した時点でDataFrameになっているのが最大のメリットですね。
しかも、株価だけでなく国債利回りやダウ平均、為替まで取得できるので、本当に無料で良いのかなと思ってしまうほどです。
二輪車業界の株価予想(2020年12月)
ここでいう二輪車に自転車は含みません。バイクや原付のことと思ってください。
※ 株式投資はくれぐれも自己責任でお願いします。
[1] 二輪車業界の主要プレイヤー
ホンダ、ヤマハ発動機、スズキ、川崎重工です。
2018年度の世界販売台数はホンダが2,023万台、売上は2兆1,000億円で世界第1位です。ヤマハ発動機の世界販売台数は第3位です*3。
企業 | 売上 | 世界販売台数 |
---|---|---|
ホンダ | 2兆1,001億円 | 2,023万台 |
ヤマハ発動機 | 1兆221億円 | 537万台 |
スズキ | 2,550億円 | 174万台 |
川崎重工 | 3,568億円 | 55万台 |
[2] 二輪車の需要
[2-1] 世界の需要
二輪市場は新興国が牽引しています。上から、インド、中国、インドネシア、ベトナムの順です。中国は自動車への以降期に入ったため減少すると見込まれています。市場全体で見れば、しばらくは緩やかな増加が見込まれます。
二輪車・バイク業界の動向・ランキング等を研究-業界動向サーチ
[2-2] 日本の需要
日本では減少の一途を辿っています。2013年と比べると、2020年は23%減です。かつては、原付第一種(最大30km/h)が販売台数の半分以上を占めていましたが激減しました。中型・大型二輪は原付ほどは減っていません。
理由は原付に対する規制が厳しくなったことと、自転車に取って変わられていることです。電動アシスト付き自転車の売上は年々増えています。また、最近はロードバイクなどのスポーツ自転車も流行っていますね。
[3] 株価の推移
[3-1] 2020年の株価推移
コロナ禍で2月から3月にかけて大きく下落したものの、11月の大統領選挙後に大きく回復しています。通年で見ると年始と年末でほとんど変わっていません。 後述の通り、各社とも減収減益とは言え利益が出ることを受けてのことでしょう(川崎重工以外)。販売台数は回復の兆しを見せています。
[3-2] 2016年以降の株価推移
スズキ以外は減少傾向にあります。各社とも二輪車以外の事業も行っていますので、減少の要因は各社それぞれです。要因についてはこの後見ていきます。
[4] 各社の販売台数推移と内訳
[4-1] ホンダ
二輪事業は9割がアジアを占めています。もう1つの主力事業である、四輪事業においてもアジアが4割を占めており、アジアでの販売台数が重要であると分かります。
販売台数の推移を見ると、コロナ禍で2019年度と2020年度は減少しています。2016年から2018年度で見ると、二輪車の増加率が大きいことが分かります。
ライフクリエーション事業の販売台数は、パワープロダクツ(発電機、耕うん機、芝刈機、除雪機)などで構成されています。
[4-2] ヤマハ発動機
ヤマハ発動機もアジアでの二輪車販売台数が8割以上を占めています。
ヤマハ発動機は、マリン事業が二輪車事業に継ぐ主力事業です。マリン事業は、ボートやウォータービークルの販売などを行っています。販売台数ではなく売上高の内訳ですが、こちらは北米が6割を占めています。
販売台数の推移を見ると、近年はやや減少傾向です。ヤマハ発動機の決算は12月なので、新型コロナウィルスの影響は含まれていません。
一方、マリン事業の方は二輪車事業と比較すると堅調です。ただ、2018年から2019年は横ばいなので、成長しているとは言えません。
[4-3] スズキ
スズキも二輪車の販売台数の8割はアジアが占めています。特に、インドでの売上が4割を占めている点が大きな特徴です。
スズキは、四輪事業が主力であり、二輪事業が売上や利益に占める割合は大きくありません(後述)。そのため、スズキの場合は四輪事業の成否が株価に大きく影響します。
販売台数の推移を見ると、コロナ禍の影響を受けています。コロナ以前(2015年度〜2018年度)は、四輪事業・二輪事業ともに販売台数を伸ばしています。
四輪事業はインドでの販売台数が大きく伸びており、日本での販売台数も順調に増えています。
[4-4] 川崎重工
川崎重工は販売台数の地域別内訳は無く、先進国・新興国で別れていました。内訳を見ると他の3社同様に、新興国の販売台数が大きな割合を占めています。
コロナ以前は販売台数が増加してましたが、2019年度はコロナ禍の影響で減少しています。
[5] 各社のセグメント別売上と利益
[5-1] ホンダ
ホンダの売上高の6割は四輪事業が占めています。一方、営業利益は内訳が年度ごとに異なっており、二輪車事業と金融サービス業が安定しています。
世界第1位の販売台数を誇る二輪車事業は、四輪事業と並ぶ利益を上げています。しかし、成長が鈍化していることや、為替影響などもあり年々利益率が低下していることが懸念点です。
[5-2] ヤマハ発動機
売上高の半分はランドモビリティ事業*4が占めており、ついでマリン事業となっています。一方、営業利益はマリン事業が半分を占めており、マリン事業の利益率が高いと分かります。
ヤマハ発動機の決算は12月のため、このデータはコロナ禍の影響を織り込んでいません。最新の決算資料を見ると、当然コロナ禍の影響はあり、2020年12月期は減収減益を見込んでいます。
2019年度実績を見ると、ランドモビリティ事業とマリン事業は減益ですが、これは為替の影響もあります。ロボティクス事業は市況悪化による減益ですが、2019年度実績は概ね前年度と横ばいと言えます。
[5-3] スズキ
スズキは四輪事業が売上高・営業利益ともに9割を占めています。コロナ禍の影響で2019年度は減収減益となっていますが、2018年までは販売台数が伸び続けており順調と言えます。
二輪事業はスズキの場合には売上高に占める割合は低く、利益もゼロに近いです。
スズキも当然新型コロナウィルスの影響を受けています。四輪車の販売台数は全ての地域で減少を見込んでいます。主戦場であるインド・アジアも大きな減少を見込んでいるため、今年度も減収減益も見込んでいます。
[5-4] 川崎重工
川崎重工の事業分野は他3社に比べて多岐に渡っています。その中でも航空宇宙システムが売上・営業利益のおよそ30%を占めていました。
しかし、コロナ禍の影響で航空業界が大打撃を受けていることから、2020年度見通しでは赤字予想となっています。
二輪車事業は、モーターサイクル&エンジンに含まれます。売上高・営業利益ともに2割程度を占めています。コロナ禍により、2019年度は減収減益、2020年度見通しも減収減益です。
その他の事業も減益の見通しとなっており、2020年度は赤字予想です。
まとめ
[6-1] ホンダ
ホンダは二輪車の販売台数で世界第1位です。世界の販売台数の約30%を占めています。四輪事業の規模も大きく、売上高の6割を占めています。また、金融サービス業の利益率が高く、毎年利益を出しています。
懸念点は成長の鈍化です。二輪車のインドでの販売台数は鈍化しており、インドや台湾メーカーも台頭してきています。
いずれ、インドは自動車への以降期に入ります。二輪車の販売台数は横ばいになり、四輪車の販売台数はより増えると予想されます。また、インド以外の新興国での二輪車の販売台数は増えるでしょう。
今後、インドや新興国で二輪車と四輪車の販売台数を増やせるかが株価上昇のカギとなりそうです。私はコロナ禍の収束により、2016年〜2017年の株価水準に戻る可能性はあるものの、それ以上の上昇の可能性は低いと見込んでいます。
事業は二輪車・四輪車の2つが主力であり、金融サービス業もホンダ製品販売のリースや融資です。二輪車と四輪車の販売台数増加には限度があるため、大きな株価上昇は無いという予想です。
[6-2] ヤマハ発動機
ヤマハ発動機もホンダ同様にアジアでの二輪車販売台数の増加が重要です。
しかし、先進国を中心としたマリン事業が好調ですし、ロボティクス事業を持っています。今後のロボット需要増加の中で売上を伸ばすことができれば、株価上昇も期待できます。
[6-3] スズキ
4社の中で唯一、2016年1月基準で株価が上昇しているのがスズキです。四輪車事業が9割の会社ですが、その四輪車事業が好調です。新型コロナウィルスの影響もあり2019年度は減収減益です。しかし、新興国でなく日本国内の販売台数も伸びており、利益も出ていることから株価が大きく下がる心配は無さそうです。
電気自動車やシェアライドが普及し始めたときに対抗できるかどうかは疑問です。しかし、普及にはまだしばらく時間がかかりそうです。そのため、コロナ禍の収束により一定の水準まで株価が上昇し、数年間は安定すると予想しています。
[6-4] 川崎重工
川崎重工は、二輪車事業の規模は大きくないものの、多岐に渡る事業で利益を出してきました。1事業がマイナスでも他事業で補えていました。しかし、コロナ禍により稼ぎ頭であった航空宇宙システムが大きな減益(前年比-500億円以上)となり、今年度は赤字となりそうです。
また、気になる点は借入が増えていることです。自己資本比率は25%前後でしたが、20%近くまで低下する見通しです。
コロナ禍の影響はしばらく続きそうです。そのため、航空業界の回復にはしばらく時間がかかりそうです。航空宇宙システムの回復も遅れると想定されるため、会社全体の利益回復には時間がかかりそうです。
そのため、株価もしばらくは低調な状態が続くと予想されます。
終わりに
今回は二輪車業界の決算資料と株価を元に、今後の株価を予想してみました。
自分自身、二輪車業界に詳しいわけでもなく、二輪車に乗るわけでもありませんでした。なので、決算資料を見て業界のことが少しわかっただけでも収穫でした。
この予想が当たるのか外れるのか楽しみに待ちたいと思います。願わくば、コロナ禍が早く収束し、予想を裏切って各社の業績が回復して欲しいです。
参考資料
鉄道業界の株価予想(2020年12月)
<鉄道業界の株価予想(2020年12月)>
*1
前回までに行ったスクレイピングで得た情報も取り入れつつ、株価予想をしていきます。
コロナウィルスの影響を大きく受けている鉄道業界の株価予想をします*2。
※ 株式投資はくれぐれも自己責任でお願いします。
[1] 2020年の株価推移
コロナウィルスの影響で株価は大きく下落しています。
特に新幹線事業を行っているJR九州・JR東日本・JR東海・JR西日本の下落幅が大きいです。 それ以外は各社に依りけりです。東急は年初と比較して40%近く下落していますが、他社は最大30%弱の下落幅に留まっています。
[2] JR東日本の状況
JR東日本の決算情報から、新幹線事業を持つ企業の状況を見ていきます。
[2-1] セグメント別の収益
鉄道会社は運輸収入*3だけで稼いでるわけではありません。不動産事業やサービス事業でも稼いでいます。例えば、JR東日本は駅ビル・オフィスビル・ホテルなどの不動産を所持しています。また、アトレやエキュートなどの小売業も積極的に行っています。
コロナウィルスの影響が無い、2019年3月期の決算情報から収益の割合を見てみます。運輸事業が営業収益(売上)・営業利益ともに 70%近くを占めています。
運輸事業の中での内訳を見ると、在来線が6割、新幹線が3割程度です。つまり、JR東日本の収益の40%は在来線、20%は新幹線ということです。
[2-2] 2020年第2四半期の実績
2020年4月〜9月の実績は、営業収益が50%減です。特に新幹線の輸送量は前年度比70%減であり、コロナウィルスの影響が顕著です。不動産事業は前年同期比と比較して30%減、サービス事業は50%減となっています。
2020年通期の純利益は、4,000億円マイナスの予想です。コロナウィルスの影響が軽減されれば業績は戻っていくと思いますが、しばらくは厳しい状況が続きそうです。2020年3月期の純利益が約2,000億円ですから、かなり大きいマイナスです。
[3] 近鉄GHDの状況
続いて、JR系と比較して株価の落込みが小さい、近鉄GHDの決算状況を見ていきます。
近鉄グループホールディングス株式会社|株主・投資家情報|IR資料|決算説明会
[3-1] セグメント別の収益
JR東日本と同様に、コロナウィルスの影響が無い、2019年3月期の決算情報から収益の割合を見てみます。運輸事業が占める比率は、営業収益が17%, 営業利益が49%です。JR東日本の68%, 70%と比較すると、運輸事業への依存度は低いことが分かります。
[3-2] 2020年第2四半期の実績
2020年4月〜9月の実績は、営業収益が54%減です。2020年通期の純利益は、310億円マイナスの予想です。50%減収という点は、JR東日本と変わりません。
セグメント別の営業収益を見ると、運輸が40%減、不動産が20%、流通が25%減、ホテル・レジャーが90%減です。
運輸の影響はJR東日本と比べて小さいのですが、ホテル・レジャーの減益がかなり大きいと予想されています。ホテル・レジャーの営業利益は360億円のマイナス予想になっています。
[4] 2016年からの株価推移
2016年1月4日の株価を基準とした、株価の上昇率はグラフの通りです。
全体的にみると、2020年初頭までは20%程度上昇し、2020年に入ってから大きく下落しています。
2016年1月から2020年1月までの間に、TOPIXは20%近く上昇しました。 TOPIXと同じ上昇率なので、鉄道業界が目立って浮き沈みはしていないと言えます。
[5] 鉄道輸送量の推移
政府の統計ページ e-Statより、鉄道輸送量の年度別推移を見てみます。緩やかに上昇していましたが、2019年度に減少に転じています。日本の人口は減少し続けているので、今後大きく増えていくことは期待できません。緩やかに減少していくでしょう。
鉄道業界の主な収益は運輸収入です。輸送量が減れば運輸収入も減りますので、鉄道業界が大きく成長することは見込めないわけです。成長するとしたら別セグメントが成長するか海外展開でしょう。
[6] 今後の株価予想
しばらくは低調な状態が続きそうです。本格的に回復するのは、コロナウィルスのワクチン接種が始まってからでしょう。
JR系と民鉄で比較すると、民鉄の方が低迷が続くと思います。ホテル・レジャー事業のマイナスが大きいからです。数年間は国内旅行の減少、インバウンドの減少は戻らないと予想しています。
一方、JR系はコロナウィルスの影響が落ち着けば、運輸収入はだいぶ戻るでしょう。株価も元に戻っていくと予想できます。ただし、今後大きな成長が見込める業界ではありません。株価が上昇するとしても、日経平均やTOPIXと近い上昇率で推移すると思われます。
日本株の株価をpythonでスクレイピングを使って取得しmatplotlibで可視化する(複数銘柄)
前回の続きです。前回は単一銘柄の株価をスクレイピングで取得しmatplotlibで可視化しました。今回は複数銘柄の株価を取得し比較していきます。
[1] 複数銘柄の株価取得
get_stock_price
, astype_stock_price
, add_moving_average_stock_price
は前回のソースコードを関数化したものです。各銘柄に対してスクレイピングを行い、取得した結果をDataFrameの末尾に結合していきます。
import time import pandas as pd codes = { 'JR東日本': 9020, 'JR西日本': 9021 } start_year, end_year = 2019, 2020 df = None for name in codes.keys(): # 指定した証券コードの決算情報を取得する。 code = codes[name] df_tmp = get_stock_price(code, start_year, end_year) # 株価情報のデータタイプを修正する df_tmp = astype_stock_price(df) # 株価情報に移動平均を追加する df = add_moving_average_stock_price(df) # 銘柄名を追加し、MultiIndexにする。 df_tmp['銘柄'] = name df_tmp = df_tmp.set_index('銘柄', append=True) # 2回目以降は既存のDataFrameの末尾にデータを追加 if df is None: df = df_tmp else: df = df_tmp.append(df) # 1秒ディレイ time.sleep(1) # indexを入れ替える df = df.swaplevel('銘柄', '日付').sort_index()
結果、次のデータが得られました。
始値 | 高値 | 安値 | 終値 | 出来高 | 終値調整 | 5日移動平均 | 25日移動平均 | 75日移動平均 | ||
---|---|---|---|---|---|---|---|---|---|---|
銘柄 | 日付 | |||||||||
JR東日本 | 2019-01-04 | 9675.0 | 9820.0 | 9627.0 | 9731.0 | 1337200.0 | 9731.0 | NaN | NaN | NaN |
2019-01-07 | 9918.0 | 10065.0 | 9915.0 | 10030.0 | 887500.0 | 10030.0 | NaN | NaN | NaN | |
... | ... | ... | ... | ... | ... | |||||
JR西日本 | 2020-12-03 | 5085.0 | 5249.0 | 5077.0 | 5233.0 | 1869000.0 | 5233.0 | 4986.8 | 4907.92 | 5172.11 |
2020-12-04 | 5299.0 | 5414.0 | 5257.0 | 5317.0 | 1745900.0 | 5317.0 | 5038.2 | 4932.32 | 5173.95 |
[2] 可視化
[2-1] 2020年以降のデータを抽出
DataFrameをMultiIndexしたことにより、日付による抽出方法が少し変わっています。pd.IndexSlice
を用いて抽出します。
# 2020/01/01以降のデータを抽出 df = df.loc[pd.IndexSlice[:, '2020-01-01':], :] # (前回) df = df['2020-01-01':]
下記記事を参考にさせてもらいました。
pandasのMultiIndexから任意の行・列を選択、抽出 | note.nkmk.me
[2-2] 株価の可視化
まずは、終値をそのまま折れ線グラフで可視化してみます。抽出したデータを折れ線グラフで可視化します。
# Figureを取得 fig = plt.figure(figsize=(10, 4)) # 銘柄名のリスト取得 brand_names = list(df.index.unique('銘柄')) # 銘柄別に折れ線グラフで表示する for i, brand_name in enumerate(brand_names): # Axesを取得 ax = fig.add_subplot(1, 2, i+1) # 表示するデータを抽出 df_brand = df.loc[brand_name,] x = df_brand.index # 日付 y = df_brand['終値'] # 折れ線グラフ表示 label = '{0:s}-終値'.format(brand_name) ax.plot(x, y, label=label, linewidth=2.0) # 移動平均の折れ線グラフを追加 average_columns = ['5日移動平均', '25日移動平均', '75日移動平均'] for column in average_columns: x = df_brand.index # 日付 y = df_brand[column] # 移動平均 label = '{0:s}-{1:s}'.format(brand_name, column) ax.plot(x, y, label=label, linewidth=1.0) # 目盛り線を表示 ax.grid(color='gray', linestyle='--', linewidth=0.5) # 凡例を表示 ax.legend() # グラフのタイトルを追加 ax.set_title(brand_name) # 不要な余白を削る plt.tight_layout() # グラフを表示 fig.show()
[2-3] 値上がり率の可視化
株価は発行済株数によって変わるので単純比較はできません。そこで、ある日付を基準とした値上がり率で比較してみます。
2020/01/06を基準とした比率で言うと、JR西日本の方が株価が大きく下落しています。
# 基準日を設定 ref_date = datetime.date(2020, 1, 6) ref_date_str = ref_date.strftime('%04Y-%02m-%02d') # FigureとAxesを取得 fig = plt.figure() ax = fig.add_subplot(1,1,1) # 銘柄別に値上がり率を計算 for brand_name in brand_names: # 指定銘柄のデータを取得 df_brand = df.loc[brand_name,] # 値上がり率を計算 base_price = df_brand.loc[ref_date_str,'終値'] # 基準日の終値 rate = df_brand['終値'] / base_price - 1 # 値上がり率 # 表示するデータを抽出 x = df_brand.index # 日付 y = rate # 折れ線グラフを表示 ax.plot(x, y, label=brand_name) # 目盛り線を表示 ax.grid(color='gray', linestyle='--', linewidth=0.5) # 凡例を表示 ax.legend() # Y軸の単位をパーセント表示に設定 ax.yaxis.set_major_formatter(mpl.ticker.PercentFormatter(1)) # グラフのタイトルを追加 ax.set_title('値上がり率') # グラフを表示 fig.show()
期間をもう少し長く取ると、JR西日本は2019年〜2020年にわたって株価が大きく上昇していました。そのため、2020年の下落幅も大きかったことがわかります。
[2-4] 株式分割に注意
株式分割等で株価が急激に変わった場合は補正が必要です。例えば、株価¥10,000だった銘柄が株式分割で1株を10株に分割すると、株価は¥1,000になります。
株価は10分の1になりますが、1株あたりの価値は変わりません。この場合、株式分割後の株価を10倍にする補正が必要でしょう。
[2-5] 補足: DataFrameに値上がり率を追加
ちなみに、元のDataFrameに値上がり率を追加するためには以下のようにします。
# 値上がり率の列を用意 df['値上がり率'] = np.nan for brand_name in brand_names: ...(中略)... # 値上がり率をDataFrameに追加 # (rateはSeries. そのまま代入するとindexが合わないためNG) df.loc[(brand_name, ), '値上がり率'] = rate.values
終わりに
株価をスクレイピングで取得し、複数銘柄間の比較を行ました。上記では'JR東日本'と'JR西日本'の2銘柄のみ扱いましたが、銘柄数を増やすことは用意です。いちど仕組みを作ってしまえば応用することは難しくありません。
そもそもスクレイピングを始めた目的は、株式投資のために企業分析を行うことでした。より詳しい分析を行うためには各企業の決算情報なども見ないと足りないなと感じました。
ただ、今回行ったことは分析の取っ掛かりとしては悪くありませんでした。その業界全体の傾向を見る場合や、その業界のなかで他と値動きが異なる企業を探す場合に活用できそうです。
[補足]
[補足1] get_stock_pricesのソースコード
def get_stock_price(code, start_year, end_year): whole_df = None headers = None # 指定した年数分の株価を取得する years = range(start_year, end_year+1) for year in years: # 指定URLのHTMLデータを取得 url = 'https://kabuoji3.com/stock/{0:d}/{1:d}/'.format(code, year) html_headers ={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'} html = requests.get(url, headers=html_headers) # BeautifulSoupのHTMLパーサーを生成 bs = BeautifulSoup(html.content, "html.parser") # <table>要素を取得 table = bs.find('table') # <table>要素内のヘッダ情報を取得する。 if headers is None: headers = [] thead_th = table.find('thead').find_all('th') for th in thead_th: headers.append(th.text) # <tr>要素のデータを取得する。 rows = [] tr_all = table.find_all('tr') for i, tr in enumerate(tr_all): # 最初の行は<thead>要素なので飛ばす if i==0: continue # <tr>要素内の<td>要素を取得する。 row = [] td_all = tr.find_all('td') for td in td_all: row.append(td.text) # 1行のデータをリストに追加 rows.append(row) # DataFrameを生成する df = pd.DataFrame(rows, columns=headers) # DataFrameを結合する if whole_df is None: whole_df = df else: whole_df = pd.concat([whole_df, df]) # 1秒ディレイ time.sleep(1) return whole_df
[補足2] astype_stock_priceのソースコード
def astype_stock_price(df): # 列とデータ型の組み合わせを設定 dtypes = {} for column in df.columns: if column == '日付': dtypes[column] = 'datetime64' else: dtypes[column] ='float64' # データ型を変換 new_df = df.astype(dtypes) # インデックスを日付に変更 new_df = new_df.set_index('日付') return new_df
[補足3] add_moving_average_stock_priceのソースコード
def add_moving_average_stock_price(df): df['5日移動平均'] = df['終値'].rolling(window=5).mean() df['25日移動平均'] = df['終値'].rolling(window=25).mean() df['75日移動平均'] = df['終値'].rolling(window=75).mean() return df
出典
- アイキャッチはGerd AltmannによるPixabayからの画像