ストックドッグ

KatoTakahiro。金融系の会社で働くSEが株やPython、その他諸々について書いています。サービスも運営してます→http://fmbrain.work

Promiseとasyncによる非同期処理

非同期処理

javascriptで非同期処理をかくために勉強した内容をメモ。


Promiseの公式ドキュ
developer.mozilla.org


asyncの公式ドキュ
developer.mozilla.org


JavaScript Promiseの本
azu.github.io

Promise

promiseオブジェクトを定義し、そこに関数resolveとrejectを登録する。

関数成功時はresolveが返され、失敗時はrejectが返される、ようにコードを書く。


成功時、失敗時の次の実行したいことを.then()で書く。

.then()はいくらでもつなげて書くことができる。

function promise() {
 return new Promise(function(resolve, reject) {
     setTimeout(function() {
         console.log('root', 5)
         resolve(5)
     }, 1000)
 })
}

promise().then(function(value) {
 console.log('promise', value)
 return value + 5
}).then(function(value){
 console.log('promise', value)
}).catch(function(error) {
 console.log('error')
 return new Error('error')
})

async

非同期処理を行う関数として、async functionとして定義する。

asyncで定義すると返り値は、promiseオブジェクトになる。

そのため.then()の書き方はいっしょ。


ただ、.then()をチェーン式に書かなくていいようなawaitという関数が用意されている。

awaithは、promiseの解決まで処理を一時停止するため、awaitを複数書けば.then()と実質いっしょのコードになる。

async function wait() {
 try {
     const x = await promise()
     console.log('async', x)

     const y = x + 5
     console.log('async', y)

     return y

 } catch(e) {
     return new Error('error')
 }
}

wait().then(function(value) {
})

firebase realtime databaseの.on('value')と.on('child_added')の使い分け

.on('value')と.on('child_added')の使い分け

Firebase realtime databaseの公式ドキュにはガッツリ書かれていますが、備忘を兼ねて記事に。

全くWeb周り詳しくないので記事レベルはあしからず。

firebase.google.com


realtime databaseのデータ取得のときに.on('value')で取得するべきか、.on('child_added')で取得するべきか、悩むときがありますね。

そんなときにどう使い分けるかの記事です。


.on('value')で取得するサンプルコードはこんな感じ。

const sampleRef = firebase.database().ref('sample')

sampleRef.on('value', function(snapshot){
	console.log('value', snapshot.val())
})


.on('child_added')で取得するサンプルコードはこんな感じ。

sampleRef.on('child_added', function(snapshot){
	console.log('child_added', snapshot.val())
})


公式に全部書いてあるので、正直、この記事の存在価値は皆無ですが、あくまでも備忘なので!!


f:id:doz13189:20190203155520p:plain

大別すると...

  • child_added


この2つになります。childはchangedとかありますが、根本的にvalueとchild_addedは取得方法が異なります。

value

valueは、指定されたノード全体のキャプチャを取得します。

以下のノードがあれば、これをまるっと取得してきます。

f:id:doz13189:20190203155644p:plain

const sampleRef = firebase.database().ref('sample')

sampleRef.on('value', function(snapshot){
	console.log('value', snapshot.val())
})

そのため、valueの返り値は指定したノード全体、ということになります。


f:id:doz13189:20190203155711p:plain

child_added

child_addedは、指定されたノードの子ノードを取得してきます。

以下のノードは、子ノードを5つ持っており、これらを取得します。


f:id:doz13189:20190203155644p:plain

sampleRef.on('child_added', function(snapshot){
	console.log('child_added', snapshot.val())
})


{test: 0.9033302588248819}が返り値になっている理由は子ノードを取得しているからです。


f:id:doz13189:20190203155805p:plain


そして、子ノードを取得してくるため、子ノードが5つあれば5回実行されることになります。


この挙動でよくミスるのが、以下のコードを書いたときです。

.onではなく、once()で書いています。

sampleRef.once('child_added', function(snapshot){
	console.log('child_added', snapshot.val())
})

