どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

【Python】株価のテクニカル分析指標から売買タイミングを自動計算

以前に「pandas-datareader」と「mplfinance」を用いて、テクニカル分析チャートを作成しました。その際に「MACD」や「RSI」などのテクニカル分析指標を算出しました。

predora005.hatenablog.com

今回は、これらの指標から売買のタイミングを計算します。

1銘柄ずつチャートを目視していくのは大変です。注目した方がよい銘柄を自動で割り出して、その銘柄のチャートだけを見るようにして手間を省きたいのです。

[1] 株価の取得、テクニカル分析指標の算出

以前の記事で紹介した内容で行います。

ここまで済めば、テクニカル分析チャートまでは作れます。

[2] 今回使用するデータ

ソフトバンク(9984.JP)の2021/04/01〜07/13の株価を使用します。

f:id:predora005:20210714211408p:plain

[3] ゴールデンクロスデッドクロス(MACD)

MACDとシグナルが重なるときが、売買のタイミングと言われています。

詳しい説明は、下記サイト等を参考にしてみてください。

[3-1] ソースコード

ヒストグラム(MACD - シグナル)を用いて計算します。

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] チャート

出力結果をチャート上に示すと、赤枠青枠の部分になります。赤枠がゴールデンクロス青枠がデッドクロスです。

f:id:predora005:20210714211524p:plain

[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] チャート

出力結果をチャート上に示すと、赤枠と青枠の部分になります。

f:id:predora005:20210714220851p:plain

[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

売買のタイミングは判断できませんが、傾向を見るのには役立つかもしれません。ボトムアウト・ピークアウトは傾向を見るにとどめるのが良さそうです。

f:id:predora005:20210714223303p:plain

ソースコードは次の通りです。

# ヒストグラムと日付を取り出す
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%のラインです。

f:id:predora005:20210714225116p:plain

[5-3] 計算通りに売買したら成功したか

  • 2021/05/13〜05/31 RSI<30%で買い:失敗
  • 2021/06/17〜06/23 RSI<30%で買い:失敗

売られすぎ(RSI<30%)の期間が二度ありました。このタイミングで買っていたら、いずれも失敗でした。

RSIは横ばいの相場で有効と言われています。この例では下降トレンドであったため効力を発揮しなかったものと思われます。

[5-4] 別銘柄の場合はどうか

JR東日本(9020.JP)なら、どういう結果になったのか見てみます。

f:id:predora005:20210716114200p:plain

  • 2021/04/21〜04/22 RSI<30%で買い:成功
  • 2021/06/02〜06/14 RSI>70%で売り:成功
  • 2021/06/29〜06/30 RSI<30%で買い:成功

逆に上手くいき過ぎていて気持ちが悪いです。

MACDを用いた指標で売買しても成功していました。

終わりに

テクニカル分析指標だけで売買を判断するは難しいようです。ですが、判断材料としては十分に使えそうです。

売買のタイミングを感情で左右してまうと失敗します。とくに慣れない頃はそうでした。客観的な指標も用いて、冷静に判断できればと思いました。

出典

アイキャッチjanjf93によるPixabayからの画像

Python 100行以内で書ける株価のテクニカル分析チャート(pandas-datareader & mplfinance)

Pythonで株価を取得してローソク足チャートを作ったところ、驚くほど簡単に書けました。「pandas-datareader」と「mplfinance」を使用しましたが、ソースコードの行数は60行でした。以下のようなグラフが簡単に作れます。

f:id:predora005:20210404194556p:plain

[1] 株価の取得

以前の記事で紹介した、pandas-datareaderを用いて株価を取得します。

predora005.hatenablog.com

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」が優秀すぎて数行で書けてしまいます。

f:id:predora005:20210404193827p:plain

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を追加します。

f:id:predora005:20210404194045p:plain

# 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のチャートを追加します。

f:id:predora005:20210404194107p:plain

# 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」に用意された引数を変えると、チャートの見栄えを変えることも簡単にできます。

f:id:predora005:20210404194545p:plain

# 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

出典

pandas-datareaderでTOPIX500銘柄の株価上昇率を算出

f:id:predora005:20210201003956j:plain
<pandas-datareaderでTOPIX500銘柄の株価上昇率を算出>*1

前回はpandas-datareaderで米国株の株価を取得しました。

predora005.hatenablog.com

今回は、東証一部上場銘柄の株価を取得し、業種ごとの株価変動を可視化しました。

f:id:predora005:20210211200716p:plain

[1] 東証上場銘柄を取得する

[1-1] JPX(日本取引所グループ)から取得

東証上場銘柄の一覧は、JPX(日本取引所グループ)のページから取得します。

その他統計資料 | 日本取引所グループ

中身は次のようなデータになっています。

f:id:predora005:20210211133201p:plain

[1-2] CSVに変換して読み込み

ExcelCSV形式に保存したのち、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については、下記ページに詳しく載っています。

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月は、バイデン大統領の当選や金融緩和の継続もあり、全体的に株価が上昇していました。

f:id:predora005:20210211200716p:plain

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] 棒グラフで株価上昇率を可視化

