horioの雑記帳

データ分析屋さんが、気の向いた事を色々メモってる雑記帳。

RでXBRLデータを取得してみた

※この記事は R Advent Calendar 2014 - Qiita の 15日目の記事です。

 諸事情により発表出来なかったネタを書きたいと思い、書いてみました。ポストが長いので、前編/後編の二本立てにしました。後編は、R Advent Calendar 2014 - Qiita の 19日目の記事になります。
 また、(R Adventなのに)記述が金融や会計の知識を前提としているところがありますが、ご容赦ください。後で加筆するかもしれません。

前・後編ポストのサマリー

  • 以前著者より、以下の献本を頂きました。

  • これを読んで、上場企業の有価証券報告書について、こんな感じの財務データの一覧表を作りたい、と思い立って実験した、だけの話です。
  • (下記のように)他の例もあるのは知ってはいますが、自分で仕組みを持っていた方が、何かと面白いかも?と変な気が出たまでです。
証券コード 企業名 FY 現預金 売掛金 売上
1301 極洋 2014 3,456 24,393 202,387
1332 日本水産 2014  6,849 73,250 604,249
1333 マルハニチロ 2014 13,952 98,198 851,708

(前編)RでXBRLデータを取得してみた → 本ポスト
(後編)取得データからデータマートを作ってみた → 後日公開

用語の解説

 さすがに金融門外漢の方にゼロ解説はヒドイので、EDINET, XBRLについて軽く解説をします。

EDINET

XBRL

  • XMLの一形式
  • XMLのタグを、財務諸表が表現できるように規格化したもの
  • 日本では、公認会計士協会等が中心になって設立した「一般社団法人 XBRL Japan」が規格を制定

なぜこのような事を思い立ったのか?

 気が向いたら、別立てで書くかも知れません。なんとなく面白そうだから、というのもあったりはします。*1

他のサービス

 私の知るところですと、以下のものがあります。なお、検索範囲の網羅性については確保されていません。あらかじめご了承ください。

XBRLと決算発表スケジュール − 決算プロ 【 全上場企業の決算とXBRL

 全上場企業について、過去3年分の四半期単位での財務数値があります。更新は、XBRLの更新があった毎営業日の模様です。掲載数値は大体このような項目になります。

  • BS:純資産又は株主資本、総資産、BPS
  • PL:売上高、営業利益、経常利益、純利益、EPS(希薄化なし/あり)
  • CF:営業、財務、投資の各CF
  • その他:会計基準(日、米、IFRS)、情報公開又は更新日、等々

2014年7月 - ファンダメンタルズ重視の投資メモ 〜株式・不動産投資

 勘定科目は、先に紹介した決算プロよりは細かいです。EXCEL版には、銘柄のスクリーニング機能がついております。

前・後編全体を通した手順

図示するとこんなところです。
f:id:horihorio:20141215223656p:plain

プログラム本体

 以前著者より献本頂いた本のソースが下敷きになっております。下記リンクより、書籍に掲載されいるサンプルコードのDLも可能です。
金融データ解析の基礎 / 金 明哲 編 高柳 慎一 井口 亮 水木 栄 著 | 共立出版
 XML, XBRLとは何ぞや、XBRLの仕様について、ソースの説明、等々は上記本が詳しいので、是非ともお買い求めの上ご参照ください。

注意

  • ソースでは、有限会社プレシス様「EDINET-適時開示情報(TDnet)参照API」を利用しております。ご利用は、節度を守った上でお願いします。
    • と言いながら約1週間、そこそこヒドイことしました。すみません。
  • ソースは、新規上場銘柄や一部変則的な名称の場合に上手く行かないことがあり得ます。(組織再編有価証券報告書、等)
  • 下記の使い方そのままでは、設定取得期間後に上場廃止となった銘柄については取得できません。上場廃止銘柄は、東証HPより取得可能なので、(私は未テストですが)廃止銘柄も対応可能なはずです。但し、過去分一覧はPDFなのですが…。→HTMLになりました。
    • 実は、下記のソースはXBRLを取得後に改変してます。テストをすれば良いのですが、適切なテストケースを選ぶ時間がなかったとか。

1. 書籍のサンプルコードからの改変点(含むToDo)

  • APIが、証券コードを入れるとEDINETコードを含む要素を返すので、ちゃっかり記録してます。なので、例えばEDINETの大量保有報告書とぶつける際に使えるとか。
  • 金融庁への提出日で、対象範囲を絞り込み出来るようにしてます。
    • 決算末日で絞れるようにも出来ますが、APIの呼び出し回数が数倍のオーダーになるので自重しました。
    • 但し決算末日で絞りたい場合は、以下の理由によりほぼ決算期末日から3ヶ月弱に集中するので、決算末+3カ月弱あたりの指定で、実用上は問題ない筈です。
  • 新規上場企業への対応 → 落ち着いたら、テストケースを選定して行いたい