.onceは1回しか実行されないので、1つ子ノードを取得して、終了です。

ノード全体を取得したいときは、このコードじゃ動きません。

まぁ、子ノードの追加をリッスンしたいのがchild_addedなので、.onceを使うなら用途に反してますよってことです。

.on()と.once()の違いは、この記事。

www.stockdog.work

子ノードに変更があった場合の.on('value')と.on('child_added')の違い

  • .on('value')は、ノード全体を取得してきます。
  • .on('child_added')は変更分だけ取得してきます。


返り値で見ると、以下になります。

1回目は、ノード全体をvalueもchild_addedも取得します。

データの変更があった2回目は取得内容に差があります。

f:id:doz13189:20190203155949p:plain

※warningは気にしないでね、サンプルコードのたびにindex加えてられるかい

まとめ

.onで変更を監視したいときは、child_addedじゃないとデータ取得の容量が増えてしまいます。

基本は.on('child_added')でいいんじゃないですかね(適当)

以上、終わり!

firebase realtime databaseのon()とonce()の違い

on()とonce()の使い分け

Firebase realtime databaseの公式ドキュにはサラッとしか書かれていないので、備忘を兼ねて記事に。

全くWeb周り詳しくないので記事レベルはあしからず。

firebase.google.com


ちなみに、on()とはdatabaseを読みにいくときのメソッドのこと。

const sampleRef = firebase.database().ref('sample')
sampleRef.on('value', function(snapshot){
	console.log('value', snapshot.val())
})


場合によっては、.once()で書くこともある。

const sampleRef = firebase.database().ref('sample')
sampleRef.once('value', function(snapshot){
	console.log('value', snapshot.val())
})

その使い分けって、どうやって決めてんのって記事です。

通常DBとrealtime databaseの違い

on()とonce()の使い分けは、realtime databaseの特殊スキル「リアルタイム」を使いたいか、使いたくないかで決まります。

通常のDBの更新は以下の図になります。

(更新の成功の有無のリターンコードは省いています。)


f:id:doz13189:20190203025836p:plain



realtime databaseの場合は、以下の図になります。


f:id:doz13189:20190203025939p:plain


以下の公式ドキュの記載にもある通り、realtime databaseは更新されるたびにクライアント側に同期しにいきます。

一般的な HTTP リクエストではなくデータ同期を使用します。データが変更されるたびに、接続されているあらゆる端末がその更新を数ミリ秒以内に受信します。

on()とonce()の使い分け

  • 更新された内容をクライアント側に同期したいときは.on()
  • 不要のときは.once()を使います。


.once()の場合のデータ取得は1回で終わりで、同期は不要である、ということになります。


f:id:doz13189:20190203030150p:plain

基本は.once()を使えばいい

チャットアプリのように変更を常にリッスン(監視)して、データを常に同期する必要があれば.on()を使いますが...

.on()にするということはすなわち、クライアント側とrealtime databaseが常に接続している状態です。

例えば、5台のクライアントがあり、そのうち1台のクライアントが更新処理をすると、1回の更新で5回の同期処理がはしることになります。


f:id:doz13189:20190203030249p:plain


これがクライアント100台、1000台と増えてくると、トランザクションは圧倒的に増えていきます。


ちなみに以下はrealtime databaseのキャパシティです。

1つのデータベースで、約100,000件の同時接続と、毎秒1,000回の書き込みまでスケールできます。

これを超えると無料枠では対応できなくなりますし、課金制であればその分料金割増になります。

そのため、基本は.once()で同期処理は走らないようにしておけばいい、ということです。

.off()

ある期間だけは.onにしたいけど、ある一定時間たてば同期は不要という場合は.off()を使います。

const sampleRef = firebase.database().ref('sample')
sampleRef.on('value', function(snapshot){
	console.log('value', snapshot.val())
})


ある一定時間たったときに.off()を実行します。

sampleRef.off()

まとめ

