LoginSignup
6
3

More than 3 years have passed since last update.

Google Colaboratoryで日本の株式市場の現物株と先物の投資部門別売買状況のJPXの公開資料を参照して整形する処理まわり

Posted at

概要

・某個人投資家の夕凪氏という方のtwitter、および氏の投資ブログより
yng1.png
https://twitter.com/yuunagi_dan/status/1179639303162318848

※参考:夕凪ブログ↓
http://yuunagi.enjyuku-blog.com/archives/category/投資部門別

・日本の証券市場の、最近(先週)の個人投資家や国内機関投資家、外国人投資家などの売買状況がわかる。
・東証HPには、現物株の部門別売買状況の資料(週次で先週分集計のエクセルファイル)が、原則木曜の午後3時に発表される↓
https://www.jpx.co.jp/markets/statistics-equities/investor-type
・いわゆる「日銀砲」と呼ばれているやつが、この売買状況の数字のどこに反映されるのかは、よく分からん(←教えて金融クラスターの詳しい人、kwsk)
・おそらく「日銀砲」は、日本銀行BOJのHPに公開資料が開示されているかと思われ(←今回は言及せず)
・夕凪氏は株式だけでなく、先物(日経225、日経225 mini、TOPIX、TOPIX mini、JPX日経400)の売買状況も含めて集計している模様。
・著名な個人投資家は、この売買状況を参考情報として定点観測している模様で、過去の状況しか分からない(リアルタイムで今週もしくは現在の売買状況が分かるわけではない)ので意味ないじゃんとも若干思うところもあるが、結構重要っぽい。
・現在の日本の株式市場は、外国人投資家(=外資系の機関投資家)の売買動向に結構左右されているというか、メジャーな日本の株式市場のキープレーヤーなので、その外国人投資家のお金が流入してきているのか/また逆に引き上げられてしまっているのかを推察するのに重宝される資料かと思われ...
・そういえば今は亡き匿名twitterアカウント「岡三マン」も、木曜に株式の部門別売買状況の速報ツイートをしてたな...

株式の投資部門別売買状況

・夕凪氏のような個人投資家などが部門別の売買状況を集計してブログやtwitterにアップしてくれてはるので、夕凪氏ブログをrequests/beautifulsoupでWebスクレイピングしたり、twitterAPIで夕凪氏のツイートを取得収集すればいいのでは?とも思ったが、今回は原典を直接参照して東証HPのエクセルファイルを閲覧することにした。

https://www.jpx.co.jp/markets/statistics-equities/investor-type/
test2.png
・公表資料のありかは、JPX日本取引所HP[マーケット情報]-[統計情報(株式)]-[投資部門別売買状況]のページ
・公表資料のフォーマットはエクセルファイル(.xlsx)
・エクセルファイルは、売買株数バージョンと売買金額バージョンの2つがあるが、今回は金額の方を参照。
・公表されるエクセルファイルのURL/ファイル名は無作為に決まるようなので、直接エクセルファイルを閲覧するのではなく、一旦JPXのHPの該当ページ・投資部門別売買状況を参照して、テーブル表の一番上のTRタグ内の一番右のTDタグ内にあるAタグのリンクURLを取得する形で、目的のエクセルファイルを閲覧する。

equities.py
import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np

#pandasの列表示上限を設定
pd.get_option("display.max_columns")
pd.set_option('display.max_columns', 50)