2. 使い方

1. プログラムを置いたディレクトリに、"master"という名前のフォルダーを作成します。
2. http://www.jpx.co.jp/markets/statistics-equities/misc/01.html/より、上場企業のコード一覧EXCELを取得し、"[下記の略称名].txt"のタブ区切りで保存します

  • 下記略称名は、適宜改変可能です。略称名が出力の名称になります。
  • 上記注意のとおり、取得できるのは「設定期間内で、かつ現時点でも上場している銘柄」です
市場名 略称
市場第一部 (内国株) TSE1
市場第二部 TSE2
マザーズ (内国株) Mothers
JASDAQ(グロース) JQG
JASDAQ(スタンダード) JQS

3. 以下ソースを、適宜改変の上実行して下さい。

  • APIの負荷があるので、Sleepの時間は、短くしないで下さい
  • APIの負荷があるので、そのままのコピペでは動かない筈です。改変箇所は考えてください。

3. プログラム

INPUT
  • プレシス様API
OUTPUT
  • "./master/master_SIC.txt" -> 入力した銘柄一覧
  • "./data"以下に、DLしたzip済XBRL
  • "./downloaded_XBRL.txt" -> DLしたXBRL一覧表。中身はこんな感じ
1301	E00012	ED2013062500588	【E00012】株式会社 極洋 有価証券報告書 ‐ 第90期(平成24年4月1日 ‐ 平成25年3月31日)	http://resource.ufocatch.com/data/edinet/ED2013062500588	20130625
1332	E00014	ED2013062601290	【E00014】日本水産株式会社 有価証券報告書 ‐ 第98期(平成24年4月1日 ‐ 平成25年3月31日)	http://resource.ufocatch.com/data/edinet/ED2013062601290	20130626
1352	E00017	ED2013062500566	【E00017】株式会社ホウスイ 有価証券報告書 ‐ 第78期(平成24年4月1日 ‐ 平成25年3月31日)	http://resource.ufocatch.com/data/edinet/ED2013062500566	20130625
1378	E00007	ED2013062802178	【E00007】株式会社雪国まいたけ 有価証券報告書 ‐ 第30期(平成24年4月1日 ‐ 平成25年3月31日)	http://resource.ufocatch.com/data/edinet/ED2013062802178	20130628
1379	E00008	ED2013062703175	【E00008】ホクト株式会社 有価証券報告書 ‐ 第50期(平成24年4月1日 ‐ 平成25年3月31日)	http://resource.ufocatch.com/data/edinet/ED2013062703175	20130627
1380	E00344	ED2013062600250	【E00344】株式会社秋川牧園 有価証券報告書 ‐ 第34期(平成24年4月1日 ‐ 平成25年3月31日)	http://resource.ufocatch.com/data/edinet/ED2013062600250	20130626
ソースコード "01.getXBRL.R"
rm(list=ls())
invisible(gc()); invisible(gc())

##### library
library("RCurl")
library("XML")
library("plyr")

##### 証券コード取得
tmp.list <- paste0("./master/", dir("./master/", pattern = "*.txt") )
tmp.master.SIC <- list()
master.SIC     <- c()

for (i in 1:length(tmp.list)) {
  mkt.name <- sub(".txt", "", unlist(strsplit(tmp.list[i], "/"))[3])
  tmp.master.SIC[[i]] <- read.table(
    tmp.list[i], sep = "\t", header = TRUE, stringsAsFactors = FALSE)
  tmp.master.SIC[[i]] <- cbind( mkt.name, tmp.master.SIC[[i]] )
  master.SIC <- rbind(master.SIC, tmp.master.SIC[[i]][1:6])
}
colnames(master.SIC) <- sub("X","",colnames(master.SIC))
rm(list=c("i", "mkt.name", ls(pattern = "tmp*"))) # cleanup

# 証券コードリストの出力
write.table(master.SIC, "./master/master_SIC.txt", sep = "\t"
            , col.names = TRUE, row.names = TRUE)

##### XBRL取得
### 前準備
# 対象企業の証券コードをクエリワードにする
SICs <- as.character(master.SIC$コード)