firebaseは公式ドキュが丁寧なので最高です。

こんなクソ記事読まずに公式ドキュ読みましょう笑

以上、終わり!

リロードするとVuexのデータが初期化される問題

フロントの開発初心者のぼくはVuexはストレージ的なライブラリであると勘違いしていました。

なのでVuexを使えば、「リロードするとVuexのデータが初期化される問題」は解決されると...


もちろん、コンポーネント間のデータ受け渡しをプロパティ (props)で行っていた場合に、リロードすると受け渡ししたデータが初期化される、というのは理解していました。

が、Vuexで管理してもなるやん、と。


ちょっとVuexの理解足りんなと、勉強し直したわけで、

いろいろドキュメントを読むと、FluxやReduxの思想に影響を受けたとか書いてありますが、まずそいつら知らん。

使ったことのない技術のドキュメントを読んでもなかなか理解進まないものですが、ようやく毛ほどは理解できるようになったので備忘を兼ねて記事に。

解決策

$ npm install vuex-persistedstate
import createPersistedState from 'vuex-persistedstate'

const path =     {
    // データ定義
}

export default new Vuex.Store({
        modules: {
                path
        },
        plugins: [createPersistedState()]
})

github.com

Vuex

VuexはVue.jsアプリケーションのための状態管理パターン + ライブラリです。

vuex.vuejs.org


どうやったらアプリケーション内でのデータ管理がうまくできるか、という考え方、いい感じにいうと設計思考であるFluxを元に作られているのがVuexです。
(Vuexの公式には影響を受けている、と書いてありますね)

データを"グローバルシングルトンで管理する"と公式ドキュには書いてありますが、ようはグローバルにデータを定義しよう、ということ。(言い換えになっていない)

vueで言う、componentをローカルで定義するか、グローバルで定義するか、vuexではデータをグローバルに定義しているってことになる。


コンポーネント間でデータの受け渡しを定義してたら大変だから、グローバルに定義して、どの階層にいるコンポーネントからでもデータにアクセスできるようにしよう、とそういうことですね。


まったくストレージ要素はありません...


なので、リロードするとデータが初期化される、という問題はVuexで定義しようと、プロパティ(Props)で定義しようとまったく関係ありません。

どちらも保存先はメモリなので。

vuex-persistedstate

解決策としては、メモリ以外の場所にデータを保存する、であり保存する場所はリロードしても消えない場所です。

選ばれたのは、WebStorageでした。

Vuexで定義されたデータをWebStorageに保存するライブラリがvuex-persistedstateです。

WebStrage

データをブラウザに保存することができる仕組み。

WebStrage APIを使って使用することができるので、ChromeFirefoxならもちろん使用可!

WebStrage APIのStrageには、localStrageとsessionStrageの2種類の保存先があり、どちらもJavaScriptで使えるので、例えばChromeのコンソールから使うことができる。

# 定義した
localStorage.hello = 'World'

# 呼び出した
localStorage["hello"]

今使ったのはlocalStrageで、localStrageとsessionStrageは全然違うので用途によって使い分ける。

以下、参照。

www.htmq.com


vuex-persistedstateでは、デフォルトでlocalStorageに保存される。

まとめ

Vuexで定義したデータをvuex-persistedstateを使ってlocalStrageに保存することで、

「リロードするとVuexのデータが初期化される問題」は解決する。

以上、終わり!

XBRLをAPI経由で取得する方法 ~Pythonで実装してみる~


EDINETのAPIの仕様が公開され、XBRL解析する勢はワクワクしています...(たしか公開は2019年3月)


と言いつつ、現状でもAPIは提供されており、そこからXBRLの取得自体は可能です。(提供元は違います)


そもそもXBRLって何って方は、こちらの記事参照。


www.stockdog.work


提供されているのは、有限会社プレシスという会社が有報キャッチャーというAPIです。

(引用)
有報キャッチャーは、EDINETやTDnetで開示されたIR情報をご提供するサービスです。
XBRLデータを活用し、会社属性情報などを整理・閲覧することができます。

