Day18 財務分析

先人の方達が書いてくれたものを自分なりにまとめてみました。ありがとうございます。
以下は書かれているものを写経したものです。私が実践して得たものではなく、記事を読んだものをただただまとめただけです。
私はこれをもとに実践するつもりです。それではいきまそいきまそ。

EDINET APIを使用する財務分析は、単純な機能のプログラムの集まり。


証券コードリスト取得 → ②XBRL取得 → ③XBRLの読み込み → ④データ抽出(分量が多い) → ⑤グラフ化 → ⑥分析
XBRLの取得、XBRLの読み込み、勘定科目の抽出、グラフ化、時系列分析。


これらの機能を一つずつ小さく小さく作成していく。
ただXBRLを取得して、実際に銘柄分析に役立てるまでたどり着くのは時間がかかる。


XBRLの取得方法
XBRLとは財務情報や投資情報などの事業報告用の情報作成・流通・利用できるように標準化されたxmlベースの言語。

EDINETコードリスト(CSV形式)を使用してXBRLを取得する。
EDINETコードリストはZIP形式で圧縮されている。解凍すると、中身はCSV形式。文字のエンコーディングはcp932。文字化けする時はcp932を指定する。


① まずこのEDINETコードリスト(CSV)をpandasライブラリで読みこむ。

EDINETコードリストをpandasで読み込む方法
pandas.read_csv()で読み込む。
データは文字列で(str)として読み込むのが良い。
最初はいらない行なので1行分だけスキップ。
スキップしてから最初の行(0行目)がラベル行。

import pandas as pd
def get_edinet_codes_df(file):
    """EDINETコードリストのデータフレーム取得"""
    # データフレーム生成
    df = pd.read_csv(
        file,
        encoding='cp932',
        dtype='str',
        skiprows=1, # 最初の行をスキップ
        header=0,
        )
    return df


ここから上場企業の情報だけを抽出したりする。
以下抽出例。データフレームはリストに変換することもできる。

 # 上場企業だけを抽出
    df = df[df['上場区分']=='上場']

    # 必要な列だけ抽出
    df = df.loc[:, [
        'EDINETコード', '上場区分',
        '提出者名', '提出者名(ヨミ)', '提出者名(英字)',
        '提出者業種', '証券コード', '提出者法人番号',
        ]]

    # データフレームをリストに変換
    datas = df.values.tolist()


作成したデータフレームの保存と読み込みをする。データベース保存にも使える。
インデックス列も保存したいときは「index=None」を削除する。
CSV保存:pandas.DataFrame.to_csv()
CSV読み込み:pandas.read_csv()

    # データフレームをCSVで保存
    df.to_csv(symbols_csv, encoding='utf-8', index=None)

    # CSVから読み込み
    df = pd.read_csv(symbols_csv, encoding='utf-8')

②そしてこのコードリストを使用してXBRLを取得する。

サンプルコード:
SUMMARY_TYPE = 2
def download_all_documents(date, save_path, 
                           doc_type_codes=['120', '130', '140', '150', '160', '170']):
    params = {'date': date, 'type': SUMMARY_TYPE}
    doc_summary = get_submitted_summary(params)
    df_doc_summary = pd.DataFrame(doc_summary['results'])
    df_meta = pd.DataFrame(doc_summary['metadata'])
     
    # 対象とする報告書のみ抽出
    if len(df_doc_summary) >= 1:
        df_doc_summary = df_doc_summary.loc[df_doc_summary['docTypeCode'].isin(doc_type_codes)]
 
        # 一覧を保存
        if not os.path.exists(save_path + date):
            os.makedirs(save_path + date)
        df_doc_summary.to_csv(save_path + date + '/doc_summary.csv')
 
        # 書類をを保存
        for _, doc in df_doc_summary.iterrows():
            download_document(doc['docID'], save_path + date + '/')
            open_zip_file(doc['docID'], save_path + date + '/')
        return df_doc_summary
     
    return df_doc_summary 


XBRLを読み込み方。zipファイルをそのまま読み込む方法、beautiful soupで読み込む方法、lxmlで読み込む方法の三つぐらいあるらしい。

今回はzipファイルをそのまま読み込む方法をとる。
XBRLのファイル群はzipでひとつにまとめて公開されている。
これは解凍せずに読み込むことができる。
Python のzipモジュールにXBRLのzipファイルパスを渡すと読み込む事ができる。
そうするとバイナリデータが返ってくるので、それをそのままBeautifulSoup()やlxmlに渡せば解析してくれる。

zipを展開せずに直接読み込む方法[python, XBRL]
つまり、Python で zipを解凍せずに直接読み込む方法

zipファイルの中身を読み込む時は、いったんHDD か SSD に解凍(展開)するのが一般的であるが、zipから直接読み込むこともできる。