# XBRL形式のファイル一式をダウンロード,"data"フォルダに保存
dir.create("data") # "data"フォルダを作成

# 定数設定
api.service.url <- "http://resource.ufocatch.com/atom/edinetx" # APIのURI

# 取得期間を設定
date.from <- as.Date('2013/6/1')
date.to   <- as.Date('2013/6/30')

# XBRL取得の本体
for (strSIC in SICs[1:1]){
  # 指定秒数のWAIT
  Sys.sleep(10)
  
  # リクエストを送信する
  strURL <- paste0(api.service.url, "/query/", strSIC) # URIと証券コードを結合
  Sys.sleep(1) # WAITを入れないとエラーとなることがある
  objQuery <- httpGET(strURL) # レスポンスを取得
  
  # xmlParseにより,APIのResponseをXMLInternalDocument形式に変換
  objXML <- xmlParse(objQuery,encoding="UTF-8")
  # 名前空間の定義をベクトル形式(simplify=TRUE) で取得
  objXML.namespaces <- xmlNamespaceDefinitions(objXML,simplify=TRUE)
  # デフォルトの名前空間に適当な接頭辞を付け直す
  names(objXML.namespaces)[ names(objXML.namespaces)=="" ] <- "default"
  
  # 要素ノード:entryを指定
  nodes.entry <- getNodeSet(objXML,"//default:entry",namespaces=objXML.namespaces)
  # 有価証券報告書のみを抽出
  lst.YUHO <- list()
  for(node in nodes.entry){ # <entry>タグをひとつずつ処理
    lst.temp <- list()
    # 提出書類のタイトルを取得→要素ノード:title の内容を取得
    title.value <- xpathSApply(node,path="default:title",fun=xmlValue,namespaces=objXML.namespaces)
    # 提出書類のタイトルに「有価証券報告書」を含むか否かを判定
    is.YUHO <- grepl(pat="*有価証券報告書*",x=title.value)
    if(is.YUHO){ #「含む」と判定された場合
      
      # 提出日で判定
      is.date <- xpathSApply(node,path="default:updated",fun=xmlValue,namespaces=objXML.namespaces)
      is.date <- as.Date(substr(is.date, 1, 10))
      if (date.from <= is.date && is.date <= date.to) { # 提出日が指定範囲内の場合
        # IDを取得
        lst.temp$id <- xpathSApply(node,path="default:id",fun=xmlValue,namespaces=objXML.namespaces)
        lst.temp$title <- title.value
        # <link>タグのうち,type='application/zip'のhref属性を取得
        lst.temp$url <- xpathSApply(node,path="default:link[@type='application/zip']/@href",namespaces=objXML.namespaces)
        lst.YUHO[[lst.temp$id]] <- lst.temp
      }
    }
  }
  
  # XBRLデータを保存
  # 抽出した情報を保存
  # plyr パッケージのldply 関数でデータフレームに変換
  dat.export <- ldply(lst.YUHO,.fun=data.frame)[, -1]
  
  if(!sum(dim(dat.export))==0){ # 該当なしの場合は抜ける
    for(lst in lst.YUHO){
      temp <- getBinaryURL(url=lst$url ) # バイナリデータ
      writeBin( temp, paste0("data/",lst$id,".zip") ) # zip形式で保存
    }
    dat.export <- cbind(strSIC
                        , substr(dat.export$title, 2, 7)
                        , dat.export)
    write.table(dat.export, file = "downloaded_XBRL.txt"
                , sep = "\t", append = TRUE
                , row.names = FALSE, col.name = FALSE)
  }
}

rm(list = ls()) # cleanup

感想

  • APIについては、本家本元のEDINETが提供すればなぁ、とは思いました。十分公益に資する税金の使い方と思うのですが、どうでしょうか。
  • (後編も含めてですが)色々とドツボに嵌りました、というかまだ嵌っています。間違いなく、後編公表日までには解消出来ません、と今からザンゲします。
  • データクレンジングや項目間整合性の確認、基礎集計なりは遠い先のお話ですね。。。
  • そもそもはてなブログ書くのもこれが初めてとか。色々ようわからんことによる苦労が。

Disclaimer

 本記事は個人的な勉強のために作成したものです。本情報の内容については万全を期しておりますが、その内容を保証するものではありません。これらの情報によって生じたいかなる損害についても、筆者は一切の責任を負いません。
 なお、本資料における意見、見解等はすべて筆者の個人的なものであり、筆者の属する組織の意見、見解等ではないことをご了承ください。

*1:書いたら今日中に間に合いそうもなかったので、というのはナイショ。