ufocatch.com



APIの使い方はどシンプルなので、記事にするほどでもないのですが、XBRLのフォルダ構成が絡むと少しややこしくなるので備忘含めたメモ的な記事にしておきました。

とりあえず、有報キャッチャーなるAPIを経由したXBRLの取得をPythonで実装してみました記事です。

APIのエンドポイント

http://resource.ufocatch.com/atom/edinet/query/

エンドポイントの末にクエリワード(銘柄コードやEDINETコード)を指定して、リクエストを送ると、EDINETに提出されているドキュメントのURL一覧が返ってきます。


あくまでもレスポンスは、ドキュメントのURL一覧であり、まだXBRLは取得できません。

APIを使ってみる

例えば、「三菱UFJ 日本成長株オープン」という投資信託のドキュメント一覧を取得したければ...

# 三菱UFJ 日本成長株オープンのEDINETコードはG01051
GET http://resource.ufocatch.com/atom/edinet/query/G01051

EDINETコードは、EDINETサイトからCSV形式で取得可能です。


ok google 「EDINETコードを検索して」

と言えばひっかかる気がします。(EDINETはURL長いので貼りたくありませんでした...)


上のリクエストを送ると、レスポンスはこんな感じ。

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>有報キャッチャー - EDINET情報配信サービス</title>
  <link href="http://resource.ufocatch.com/" />
  <updated>2018-09-22T10:04:03Z</updated>
  <id>http://resource.ufocatch.com/atom/edinet/query/G01051</id>
  <entry>
    <title>【G01051】三菱UFJ投信株式会社 有価証券報告書(内国投資信託受益証券)-第19期(平成29年4月21日-平成30年4月20日)</title>
    <link rel="alternate" type="text/html" href="http://resource.ufocatch.com/pdfv/ED2018071900080" />
    <id>ED2018071900080</id>
    <docid>S100DKHL</docid>
    <updated>2018-07-19T09:19:00+09:00</updated>
    <link rel="related" type="application/zip" href="http://resource.ufocatch.com/data/edinet/ED2018071900080" />
    <link rel="related" href="http://resource.ufocatch.com/xbrl/edinet/ED2018071900080/S100DKHL/XBRL/AuditDoc/jpaud-aar-cn-001_E11518-000_2018-04-20_01_2018-07-19.xbrl" type="text/xml" />
    <link rel="related" href="http://resource.ufocatch.com/xbrl/edinet/ED2018071900080/S100DKHL/XBRL/AuditDoc/jpaud-aar-cn-001_E11518-000_2018-04-20_01_2018-07-19.xsd" type="text/xml" />
    ...


長いので省略しますが...

提出されているドキュメントの一覧が返ってきます。

厳密には提出されているファイルのパスの一覧が返ってくる


提出されているドキュメントの一覧が返ってくるというのは誤りで、キレイな一覧では返ってこないです。

理想は...

$ GET http://resource.ufocatch.com/atom/edinet/query/G01051
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2018-03-23に提出された有報.xbrl
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2017-03-23に提出された有報.xbrl
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2016-03-23に提出された有報.xbrl
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2015-03-23に提出された有報.xbrl

みたいな、ドキュメントの一覧なのですが、そうはいきません。

キレイな一覧で返ってこない理由は...

例えば、有報(きっと他のドキュメントも)。

これらは1つの有報というドキュメントを複数ファイルで構成されているからです。

$ GET http://resource.ufocatch.com/atom/edinet/query/G01051
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2018-03-23に提出された有報.xbrl
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2018-03-23に提出された有報の画像.image
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2018-03-23に提出された有報の一部.htm

http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2017-03-23に提出された有報.xbrl
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2017-03-23に提出された有報の画像.image
http://resource.ufocatch.com/xbrl/edinet/ED2018032300059/2017-03-23に提出された有報の一部.htm

...

イメージで言うと、上記のようなレスポンスになります。