次は上昇率を棒グラフで可視化します。

f:id:predora005:20210211200803p:plain

棒グラフを表示する前に、一度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の方が簡単に株価を取得できました。

predora005.hatenablog.com

ただ、取得できるデータ数が限られるという利用制限があります。一度取得したデータを保存しておく等の工夫は必要だとは感じました。過去データはpandas-datareaderで取得・保存しておいて、最新の株価はスクレイピングで取得するのがよいのかもしれません。

*1:StockSnapによるPixabayからの画像

*2:2021/2/8時点

pandas-datareaderのインストール手順と米国株の株価取得方法

f:id:predora005:20210201003956j:plain
<pandas-datareaderで米国株の株価取得>*1

「pandas-datareader」で株価を取得します。以前、日本株の株価をスクレイピングで取得しましたが、「pandas-datareader」の方がより簡単に取得できました。

バンガード S&P500 ETF (VOO)の株価を取得し、グラフ表示するまでの手順を紹介します。

f:id:predora005:20210201003119p:plain

[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()

f:id:predora005:20210201003119p:plain

終わりに

驚くべきほど簡単に株価が取得できました。取得した時点でDataFrameになっているのが最大のメリットですね。

しかも、株価だけでなく国債利回りやダウ平均、為替まで取得できるので、本当に無料で良いのかなと思ってしまうほどです。

*1:StockSnapによるPixabayからの画像

二輪車業界の株価予想(2020年12月)

f:id:predora005:20201228041731j:plain
<二輪車業界の株価予想(2020年12月)> *1

二輪車業界の株価予想をしました*2

ここでいう二輪車に自転車は含みません。バイクや原付のことと思ってください。

株式投資はくれぐれも自己責任でお願いします。

[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)が販売台数の半分以上を占めていましたが激減しました。中型・大型二輪は原付ほどは減っていません。

2019年度二輪車市場動向調査について | JAMA

理由は原付に対する規制が厳しくなったことと、自転車に取って変わられていることです。電動アシスト付き自転車の売上は年々増えています。また、最近はロードバイクなどのスポーツ自転車も流行っていますね。

[3] 株価の推移

[3-1] 2020年の株価推移

コロナ禍で2月から3月にかけて大きく下落したものの、11月の大統領選挙後に大きく回復しています。通年で見ると年始と年末でほとんど変わっていません。 後述の通り、各社とも減収減益とは言え利益が出ることを受けてのことでしょう(川崎重工以外)。販売台数は回復の兆しを見せています。

f:id:predora005:20201226232406p:plain

[3-2] 2016年以降の株価推移

スズキ以外は減少傾向にあります。各社とも二輪車以外の事業も行っていますので、減少の要因は各社それぞれです。要因についてはこの後見ていきます。

f:id:predora005:20201226233827p:plain

[4] 各社の販売台数推移と内訳

[4-1] ホンダ

二輪事業は9割がアジアを占めています。もう1つの主力事業である、四輪事業においてもアジアが4割を占めており、アジアでの販売台数が重要であると分かります。

f:id:predora005:20201227090549p:plain

販売台数の推移を見ると、コロナ禍で2019年度と2020年度は減少しています。2016年から2018年度で見ると、二輪車の増加率が大きいことが分かります。

f:id:predora005:20201227092642p:plain

ライフクリエーション事業の販売台数は、パワープロダクツ(発電機、耕うん機、芝刈機、除雪機)などで構成されています。

[4-2] ヤマハ発動機

ヤマハ発動機アジアでの二輪車販売台数が8割以上を占めています。

ヤマハ発動機は、マリン事業が二輪車事業に継ぐ主力事業です。マリン事業は、ボートやウォータービークルの販売などを行っています。販売台数ではなく売上高の内訳ですが、こちらは北米が6割を占めています。

f:id:predora005:20201227135016p:plain

販売台数の推移を見ると、近年はやや減少傾向です。ヤマハ発動機の決算は12月なので、新型コロナウィルスの影響は含まれていません。

f:id:predora005:20201227135657p:plain

一方、マリン事業の方は二輪車事業と比較すると堅調です。ただ、2018年から2019年は横ばいなので、成長しているとは言えません。

f:id:predora005:20201227140422p:plain

[4-3] スズキ

スズキも二輪車の販売台数の8割はアジアが占めています。特に、インドでの売上が4割を占めている点が大きな特徴です。

f:id:predora005:20201227164848p:plain

スズキは、四輪事業が主力であり、二輪事業が売上や利益に占める割合は大きくありません(後述)。そのため、スズキの場合は四輪事業の成否が株価に大きく影響します。

販売台数の推移を見ると、コロナ禍の影響を受けています。コロナ以前(2015年度〜2018年度)は、四輪事業・二輪事業ともに販売台数を伸ばしています。

f:id:predora005:20201227165653p:plain

四輪事業はインドでの販売台数が大きく伸びており、日本での販売台数も順調に増えています。

f:id:predora005:20201227170052p:plain

[4-4] 川崎重工

川崎重工は販売台数の地域別内訳は無く、先進国・新興国で別れていました。内訳を見ると他の3社同様に、新興国の販売台数が大きな割合を占めています。

f:id:predora005:20201227193638p:plain

コロナ以前は販売台数が増加してましたが、2019年度はコロナ禍の影響で減少しています。

[5] 各社のセグメント別売上と利益

[5-1] ホンダ

ホンダの売上高の6割は四輪事業が占めています。一方、営業利益は内訳が年度ごとに異なっており、二輪車事業と金融サービス業が安定しています。

f:id:predora005:20201227104404p:plain

世界第1位の販売台数を誇る二輪車事業は、四輪事業と並ぶ利益を上げています。しかし、成長が鈍化していることや、為替影響などもあり年々利益率が低下していることが懸念点です。

[5-2] ヤマハ発動機

売上高の半分はランドモビリティ事業*4が占めており、ついでマリン事業となっています。一方、営業利益はマリン事業が半分を占めており、マリン事業の利益率が高いと分かります。

f:id:predora005:20201227171320p:plain

ヤマハ発動機の決算は12月のため、このデータはコロナ禍の影響を織り込んでいません。最新の決算資料を見ると、当然コロナ禍の影響はあり、2020年12月期は減収減益を見込んでいます。

2019年度実績を見ると、ランドモビリティ事業とマリン事業は減益ですが、これは為替の影響もあります。ロボティクス事業は市況悪化による減益ですが、2019年度実績は概ね前年度と横ばいと言えます。

[5-3] スズキ

スズキは四輪事業が売上高・営業利益ともに9割を占めています。コロナ禍の影響で2019年度は減収減益となっていますが、2018年までは販売台数が伸び続けており順調と言えます。

二輪事業はスズキの場合には売上高に占める割合は低く、利益もゼロに近いです。

f:id:predora005:20201227171916p:plain

スズキも当然新型コロナウィルスの影響を受けています。四輪車の販売台数は全ての地域で減少を見込んでいます。主戦場であるインド・アジアも大きな減少を見込んでいるため、今年度も減収減益も見込んでいます。

[5-4] 川崎重工

川崎重工の事業分野は他3社に比べて多岐に渡っています。その中でも航空宇宙システムが売上・営業利益のおよそ30%を占めていました。

しかし、コロナ禍の影響で航空業界が大打撃を受けていることから、2020年度見通しでは赤字予想となっています。

f:id:predora005:20201228012801p:plain

二輪車事業は、モーターサイクル&エンジンに含まれます。売上高・営業利益ともに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億円以上)となり、今年度は赤字となりそうです。