やり方は、「標準のzipfileモジュール」を使用する。
zipの中身を解凍しなくて良いため、大量のXBRLを読み込む時に、容量を省く事ができる。

zipfileモジュールでzipファイルを開いたら、
中身のファイルリストを取得するメソッド「.infolist()」と
データを読み込むメソッド「.read()」を使用する。

使い方:

zipファイルを開く:class zipfile.ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True)

中身のファイルリストを取得:ZipFile.infolist()

名前を指定して読み込む:ZipFile.read(name, pwd=None)


zipを読み込む
zipを解凍せずに中身を見るには、zipfileモジュールのZipFile()を使用する

読み込み手順
拡張子「.xbrl」のファイルだけを読み込んで辞書に入れる例。
zipfile.ZipFile()でzipファイルを開いてから、zip_data.infolist()でファイルリストを取得する。

ファイルリストの要素にはinfo.filenameという属性があるので、これをzip_data.read()に渡すと、データが読み込める。

info.filenameは、実際には以下のような「ファイル構造を含んだファイルパス」

'S100AM8K/XBRL/PublicDoc/jpcrp030000-asr-001_E00518-000_2017-03-31_01_2017-06-26.xbrl'

そのため、ファイル名を使用して読み込む・読み込まないを判定する時は、このファイルパスを意識して
判定部分を作ることになる。

コード例
まずzipfileモジュールを使用する部分

import zipfile
from collections import OrderedDict
import traceback

def read_file_from_zip(zip_file, re_match):
    """zipからファイル名を指定して読み込む"""
    file_datas = OrderedDict()

    try:
        with zipfile.ZipFile(zip_file, 'r') as zip_data:
            # ファイルリスト取得
            infos = zip_data.infolist()

            for info in infos:
                # ファイルパスでスキップ判定
                if re_match(info.filename) is None:
                    continue

                # zipからファイルデータを読み込む
                file_data = zip_data.read(info.filename)

                # ファイルパスをキーにして辞書に入れる
                file_datas[info.filename] = file_data

    except zipfile.BadZipFile:
        print(traceback.format_exc())

    return file_datas

次に、上記の関数を使用する部分。
メイン関数。
zipから読み込んだデータは、そのままlxml.etree.fromstring()に渡す事ができる。
データの中身はバイナリデータ。

import re
from lxml.etree import fromstring as etree_fromstring

def main():
    """メイン関数"""
    # zipファイルの場所
    zip_file = r'*****\S100AM8K.zip'

    # 拡張子でファイルを選択してみる
    re_match = re.compile(r'^.*?\.xbrl$').match

    # zipからファイルデータを読み込む
    files = read_file_from_zip(zip_file, re_match)

    # 読み込んだXBRLファイルを1つずつ解析
    for (file, data) in files.items():
        # ファイル名
        print('file: %s' % file)

        # XML解析 lxml.etree
        root = etree_fromstring(data)
        print('root.tag: %s' % root.tag)

        print('')
    return

そしてメイン関数を呼び出す。
if __name__=='__main__':
    main()

zipから読み込んだXMLデータをBeautifulSoupで読み込む場合。
バイナリデータをそのままわたす。単に文字列に変換したい場合は、適当なエンコーディングを指定して『.decode()』のメソッドを呼び出す。
バイトオーダーマーク (BOM) 付きの UTF-8 をデコードするときは、'utf-8-sig' を指定する。
'utf-8' でデコードしたら、先頭にパイトオーダーマークの文字列が残ってしまう。

from bs4 import BeautifulSoup

# XML解析 BeautifulSoup(HTMLパーサー)
soup = BeautifulSoup(data, features='lxml')

# XML解析 BeautifulSoup(XMLパーサー)
soup = BeautifulSoup(data, features='xml')

# バイナリをデコードして文字列に変換
text = data.decode('utf-8-sig')

参考:https://srbrnote.work/archives/1297





その勘定科目の抽出には2種類ある。

XBRLを一気に読み込んでから抽出するアプローチ。(今回はこっち)
XBRLを解析しながら狙った科目だけ抽出するアプローチ

XBRLを一気に読み込んでCSVに保存しておくと、内容を一望でき、タグを探すのが楽になる。
CSVは企業ごとに全ての決算を連結していったものを作成し、
これを一つ開くだけでその企業の何年分というタグを一覧する事ができる。

そして、「タグの読み込み」と「勘定科目の抽出」を分けたら、プログラムの分割がうまくいく。

PythonXBRLの読み込みに専念して、時系列分析のための表作成はpandasデータフレームの機能に任せる。
できる人であれば、専用のデータベースに取り込んで、時系列をリクエストするのが良い。


参考:https://srbrnote.work/archives/1100
参考:https://srbrnote.work/archives/1136
参考:https://srbrnote.work/archives/1123


あとは抽出してグラフ化するだけ。