#株式売買動向
def fn_eqty(pth,si):
    #URL指定
    url = 'https://www.jpx.co.jp/' + pth
    print(url , si)

    #データの読み込み(excel)
    df = pd.read_excel(url,  sheet_name=si)

    #期間日付、取引金額単位、市場名の取得
    mkt = df.iloc[4,0]
    mkt = str(mkt).replace('総売買代金', '')
    prd = df.iloc[2,0] 
    ymw = prd[:10]
    unit = df.iloc[3,10] 
    unit = unit.split(',')[0]
    unit = str(unit)
    print(prd ,ymw, unit ,mkt)

    #整形:データの絞り込み
    df = df[11:63] 
    df.columns = ['a', 'b', 'c', 'd', 'e', 'f','g','h','i','j','k']
    df = df.drop(['c', 'd', 'e', 'f','g','h','j' ,'k'], axis=1) #'k'は検算確認用、DBに格納する場合は'k'は削除
    df = df[df['b']!='合計']
    df = df.dropna(subset=['i'])
    df = df.dropna(how='all')

    #値の置換
    dct = {'Proprietary': '自己計', 'Brokerage': '委託計', 'Total':'総 計', 'Institutions': '法 人'
          , 'Individuals': '個 人', 'Foreigners':'海外投資家','Securities Cos.':'証券会社'
          , 'Investment': '投資信託',  'Business Cos.':'事業法人', 'Other Cos.': 'その他法人等'
          , 'Financial': '金融機関', 'Life & Non-Life':'生保・損保', 'City & Regional BK':'都銀・地銀等'
          , 'Trust BK': '信託銀行',  'Other Financials':'その他金融機関'}
    #df.a = df.a.replace(dct)
    df["a"] = df["a"].replace(dct)

    #整形:データの絞り込みや列名設定
    df = df.query("a not in ['委託計', '総 計', '法 人', '金融機関']")
    df.columns = ['clsfctn', 'bysl', 'vle'] #'diff' 
    df = df.reset_index()

    #期間日付、取引金額単位、市場名がセットされたデータフレームを生成
    df2 = pd.DataFrame([[prd,ymw,unit,mkt ]]*len(df))
    df2.columns = [ 'prd', 'ymw' ,'unit', 'mkt' ]

    #dfの結合
    df = pd.concat([df, df2], axis=1)
    print( df )

    ### to_sql()でHerokuのPostgreSQLの任意のテーブルにデータ格納: 割愛! ### 

    #dfの集計: wide_formに変換
    df3 = df.pivot(index='clsfctn', columns='bysl', values='vle')
    df3 = df3.apply(lambda x: x.str.replace(',','')).astype(np.int)  #カンマ表記のString型をint型に変換
    df3['sum'] = df3['買い'] + df3['売り'] #合算
    df3['dif'] = df3['買い'] - df3['売り'] #減算

    #並び替え:部門
    d_order = {'自己計': 0, '個 人': 1, '海外投資家': 2, '証券会社': 3
               , '投資信託': 4, '事業法人': 5, 'その他法人等': 6, '生保・損保': 7
               , '都銀・地銀等': 8, '信託銀行': 9, 'その他金融機関': 10}

    df3['order'] = df3.index.map(d_order)
    df3 = df3.sort_values('order')
    #列名設定
    df3.columns = ['sell', 'buy', 'sum' ,'dif' , 'order' ]
    display(df3)


def fn_main(url):  
    html = urlopen(url)
    bsObj = BeautifulSoup(html, "html.parser")
    table = bsObj.findAll("table")[0]
    rows = table.findAll("tr")[0:2]  #表の1行目を狙い撃ち

    for r in rows:
        tds = r.findAll("td")
        for td in tds[1:]:
            lnk = td.a.get("href")
            #print(lnk)
            if str(lnk).find('equities')>0 and str(lnk).find('xls')>0 and str(lnk).find('val')>0:
               #[si]0:東証1部、1:東証2部、2:マザーズ、3:Jasdaq、4:2市場[東証&名証]
               x=[fn_eqty(lnk ,si) for si in range(0,5)] 

# main
if __name__ == '__main__':

     url = 'https://www.jpx.co.jp/markets/statistics-equities/investor-type/00-00-archives-00.html'
     fn_main(url)