f:id:predora005:20201228015920p:plain

また、気になる点は借入が増えていることです。自己資本比率は25%前後でしたが、20%近くまで低下する見通しです。

コロナ禍の影響はしばらく続きそうです。そのため、航空業界の回復にはしばらく時間がかかりそうです。航空宇宙システムの回復も遅れると想定されるため、会社全体の利益回復には時間がかかりそうです。

そのため、株価もしばらくは低調な状態が続くと予想されます。

終わりに

今回は二輪車業界の決算資料と株価を元に、今後の株価を予想してみました。

自分自身、二輪車業界に詳しいわけでもなく、二輪車に乗るわけでもありませんでした。なので、決算資料を見て業界のことが少しわかっただけでも収穫でした。

この予想が当たるのか外れるのか楽しみに待ちたいと思います。願わくば、コロナ禍が早く収束し、予想を裏切って各社の業績が回復して欲しいです。

参考資料

Honda | 投資家情報 | IR資料室 | 決算関連資料

決算・発表資料 - 株主・投資家情報 | ヤマハ発動機

投資家向け説明会|スズキ

決算説明資料 | IRライブラリ | 川崎重工業株式会社

*1:Free-PhotosによるPixabayからの画像

*2:2020年12月末の情報で予想しています

*3:日経業界地図 2020年版より