XBRLをダウンロードしたことがあって、中身を見たことがある人はわかると思います。

ダウンロードするとXBRLのファイルだけではなくて、他にもhtmやimageなど色々とあるんですね。


f:id:doz13189:20180922195607p:plain


この中でほしいのは、拡張子が.xbrlのファイルだけです。

他はノイズです。


そのため、返ってきたレスポンスの中から.xbrlのパスのみを取得して、その取得したパスを見に行くことでXBRLが取得できます。

Pythonで実装

import requests
from bs4 import BeautifulSoup
import re
import time

# エンドポイントにリクエスト
response = requests.get("http://resource.ufocatch.com/atom/edinet/query/G01003")
response.encoding = response.apparent_encoding

# レスポンスをBS4でHTMLを解析
soup = BeautifulSoup(response.text, "lxml")
links = soup.find_all("link")

# 返ってきた一覧のうち、.xbrlかどうかを正規表現で判別
pattern = ".*PublicDoc.*\.xbrl"

for lin in links:
	result = re.search(pattern , lin.get("href"))

	# .xbrlであれば、xbrlを取得
	if result != None:
		print(lin.get("href"))
		response = requests.get(lin.get("href"))
		response.encoding = response.apparent_encoding
		print(response.text)

	else:
		pass

	time.sleep(2.0)


少し正規表現の部分を補足すると...

pattern = ".*PublicDoc.*\.xbrl"

拡張子を.xbrlで指定しています。

また、Publicと指定しているのは、EDINET上に公開されている方の情報を取得するためです。

Publicと同じ階層にAuditがあるのですが、こちらは独立監査人用のものなようです(たぶん)。

まとめ

とりあえず、特にエラーがなければ上記のコードでXBRLが取得できたはずです。

素でAPI叩くのもいいですが、edinet-xbrlというライブラリもあるので、APIの構造が理解できれば以降はライブラリを使ったほうがいいですね。


github.com



以上、終わり!

ざっっくりXBRL ~XBRLとは?何であるの?どうやって読むの?~

XBRLとは

財務系の書類を作成、流通させることを目的にXMLをベースに標準化された言語です。

こういった書類ってPDFのようなテキストベースなイメージがありますが、テキストベースだと人の目でしか基本は読めないので流通しにくいです。

ステマティックに情報を取得可能にしよう、という目的です。

f:id:doz13189:20180922152105p:plain

なんでXMLじゃだめなの?

よくわからん形式作んなよと。

思うかもしれませんが、XBRLがあることには合理的な理由があります。


簡単に言うと、XMLを財務系にカスタマイズさせたものです。


もう少し細かい説明をすると、

重要なのはxmlがベースであるということ。

xmlは自由にタグを定義することができます。


htmlはタグとしてbodyやtitleが定義されています。

新しく自分でhandやfaceというタグを定義することはできません。


繰り返しになりますが、xmlはhtmlとは違い、自由にタグの定義が可能です。

財務系のドキュメント、例えば有価証券報告書xmlを作成しようとすると、売上はSale、会社名はCompanyNameのようにタグを定義すると便利です。


有価証券報告書は、たくさんの会社が作成します。

xmlベースで有価証券報告書をたくさんの会社が作ることになり、ある会社は売上をSaleとして定義し、ある会社はUriageのように定義するとします。

こうなると、xmlベースでせっかく作成しても、情報の取得しにくさはあまり変わりません。

いろんな会社の情報を横断的に取得したいものです。


なので、売上はSaleにして!というルールを統一します。

これがXBRLです。


財務系に使われる単語を網羅して、これはこのタグ!あれはこのタグ!のように一覧を作成しています。

この一覧に従って作成されたものがXBRLです。

XBRLのざっっっっっくりイメージ

定義しているタグは私が勝手にわかりやすさベースで作ったものです。

<CompanyInfo>
	<CompanyName>TOYOTA</CompanyName>
	<CompanyCode>7203</CompanyCode>