※実行結果1
test1.png
・今回はお手軽にGoogle colaboratoryでpythonのスクリプトを実行した。
・もちろん自分のPC上のanaconda等でも実行OKかと。
・上記スクリプトでは、JPXの統計情報ページにある現物株売買状況のTOPページを最初にrequestsで参照して、そのページの最初のテーブルの1行目(<th>タグはスキップした最初の<tr>タグ)の<td>タグを順番に閲覧して、<a>タグを含む場合、その<a>タグのリンクhrefの箇所を取得してエクセルファイルのURLを特定
・たまたまpyconJP2018のセッションのスクレイピングについての資料をさっきspeakersDeckで見つけて眺めていたら、例えばtry-except()でエラー回避しましせふと言及されており、またretryデコレータ書いておくといいかもなどとも述べられていて結構いいこと書いてあるなと思った...が、上記のスクリプトでは何もやってない。対不起。(今回は割愛)
・Jupyter Notebookでpandasのdfデータオブジェクトを確認する際に、表示される列数や行数の上限がデフォルトで設定されているので、その上限を設定し直して、途中で省略されないように、pd.get_option()を利用している
・はっ、今まで「Jupyter Notebook(ジュピターノートブック)」は、「Jupiter(木星)」だと思ってたけれど、よく見たら「Jupyter」やった(pythonだけに「py」なんすね)...気付かずにポーっと生きてたわー(汗
・公表資料がエクセル(.xlsx)なので、pandasのpd.read_excelを利用
・面倒くさいのがデータフレームの整形作業で、不要なレコード列を除去するのが煩雑だが、参照するエクセルのシートは、投資部門ごとに「売り行、買い行、合計行」という組み合わせで表記されているので、必要なのは「売り行」と「買い行」の2行(合計行は不要、また売りと買いの差額の列も、「売り行」と「買い行」の2行を取得できればあとで引き算すれば差額も計算できるので不要)なので、不要な行と列はひたすらdfからdropしてパージしていくイメージ。

・最初以下のスクリプトのようにdfデータフレームを1行ずつループ処理でまわして、1行ごと取捨選択の処理をしていくように考えたのだが、そういう手続き型の処理ではなく、どちらかというとSQLでSelect文のWhere句に条件を付け足して不要なものを除去して、必要なもののみを凝縮・抽出するような思考展開のイメージ?(よくわからんorz)

botu.py
    '''
    #[botu] dfを1行ずつループしながら整形しようとしたが途中で却下した案
     for index, row in df.iterrows():
        print(index)
        print('~~~~~~')
        if str(row[0]).find('総 計')<0 and str(row[0]).find('法 人')<0 and str(row[0]).find('金融機関')<0:
              print(row[0])
              print(row[1])
              print(row[7])
              print(row[8])
              print(row[10])
              print('======\n')
    '''

・期間日付、取引金額単位、市場名はエクセルシートの先頭の方に記載があるので、別途取得してdfデータフレームオブジェクトを作っておいて、本体の売り買いのdfデータフレームとpd.concat()で結合して右列にくっつけている。
・期間日付、取引金額単位、市場名がセットされたデータフレームを生成するのは、まず期間日付、取引金額単位、市場名はセットされているリスト[list]に対して、concatで結合する本体のデータフレームdfの長さlen()を掛け算してdfの格納されてるデータ量だけ水増ししているからデータフレームを作成している点がポイント。
df2 = pd.DataFrame([[prd,ymw,unit,mkt ]]*len(df))
・dfの長さを揃えてからconcat()で結合。SQLで言うところのcross join でon句がないイメージのJOIN結合なので、タッパを揃えるためにlen(df)を掛け算している書き方がこ賢しい?というかpythonっぽい書き方だなーと思...
・期間日付は別の方法でも取得可能なのでともかく、取引金額単位と市場名は、シートによって可変になっているので、閲覧しているシートに応じて取得するようにしたほうが望ましい。
・今回は割愛しているが、pandasのto_sql()HerokuのPostgresSQL(RDB)のテーブルにデータ保存することも可能。

※実行結果2
test3.png
・エクセルファイルのシートから取得整形したデータフレームを、povot()で上記の形のように、各売買部門ごとに売り列と買い列、売りと買いの合算の列、売りと買いの引き算した列と横に表記した形に変換すると、夕凪氏が集計してtwitter投稿するような値が表示できるかと。
・pivot()でワイドフォーマットなdfデータフレームオブジェクトを生成した後に、数字がカンマ区切りの文字列型なので、合算減算する前に、dfの各列に対してapply関数で、lambda()でカンマを除去してnpのint型表記に変換している。その上で合算(取引金額)と減算([買い超し|売り越し]金額)をしている。
・dfの行の並べ替えは、部門単位の名称で並び替えると文字順になってシートに表記されている順番で無くなるので、一旦リストで部門単位の名称に番号を付与して、そのリストをmap()関数でよってorder用のdfの列を生成し、sort_sort_values()でdfの行の並べ替えを行っている。
・pandasのdfの操作の練習になる! (← 特にmap()やapply()を使って操作する箇所...)
・pandasでのpivot()は、SQL(RDB)ではCASE WHENの分岐処理をGROUP BYのMAX関数で集計するようなSelect文でも記述可能で、まあ川上でやるか川下でやるかの違いなので、お好きな方で処理してOK。
・これが1シートの処理で、エクセルファイルはシートが5つ([1]東証1部、[2]東証2部、[3]マザーズ、[4]Jasdaq、[5]2市場(東証&名証))なので、ループ処理で5シートに対して処理すればよいかと。
・夕凪氏のツイートの値と、上記スクリプトで生成したdfのdif列(売りと買いの差額)が合致しているのでまあ妥当かと(=自己責任で用量用法を正しくお使いください的な、ディスクレーマーっぽい責任回避文章)

先物の投資部門別売買状況

derivatives.py
import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np

#pandasの列表示上限を設定
pd.get_option("display.max_columns")
pd.set_option('display.max_columns', 50)

#先物売買動向
def fn_dvs(pth):
    #URL指定
    url = 'https://www.jpx.co.jp/' + pth
    print(url)

    #データの読み込み(csv)
    df = pd.read_csv(url , header=None)

    #整形:データの絞り込み、列名設定など
    df = df[0:100] 
    df.columns = ['a', 'b', 'c', 'd', 'e', 'f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x']
    #df = df[df['a']==301]
    df = df.query("a in ['301', '313', '314', '316', '323']")
    df = df.query("h not in ['80', '30', '20']")
    df = df[df['d']==1]
    #df = df[df['h']==10]
    df = df.drop(['b', 'i','j' ,'k' ,'n','m','o','p','q','s','t','u','v','w','x'], axis=1)
    df = df.rename(columns={'l': 'sell', 'r': 'buy'})
    #df = df.reset_index()

    #売買金額の合算と減算
    df['sum'] = df['buy'] + df['sell']
    df['dif'] = df['buy'] - df['sell']

    #値の置換
    dct1 = {10: '自己計', 70: '委託計', 50: '個 人', 60: '海外投資家',41: '証券会社'
            ,31: '投資信託',  32: '事業法人', 33: 'その他法人等', 21: '生保・損保'
            ,22: '都銀・地銀等', 23: '信託銀行', 24: 'その他金融機関'}
    dct2 = {301: 'nk225', 313: 'nk225m'
            ,314: 'topix', 316: 'topixm', 323: 'nk400' }
    df["clsfctn"] = df["h"].replace(dct1)
    df["drvts"] = df["a"].replace(dct2)

    #並び替え:銘柄,部門
    d_order = {'自己計': 0, '委託計': 1, '個 人': 2, '海外投資家': 3, '証券会社': 4
               , '投資信託': 5, '事業法人': 6, 'その他法人等': 7, '生保・損保': 8
               , '都銀・地銀等': 9, '信託銀行': 10, 'その他金融機関': 11}
    df['order'] = df["clsfctn"].map(d_order)
    df = df.sort_values(['a','order'])
    print(df)

def fn_main(url):  
    html = urlopen(url)
    bsObj = BeautifulSoup(html, "html.parser")
    table = bsObj.findAll("table")[0]
    rows = table.findAll("tr")[0:2]  #表の1行目を狙い撃ち

    for r in rows:
        tds = r.findAll("td")
        for td in tds[1:]:
            lnk = td.a.get("href")
            if str(lnk).find('derivatives')>0 and str(lnk).find('csv')>0: ret=fn_dvs(lnk )
# main
if __name__ == '__main__':

     url = 'https://www.jpx.co.jp/markets/statistics-derivatives/sector/index.html'
     fn_main(url)

・公表資料のありかは、JPX日本取引所HP[マーケット情報]-[統計情報(先物・オプション関連)]-[投資部門別取引状況]のページ (※週間/最新のデータ)
https://www.jpx.co.jp/markets/statistics-derivatives/sector/index.html
・外国人投資家は、現物株式よりも先物を結構購入しているようなので、株式(現物)の売買状況のみ把握していても片手落ちになる。故に先物オプション市場の部門別売買状況も忘れずに把握するべし。
・夕凪氏は先物の、日経225、日経225 mini、TOPIX、TOPIX mini、JPX日経400の5つの先物商品について集計しているようである。日経平均先物の数字は、日経225と日経225miniを合算し、TOPIX先物は、TOPIXとTOPIX miniを合算している模様。あとダウ平均以下のシートについては、夕凪氏は割愛している(計算していない)模様。
・先物市場の投資部門別売買状況の資料は、現物株の資料とはJPX日本取引所HP内で、URLが異なる。
・推測するに、先物市場はもとも大阪証券取引所が得意としていた領域で、数年前に東京証券取引所と大阪証券取引所が合併してJPX日本取引所が設立された経緯があって、まあJPXの担当する部署が現物株式と先物とは、職場のカルチャー的にも出身母体的にも働いている人間の種族的にもまあ別物なんでしょうね(白目
・で、先物市場の投資部門別売買状況の資料は、現物株の投資部門別売買状況の資料とは、ファイルフォーマットも異なるし、表記方法も異なる。先物の投資部門別売買状況の資料はファイルの拡張子が.csvで、すでに売りと買いが横に表記された形なので、上記スクリプトでは、pandasのread_csv()を使ってdfデータフレームを生成し、現物株のdf整形時に利用したpivot()の処理はやらなくてもすでにその形になっている。そういう意味ではpandasフレンドリー。

※実行結果3
test5.png
・現物株の売買状況の資料と比べると、ある意味デジタル処理する用途を想定したかのようなデジタルフレンドリー?な形のスプレッドシートのまとめ方とも言えるかも?
・とは言いつつも、DBへデータ格納する際は、この形式のdfの持たせ方は、正規化の考えに適合しておらず、一旦melt()などでロングフォーマットな形のdfに変換してから保存したほうがいいかも。そういう意味では、DBのテーブル格納を考えると、現物株の売買状況の資料のほうが1周回ってフレンドリーとも言えるかもしれない(注:いや現物株の売買状況の資料の方は、数字がカンマ表記の文字列だし、単位はシートによってバラバラだし、ぜんぜんデジタルフレンドリーではなく、人間が紙をプリントアウトして閲覧する用途に最適化したかのようなファイルフォーマッティングの仕方なのだが、1点だけ「売り」と「買い」が列表記でなく、行表記になっている点が、RDBの正規化の考え方に有利に働いているような...いや思いっきりDQN官僚っぽい資料なのだが。)

pandasのStyling機能

・pandasで色をつける(任意の行をハイライト、マイナスの値を赤色に、列ごとに最大セルをマーカー)
参考↓
https://qiita.com/kenrota/items/01bc42ac1fab661555a9
https://nerimplo.hatenablog.com/entry/2019/09/11/120000

color_df.py
import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np

#pandasの列表示上限を設定
pd.get_option("display.max_columns")
pd.set_option('display.max_columns', 50)

#df_style変更
def color_negative_red(val):
    #売り越しは赤字表記にする
    color = 'red' if val < 0 else 'black'
    return 'color: %s' % color

def highlight_max(s):
    is_max = s >= s.max()
    styles = is_max.map(lambda x: 'color: blue' if x else '')
    return styles

def highlight_cols(df):
    styles = df.copy()
    styles.loc[:,:] = ''
    #個人投資家と海外投資家の背景色を変更する
    styles.loc['海外投資家', :] = 'background-color: lightgreen'
    styles.loc['個 人', :] = 'background-color: yellow'
    return styles

#株式売買動向
def fn_eqty(pth,si):
    #URL指定
    url = 'https://www.jpx.co.jp/' + pth
    print(url , si)

    #データの読み込み(excel)
    df = pd.read_excel(url,  sheet_name=si)

    #期間日付、取引金額単位、市場名の取得
    mkt = df.iloc[4,0]
    mkt = str(mkt).replace('総売買代金', '')
    prd = df.iloc[2,0] 
    ymw = prd[:10]
    unit = df.iloc[3,10] 
    unit = unit.split(',')[0]
    unit = str(unit)
    print(prd ,ymw, unit ,mkt)

    #整形:データの絞り込み
    df = df[11:63] 
    df.columns = ['a', 'b', 'c', 'd', 'e', 'f','g','h','i','j','k']
    df = df.drop(['c', 'd', 'e', 'f','g','h','j' ,'k'], axis=1) #'k'は検算確認用、DBに格納する場合は'k'は削除
    df = df[df['b']!='合計']
    df = df.dropna(subset=['i'])
    df = df.dropna(how='all')

    #値の置換
    dct = {'Proprietary': '自己計', 'Brokerage': '委託計', 'Total':'総 計', 'Institutions': '法 人'
          , 'Individuals': '個 人', 'Foreigners':'海外投資家','Securities Cos.':'証券会社'
          , 'Investment': '投資信託',  'Business Cos.':'事業法人', 'Other Cos.': 'その他法人等'
          , 'Financial': '金融機関', 'Life & Non-Life':'生保・損保', 'City & Regional BK':'都銀・地銀等'
          , 'Trust BK': '信託銀行',  'Other Financials':'その他金融機関'}
    #df.a = df.a.replace(dct)
    df["a"] = df["a"].replace(dct)

    #整形:データの絞り込みや列名設定
    df = df.query("a not in ['委託計', '総 計', '法 人', '金融機関']")
    df.columns = ['clsfctn', 'bysl', 'vle'] #'diff' 
    df = df.reset_index()

    #期間日付、取引金額単位、市場名がセットされたデータフレームを生成
    df2 = pd.DataFrame([[prd,unit,mkt ]]*len(df))
    df2.columns = [ 'prd', 'unit', 'mkt' ]

    #dfの結合
    df = pd.concat([df, df2], axis=1)
    #print( df )

    ### to_sql()でHerokuのPostgreSQLの任意のテーブルにデータ格納: 割愛! ### 

    #dfの集計: wide_formに変換
    df3 = df.pivot(index='clsfctn', columns='bysl', values='vle')
    df3 = df3.apply(lambda x: x.str.replace(',','')).astype(np.int)  #カンマ表記のString型をint型に変換
    df3['sum'] = df3['買い'] + df3['売り'] #合算
    df3['dif'] = df3['買い'] - df3['売り'] #減算

    #並び替え:部門
    d_order = {'自己計': 0, '個 人': 1, '海外投資家': 2, '証券会社': 3
               , '投資信託': 4, '事業法人': 5, 'その他法人等': 6, '生保・損保': 7
               , '都銀・地銀等': 8, '信託銀行': 9, 'その他金融機関': 10}

    df3['order'] = df3.index.map(d_order)
    df3 = df3.sort_values('order')
    #列名設定
    df3.columns = ['sell', 'buy', 'sum' ,'dif' , 'order' ]
    df3.style.highlight_null().render().split('\n')[:10]
    df3 = df3.style.applymap(color_negative_red).apply(highlight_cols, axis=None )
    df3 = df3.apply(highlight_max, axis='index')   #axis='columns')
    display(df3)


def fn_main(url):  
    html = urlopen(url)
    bsObj = BeautifulSoup(html, "html.parser")
    table = bsObj.findAll("table")[0]
    rows = table.findAll("tr")[0:2]  #表の1行目を狙い撃ち

    for r in rows:
        tds = r.findAll("td")
        for td in tds[1:]:
            lnk = td.a.get("href")
            #print(lnk)
            if str(lnk).find('equities')>0 and str(lnk).find('xls')>0 and str(lnk).find('val')>0:
               #[si]0:東証1部、1:東証2部、2:マザーズ、3:Jasdaq、4:2市場[東証&名証]
               x=[fn_eqty(lnk ,si) for si in range(0,5)] 

# main
if __name__ == '__main__':

     url = 'https://www.jpx.co.jp/markets/statistics-equities/investor-type/00-00-archives-00.html'
     fn_main(url)

※ 実行結果4
test6.png
・pandasにはstyleで色を付与することが可能で、PyConJP 2019にもそんなセッションありましたですね。で、自分もやってみた。
・値がマイナスの場合に赤色表記にするケースを1つ、また任意の行(上記では個人投資家や海外投資家の行)が超重要ということで背景色を変更しているケースを1つ書いてみた。引数にdf指定すると行単位や列単位でstyleで色変換が可能で、引数にvalを設定するとdfの各行列の値が引数に渡される模様。

・各列においての最大値を青文字に変換するケースは、別に最大値を求める必要のない列にまで適応されてしまっているので、改良の余地あり。ただし、個人投資家や海外投資家の値が最大値になるのは、注意喚起として青文字表記になるのは全然OK(むしろ大歓迎)なので、どこかで利用するシチュエーションはあろうかと...
・今回整形したdfデータフレームを元に、plotlyやdashなどでビジュアライゼーションするというのも普通にアリですが、今回は時間切れ(割愛)。

・よく見ると、この週(2019年第4週)は、個人投資家は東証1部2部では買い越し(売り金額<買い金額)だけど、マザーズとJasdaqでは売り越し(売り金額>買い金額)であるのに対して、海外投資家はその逆で東証1部2部では売り越し(売り金額>買い金額)だけど、マザーズとJasdaqでは買い越し(売り金額<買い金額)なんすね。(←注:それが株式投資的にはどういう意味を持っているのかの解釈については、自分ド素人なのでよく分からず...
・余談だが、そういう資料から読み取れる株式投資的な解釈というか、経済的な意味を読み取る系のスキルについては、プログラミングできてなおかつ実際に株式投資も自分でやられてて株式投資関連のドメイン知識を保有、皮膚感覚も備わっている方として、この方だったりこの方だったりが、今回の資料の解釈等は詳しいのでしょうなー。(教えを請いたいところ!)

まとめ

項目 株式(現物株) 先物
URL 株式-統計情報 先物-統計情報
証券取引所名 東証/名証 大証
ファイルフォーマット xlsx csv
使用するpandasの関数 read_excel() read_csv()
dfのスタイル ロングフォーマット(っぽい形式) ワイドフォーマット(っぽい形式)
人間フレンドリーな表形式への変換 pivot()での変換が必要 そのままでOK
DBフレンドリーな表形式への変換 そのままでOK melt()での変換が必要
数値のスタイル カンマ表記の文字列型 数字型(カンマ表記なし)
'ymw'の有無(yyyymmw) なし(「yyyy年mm月w週」から生成する必要あり) あり
投資部門の表記 文字列表記 コード表記
matome.py
import requests
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np

#pandasの列表示上限を設定
pd.get_option("display.max_columns")
pd.set_option('display.max_columns', 50)

#df_style変更
def color_negative_red(val):
    #売り越しは赤字表記にする
    color = 'red' if val < 0 else 'black'
    return 'color: %s' % color

def highlight_max(s):
    is_max = s >= s.max()
    styles = is_max.map(lambda x: 'color: blue' if x else '')
    return styles

def highlight_cols(df):
    styles = df.copy()
    styles.loc[:,:] = ''
    #個人投資家と海外投資家の背景色を変更する
    styles.loc['海外投資家', :] = 'background-color: lightgreen'
    styles.loc['個 人', :] = 'background-color: yellow'
    return styles

#先物売買動向
def fn_dvs(pth):
    #URL指定
    url = 'https://www.jpx.co.jp/' + pth
    print(url)

    #データの読み込み(csv)
    df = pd.read_csv(url , header=None)

    #整形:データの絞り込み、列名設定など
    df = df[0:100] 
    df.columns = ['a', 'b', 'c', 'd', 'e', 'f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x']
    #df = df[df['a']==301]
    df = df.query("a in ['301', '313', '314', '316', '323']")
    df = df.query("h not in ['80', '30', '20']")
    df = df[df['d']==1]
    #df = df[df['h']==10]
    df = df.drop(['b', 'i','j' ,'k' ,'n','m','o','p','q','s','t','u','v','w','x'], axis=1)
    df = df.rename(columns={'l': 'sell', 'r': 'buy'})
    #df = df.reset_index()

    #売買金額の合算と減算
    df['sum'] = df['buy'] + df['sell']
    df['dif'] = df['buy'] - df['sell']

    #値の置換
    dct1 = {10: '自己計', 70: '委託計', 50: '個 人', 60: '海外投資家',41: '証券会社'
            ,31: '投資信託',  32: '事業法人', 33: 'その他法人等', 21: '生保・損保'
            ,22: '都銀・地銀等', 23: '信託銀行', 24: 'その他金融機関'}
    dct2 = {301: 'nk225', 313: 'nk225m'
            ,314: 'topix', 316: 'topixm', 323: 'nk400' }
    df["clsfctn"] = df["h"].replace(dct1)
    df["drvts"] = df["a"].replace(dct2)

    #並び替え:銘柄,部門
    d_order = {'自己計': 0, '委託計': 1, '個 人': 2, '海外投資家': 3, '証券会社': 4
               , '投資信託': 5, '事業法人': 6, 'その他法人等': 7, '生保・損保': 8
               , '都銀・地銀等': 9, '信託銀行': 10, 'その他金融機関': 11}
    df['order'] = df["clsfctn"].map(d_order)
    df = df.sort_values(['a','order'])
    print(df)

#株式売買動向
def fn_eqty(pth,si):
    #URL指定
    url = 'https://www.jpx.co.jp/' + pth
    print(url , si)

    #データの読み込み(excel)
    df = pd.read_excel(url,  sheet_name=si)

    #期間日付、取引金額単位、市場名の取得
    mkt = df.iloc[4,0]
    mkt = str(mkt).replace('総売買代金', '')
    prd = df.iloc[2,0] 
    ymw = prd[:10]
    unit = df.iloc[3,10] 
    unit = unit.split(',')[0]
    unit = str(unit)
    print(prd ,ymw, unit ,mkt)

    #整形:データの絞り込み
    df = df[11:63] 
    df.columns = ['a', 'b', 'c', 'd', 'e', 'f','g','h','i','j','k']
    df = df.drop(['c', 'd', 'e', 'f','g','h','j' ,'k'], axis=1) #'k'は検算確認用、DBに格納する場合は'k'は削除
    df = df[df['b']!='合計']
    df = df.dropna(subset=['i'])
    df = df.dropna(how='all')

    #値の置換
    dct = {'Proprietary': '自己計', 'Brokerage': '委託計', 'Total':'総 計', 'Institutions': '法 人'
          , 'Individuals': '個 人', 'Foreigners':'海外投資家','Securities Cos.':'証券会社'
          , 'Investment': '投資信託',  'Business Cos.':'事業法人', 'Other Cos.': 'その他法人等'
          , 'Financial': '金融機関', 'Life & Non-Life':'生保・損保', 'City & Regional BK':'都銀・地銀等'
          , 'Trust BK': '信託銀行',  'Other Financials':'その他金融機関'}
    #df.a = df.a.replace(dct)
    df["a"] = df["a"].replace(dct)

    #整形:データの絞り込みや列名設定
    df = df.query("a not in ['委託計', '総 計', '法 人', '金融機関']")
    df.columns = ['clsfctn', 'bysl', 'vle'] #'diff' 
    df = df.reset_index()

    #期間日付、取引金額単位、市場名がセットされたデータフレームを生成
    df2 = pd.DataFrame([[prd,ymw,unit,mkt ]]*len(df))
    df2.columns = [ 'prd', 'ymw' ,'unit', 'mkt' ]

    #dfの結合
    df = pd.concat([df, df2], axis=1)
    print( df )

    ### to_sql()でHerokuのPostgreSQLの任意のテーブルにデータ格納: 割愛! ### 

    #dfの集計: wide_formに変換
    df3 = df.pivot(index='clsfctn', columns='bysl', values='vle')
    df3 = df3.apply(lambda x: x.str.replace(',','')).astype(np.int)  #カンマ表記のString型をint型に変換
    df3['sum'] = df3['買い'] + df3['売り'] #合算
    df3['dif'] = df3['買い'] - df3['売り']   #減算

    #並び替え:部門
    d_order = {'自己計': 0, '個 人': 1, '海外投資家': 2, '証券会社': 3
               , '投資信託': 4, '事業法人': 5, 'その他法人等': 6, '生保・損保': 7
               , '都銀・地銀等': 8, '信託銀行': 9, 'その他金融機関': 10}

    df3['order'] = df3.index.map(d_order)
    df3 = df3.sort_values('order')
    #列名設定
    df3.columns = ['sell', 'buy', 'sum' ,'dif' , 'order' ]

    #df3.style.highlight_null().render().split('\n')[:10]
    #df3 = df3.style.applymap(color_negative_red).apply(highlight_cols, axis=None )
    #df3 = df3.apply(highlight_max, axis='index')   #axis='columns')
    display(df3)

def fn_hoge(url):  
    html = urlopen(url)
    bsObj = BeautifulSoup(html, "html.parser")
    table = bsObj.findAll("table")[0]
    rows = table.findAll("tr")[0:2]  #表の1行目を狙い撃ち

    for r in rows:
        tds = r.findAll("td")
        for td in tds[1:]:
            lnk = td.a.get("href")
            #print(lnk)
            if str(lnk).find('equities')>0 and str(lnk).find('xls')>0 and str(lnk).find('val')>0:
               #[si]0:東証1部、1:東証2部、2:マザーズ、3:Jasdaq、4:2市場[東証&名証]
               x=[fn_eqty(lnk ,si) for si in range(0,5)] 
            if str(lnk).find('derivatives')>0 and str(lnk).find('csv')>0: ret=fn_dvs(lnk )

# main
if __name__ == '__main__':

     urls = ['https://www.jpx.co.jp/markets/statistics-equities/investor-type/00-00-archives-00.html'
           ,'https://www.jpx.co.jp/markets/statistics-derivatives/sector/index.html']
     #x = [fn_hoge(url) for url in urls]

     fn_hoge(urls[0])
     fn_hoge(urls[1])

・株式(現物株)と先物の公開資料を各参照して、dfを作って整形して最終的にはto_sql()HerokuのPostgresSQL(RDB)のテーブルにデータ保存する展開が考えられる。
・pandasのdfの整形/操作の練習になるね! (←2回目)
・現物株のエクセル資料の集計期間が「YYYY年M月W週」となっているが、先物市場の集計データは、YYYYMMWの列があるので、一緒に組み合わせて利用することを考えると、「YYYY年M月W週」という文字列を「yyyymmw」の値に変換して、pandasのdfの列で持たせたほうが便利かも(=先物の集計データもyyyymmwの値をもたせておいて、あとでConcat、JOINする際にyyyymmwの値をON句で利用するみたいな?)

appendix.py
import re

prd="2019年9月第4週"
#prd="2019年10月第1週"
yyyy=prd[:4]
mm=prd[5:7]
ww=prd[-3:]
ww=re.sub('第|週',"",ww)
if mm.find("月")>0:mm='0'+re.sub('月',"",mm)
print(yyyy+mm+ww)

※実行結果5
test8.png

6
3
0

Register as a new user and use Qiita more conveniently

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