*4:二輪車、オフロードビークル電動アシスト自転車など

鉄道業界の株価予想(2020年12月)

f:id:predora005:20201219140822j:plain
<鉄道業界の株価予想(2020年12月)> *1

前回までに行ったスクレイピングで得た情報も取り入れつつ、株価予想をしていきます。

コロナウィルスの影響を大きく受けている鉄道業界の株価予想をします*2

株式投資はくれぐれも自己責任でお願いします。

[1] 2020年の株価推移

コロナウィルスの影響で株価は大きく下落しています。

特に新幹線事業を行っているJR九州JR東日本JR東海JR西日本の下落幅が大きいです。 それ以外は各社に依りけりです。東急は年初と比較して40%近く下落していますが、他社は最大30%弱の下落幅に留まっています。

f:id:predora005:20201219120547p:plain

[2] JR東日本の状況

JR東日本の決算情報から、新幹線事業を持つ企業の状況を見ていきます。

決算説明会:JR東日本

[2-1] セグメント別の収益

鉄道会社は運輸収入*3だけで稼いでるわけではありません。不動産事業やサービス事業でも稼いでいます。例えば、JR東日本は駅ビル・オフィスビル・ホテルなどの不動産を所持しています。また、アトレやエキュートなどの小売業も積極的に行っています。

コロナウィルスの影響が無い、2019年3月期の決算情報から収益の割合を見てみます。運輸事業が営業収益(売上)・営業利益ともに 70%近くを占めています。

f:id:predora005:20201219120830p:plain

運輸事業の中での内訳を見ると、在来線が6割、新幹線が3割程度です。つまり、JR東日本の収益の40%は在来線、20%は新幹線ということです。

f:id:predora005:20201219121223p:plain

[2-2] 2020年第2四半期の実績

2020年4月〜9月の実績は、営業収益が50%減です。特に新幹線の輸送量は前年度比70%減であり、コロナウィルスの影響が顕著です。不動産事業は前年同期比と比較して30%減、サービス事業は50%減となっています。

2020年通期の純利益は、4,000億円マイナスの予想です。コロナウィルスの影響が軽減されれば業績は戻っていくと思いますが、しばらくは厳しい状況が続きそうです。2020年3月期の純利益が約2,000億円ですから、かなり大きいマイナスです。

f:id:predora005:20201219141706p:plain

[3] 近鉄GHDの状況

続いて、JR系と比較して株価の落込みが小さい、近鉄GHDの決算状況を見ていきます。

近鉄グループホールディングス株式会社|株主・投資家情報|IR資料|決算説明会

[3-1] セグメント別の収益