</CompanyInfo>
<Performance>
	<Sale>9999999</Sale>
</Performance>


XBRLのやりたい事は、1つ1つの情報にタグをつけてあげるということです。

例えば、会社名ならCompanyNameという風に。

上のタグを全部、日本語で表したら以下になります。

<会社情報>
	<会社名>TOYOTA</会社名>
	<会社コード>7203</会社コード>
</会社情報>
<業績>
	<売上>9999999</売上>
</業績>


全てを1つの階層にまとめると、情報にまとまりがないので、

ある程度のまとまりでグループを作って階層化しています。


会社名や会社コードは、会社情報というグループ、という風に。


これだけグループ化、タグ付けされていれば、システム的にも情報の取得がしやすいです。


タクソノミ

財務系に使われる単語を網羅して、これはこのタグ!あれはこのタグ!のように一覧を作成しています。

さきほど、財務系単語とタグが紐付けられている一覧の存在をほのめかしましたが、その一覧の名前が「タクソノミ」です。


英語でTaxsonomy、分類法という意味です。


タクソノミは、金融庁のHPからゲットできます。

2018年版EDINETタクソノミの公表について

# 上のURL開くと、色々とリンクがあるけど、以下の名称がタクソノミの一覧!
タクソノミ要素リスト(EXCEL:6,291KB)


毎年更新されてます。

最新版は2018年度版です。


開くとこんな感じ。

f:id:doz13189:20180922152222p:plain


色々と目次がありますが、右のシートの方に一覧があります。

こちらがタクソノミ本体。

f:id:doz13189:20180922153756p:plain


シートが分かれているのは様式別にExcelシートが用意されてるためです。

会社は有価証券報告書を提出するときに、金融庁が定めるフォーマットに従って作成しないといけません。

これが様式と言うやつです。

様式別にExcelシートが用意されているので、量が多めになっていますが、あまり気にしなくていいと思っています。


基本的には、財務系の書類に記載がある単語は、このExcelの中に対応するタグがあります。

そして、そのタグがXBRLには使われています。

XBRLの読み方

例えば、EDINETで三井住友トラスト・アセットマネジメント株式会社が運用するジャパン・グロース・ファンドの有価証券報告書を見てみました。

投資方針について記載がある部分を見ています。


f:id:doz13189:20180922152352p:plain

XBRLはEDINETからダウンロード可能です。

そして、投資方針に対応する部分がXBRLにもあります。


f:id:doz13189:20180922152746p:plain


投資方針は、InvestmentPolicyHeadingというタグの中に書かれていることもわかります。

# 投資方針が書かれているタグ
<jpsps_cor:InvestmentPolicyTextBlock contextRef="FilingDateInstant">


タクソノミにも投資方針はInvestmentPolicyHeadingというタグを利用することが書かれています。


f:id:doz13189:20180922152608p:plain

まとめ

ざっくりXBRLについて書いてみました。


以上、終わり!

Apschedulerで出るValueError: Unable to determine the name of the local timezon

発生エラー

久々にApchedulerを使うとこんなエラーに出くわしました。

忘れそうなのでメモしておきます。

ValueError: Unable to determine the name of the local timezone -- you must explicitly specify the name of the local timezone. Please refrain from using timezones like EST to prevent problems with daylight saving time. Instead, use a locale based timezone name (such as Europe/Helsinki).

ValueError: Unable to determine the name of the local timezone

タイムゾーンを設定しろと。

解決策

日本時間にしたいんですが、その設定ってどうやって書くんだろう...

例えば、Europe/Helsinki、みたいな感じに書けばいいっぽいので、Asia/Tokyoかな??

ビンゴでした。エラーは解決。

from apscheduler.schedulers.blocking import BlockingScheduler
import donchan

sched = BlockingScheduler(timezone="Asia/Tokyo")


@sched.scheduled_job('interval', minutes=1)
def scheduled_job():
	bot = donchan.botter()
	bot.whileman()

sched.start()