JR東日本と同様に、コロナウィルスの影響が無い、2019年3月期の決算情報から収益の割合を見てみます。運輸事業が占める比率は、営業収益が17%, 営業利益が49%です。JR東日本の68%, 70%と比較すると、運輸事業への依存度は低いことが分かります。

f:id:predora005:20201219130044p:plain

[3-2] 2020年第2四半期の実績

2020年4月〜9月の実績は、営業収益が54%減です。2020年通期の純利益は、310億円マイナスの予想です。50%減収という点は、JR東日本と変わりません。

f:id:predora005:20201219141841p:plain

セグメント別の営業収益を見ると、運輸が40%減、不動産が20%、流通が25%減、ホテル・レジャーが90%減です。

運輸の影響はJR東日本と比べて小さいのですが、ホテル・レジャーの減益がかなり大きいと予想されています。ホテル・レジャーの営業利益は360億円のマイナス予想になっています。

[4] 2016年からの株価推移

2016年1月4日の株価を基準とした、株価の上昇率はグラフの通りです。

全体的にみると、2020年初頭までは20%程度上昇し、2020年に入ってから大きく下落しています。

2016年1月から2020年1月までの間に、TOPIXは20%近く上昇しました。 TOPIXと同じ上昇率なので、鉄道業界が目立って浮き沈みはしていないと言えます。

f:id:predora005:20201219133558p:plain

[5] 鉄道輸送量の推移

政府の統計ページ e-Statより、鉄道輸送量の年度別推移を見てみます。緩やかに上昇していましたが、2019年度に減少に転じています。日本の人口は減少し続けているので、今後大きく増えていくことは期待できません。緩やかに減少していくでしょう。

f:id:predora005:20201220233506p:plain

鉄道業界の主な収益は運輸収入です。輸送量が減れば運輸収入も減りますので、鉄道業界が大きく成長することは見込めないわけです。成長するとしたら別セグメントが成長するか海外展開でしょう。

[6] 今後の株価予想

しばらくは低調な状態が続きそうです。本格的に回復するのは、コロナウィルスのワクチン接種が始まってからでしょう。

JR系と民鉄で比較すると、民鉄の方が低迷が続くと思います。ホテル・レジャー事業のマイナスが大きいからです。数年間は国内旅行の減少、インバウンドの減少は戻らないと予想しています。

一方、JR系はコロナウィルスの影響が落ち着けば、運輸収入はだいぶ戻るでしょう。株価も元に戻っていくと予想できます。ただし、今後大きな成長が見込める業界ではありません。株価が上昇するとしても、日経平均TOPIXと近い上昇率で推移すると思われます。

*1:Martin WinklerによるPixabayからの画像

*2:2020年12月末の情報で予想しています

*3:電車に乗る際に我々が支払う運賃が主

日本株の株価をpythonでスクレイピングを使って取得しmatplotlibで可視化する(複数銘柄)

前回の続きです。前回は単一銘柄の株価をスクレイピングで取得しmatplotlibで可視化しました。今回は複数銘柄の株価を取得し比較していきます。

predora005.hatenablog.com

f:id:predora005:20201206204240p:plain

[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] 株価の可視化

まずは、終値をそのまま折れ線グラフで可視化してみます。抽出したデータを折れ線グラフで可視化します。

f:id:predora005:20201206204351p:plain

# 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] 値上がり率の可視化

株価は発行済株数によって変わるので単純比較はできません。そこで、ある日付を基準とした値上がり率で比較してみます。

f:id:predora005:20201206204550p:plain

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年の下落幅も大きかったことがわかります。

f:id:predora005:20201206205046p:plain

[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銘柄のみ扱いましたが、銘柄数を増やすことは用意です。いちど仕組みを作ってしまえば応用することは難しくありません。

f:id:predora005:20201206211135p:plain

そもそもスクレイピングを始めた目的は、株式投資のために企業分析を行うことでした。より詳しい分析を行うためには各企業の決算情報なども見ないと足りないなと感じました。

ただ、今回行ったことは分析の取っ掛かりとしては悪くありませんでした。その業界全体の傾向を見る場合や、その業界のなかで他と値動きが異なる企業を探す場合に活用できそうです。

[補足]

[補足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

出典