0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZENITH Code実践⑧ ZENITH Codeの実データで分析画面を動かす

0
Posted at

ここまでの4回は、架空の2社で企業分析画面の仕組みを読んできました。最終回の今回は、ZENITH CodeのAPIキーを使って、実在する企業のデータをこの画面に表示します。

キーを使う画面を作るときに気になるのは、「APIキーがブラウザから漏れないか」と「失敗したときにおかしな数字を表示しないか」の2つです。今回はこの2つを中心に、ローカルのPythonサーバーがどのようにキーを預かり、取得したデータを確かめてから画面へ渡しているかを読みます。

実データ接続でトヨタ自動車(7203)の財務データを5期分表示した画面。売上高・営業利益・純利益・営業利益率・決算期間・会計基準の表

上の画像は、記事の後半の変更を入れて、トヨタ自動車(7203)の5期分を取得した財務表です。

私たち株式会社Crabgrassは、企業の財務データなどをAPIで提供するZENITH Codeを開発しています。本記事は自社サービスのデータを使うためのテンプレートを題材にした実装例です。

この記事のコードは、公開サンプル集のcompany-analysisテンプレート(執筆時点の版)に対応しています。起動方法は実践④の「ローカルで開く準備」と同じです。前回の実践⑦では、画面の見た目と操作を整えました。

1. APIキーを用意する

ZENITH Codeの会員登録をして、利用規約とデータの利用条件に同意し、ダッシュボードでAPIキーを作成します。Freeプランでも、この画面で使う2つのAPIを呼び出せます。利用枠などの最新の条件は料金ページで確認してください。

作ったキーは、パスワードと同じように扱います。

  • 記事、チャット、公開リポジトリ、スクリーンショットに貼らない
  • コマンドの引数に直接書かない(シェルの履歴に残るため)
  • .env ファイルに書いた場合は、Gitに含めない

2. 実データ接続で起動する

サンプル起動のサーバーを Ctrl+C で止め、--live を付けて起動し直します。

python3 templates/company-analysis/server.py --live

起動すると、ターミナルにキーの入力欄が出ます。入力した文字は画面に表示されません。

ZENITH API key (hidden):

環境変数 ZENITH_API_KEY にキーが入っていれば、入力欄は出ずにそちらを使います。サーバーは .env ファイルを自動では読み込みません。

キーを受け取る部分は server.py の main() です。

if args.live:
    key = os.environ.get("ZENITH_API_KEY", "").strip()
    if not key and sys.stdin.isatty():
        key = getpass.getpass("ZENITH API key (hidden): ").strip()
    if not key:
        parser.error("Set ZENITH_API_KEY or run interactively. Do not pass a key on the command line.")

getpass は、入力した文字を画面に出さずに受け取るPythonの標準ライブラリです。キーを引数で受け取る --key のような指定は、あえて用意していません。

ブラウザで http://127.0.0.1:8765 を開き、「実データに接続」を押します。--live を付けずに起動していると、次の案内が表示されます。

実データに接続を選んだが、キーなしで起動している場合の案内。会員登録とAPIキーの作成、--liveでの起動方法、キーは端末の非表示入力で設定する旨

3. キーはブラウザへ渡さない

証券コードに 7203 を入れて「データを取得」を押すと、ブラウザはZENITH CodeのAPIではなく、自分のパソコンの server.py に問い合わせます。

ブラウザはローカルのserver.pyだけに問い合わせ、server.pyがAPIキーを付けてZENITH CodeのAPIを呼ぶ流れ

区間 通信先 APIキー
ブラウザ → server.py http://127.0.0.1:8765/api/snapshot 含まない
server.py → ZENITH Code https://api.zenithcode.net/stable/... Pythonがヘッダーに付ける

キーはPythonのプロセスの中にだけあります。ブラウザのJavaScript、HTML、localStorage のどこにも入りません。ブラウザの開発者ツールで通信を見ても、キーは出てきません。

ローカルの窓口を、ほかのページから使わせない

server.py は、/api/snapshot への問い合わせを受けるたびに、次の3つを確かめます。

expected = "http://127.0.0.1:%d" % self.server.server_port
if self.headers.get("Origin") != expected or not secrets.compare_digest(self.headers.get("X-Local-Token", ""), token):
    self.reply(403, {"error": "local_only"})
    return
確かめること 防いでいること
Host が 127.0.0.1:ポート番号 か 別の名前でこのサーバーに届く問い合わせ(DNSリバインディング)
Origin が同じ画面か 開いている別のサイトが、裏でこの窓口を呼ぶこと
X-Local-Token が起動時の合言葉と一致するか 画面を開いていない相手からの問い合わせ

合言葉(token)は、サーバーを起動するたびに secrets.token_urlsafe(32) で作り直します。画面は最初に /api/config から受け取り、取得のたびにヘッダーへ付けます。キーそのものではないので、画面が知っていても問題ありません。

さらに、サーバーは 127.0.0.1 でだけ待ち受けます。同じWi-Fiにある別の端末からは接続できません。実践④で「スマホから同じURLを開いてもつながらない」と書いたのはこのためです。

一度に1件だけ取得する

if not gate.acquire(blocking=False):
    self.reply(429, {"error": "busy"})
    return

ボタンを続けて押しても、取得中のあいだは次の取得を始めません。画面には「別の取得が進行中です。」と表示します。利用枠を意図せず使い切らないためです。画面の起動、言語の切り替え、グラフの指標の切り替えでは、APIを呼びません。

4. 1回の取得で呼ぶAPI

「データを取得」を1回押すと、python/zenith_examples.py の snapshot が2つのAPIを呼びます。

if mode == "snapshot":
    result["latestClose"] = requester("/quote", {"symbol": symbol}, api_key)
result["annualFinancials"] = requester("/income-statement", {
    "symbol": symbol, "period": "annual", "limit": 3,
}, api_key)
API 画面での用途
/stable/quote 直近終値と株価の日付
/stable/income-statement 最新3期の売上高・利益・出典

実践④で見た demo.json の annualFinancials と latestClose は、この2つの応答と同じ形にしてあります。だから、取得したデータは normalize() を通すだけで、グラフ、表、前期比、出典の処理をそのまま使えます。架空データで読んできたコードは、実データでも同じように動きます。

5. 取得したデータを確かめてから表示する

取得したデータは、画面に出す前に normalize() で確かめます。実践④で読んだ関数です。

確かめること 満たさないとき
symbol が文字列で、配列が2つそろっている 表示を止める
各期の symbol が、取得した企業と同じ 表示を止める
各期が通期(FY)で、決算期の形が YYYY-MM 表示を止める
同じ決算期が2回出てこない 表示を止める
期の数が3以下、終値が1件以下 表示を止める

1つでも満たさなければ invalid_response のエラーになり、画面には「想定外のデータ形式のため、表示を停止しました。」と表示します。おかしなデータを、見た目だけ整えて表示することはしません。別の企業の行が混ざったまま、もっともらしいグラフができてしまうほうが、エラーよりも気付きにくいためです。

エラーの中身を画面へ流さない

except ZenithError:
    # Never relay exception bodies or upstream content into the UI.
    self.reply(502, {"error": "upstream_failed"})

APIの呼び出しに失敗したとき、サーバーはAPIから返ってきた本文を画面へ渡しません。画面には決まった文言だけを出します。エラーの本文には、キーの一部や内部の情報が含まれる可能性があるためです。

画面の表示 主な原因
実データ接続の設定が必要です。 --live を付けずに起動した
証券コードを確認してください。 4〜5文字の英数字(7203・130A など)ではない
取得できませんでした。キー・利用条件への同意・利用枠・接続状態を確認してください。 キーの誤り、利用条件への未同意、利用枠の超過、通信の失敗
想定外のデータ形式のため、表示を停止しました。 normalize() の確認を通らなかった
別の取得が進行中です。 取得中に続けて押した
ローカル接続を確認し、ページを開き直してください。 サーバーを起動し直した後、古い画面のまま操作した

エラーのあとに、自動で取得し直すことはしません。原因がキーや利用枠にある場合、繰り返しても同じ結果になり、利用枠だけを使ってしまうためです。

6. 直近終値と決算期は別の時点

実データでは、数値カードの右端に直近終値が表示されます。

  • 直近終値の下の「株価日付」は、その終値の取引日です。
  • 売上高や営業利益のカードの「通期 2026-03」は、決算期です。

2つは別の時点の値です。そのため、このテンプレートは、直近の株価と過去の決算から独自のPERやPBRを計算していません。画面の「数字を読む前に」にも、そのことを書いています。

もう1つ大切な点があります。画面の上には、次の注意が表示されます。

実データ表示:ご自身の非公開環境で利用してください。株価・制限対象の書類等の公開はできません。

株価などの取引所のデータは、自分で分析に使うことはできても、第三者に見せる形で公開することはできません。実データの画面をスクリーンショットでSNSや記事に載せる場合は、株価が写っていないかを確認してください。この記事の冒頭の画像も、株価を含まない財務データの部分だけにしています。表示できる範囲はデータの表示ルールにまとめています。

7. 実データでの企業比較

実データ接続では、企業比較の欄が証券コードの入力欄に変わります。比較したい企業のコード(例:7267)を入れて「比較データを取得」を押すと、同じ /api/snapshot で2社目を取得します。

共通の決算期の選び方と、比較条件の確かめ方は、実践⑥で読んだ架空データのときと同じです。3月決算と12月決算の企業を選ぶと、共通の決算期がないと表示されます。

8. 取得する期を5期に増やす

最後に、取得する期を3期から5期に増やします。変更は2か所です。

1. 取得する件数(python/zenith_examples.py)。snapshot で損益計算書を呼ぶ部分の limit を5にします。

result["annualFinancials"] = requester("/income-statement", {
    "symbol": symbol, "period": "annual", "limit": 5,
}, api_key)

2. 確認の上限(templates/company-analysis/model.mjs)。normalize() の期の数の上限を5にします。

if (years.length > 5 || snapshot.latestClose.length > 1)
  throw new Error("invalid_response");

2つ目を忘れると、5期を取得した時点で「想定外のデータ形式」と表示されます。normalize() が、想定より多い期をエラーとして扱うためです。上限を設けておくと、APIの引数を間違えて大量の期を受け取ったときにも気付けます。

server.py を Ctrl+C で止め、--live で起動し直します。Pythonのファイルを変えたときは、ブラウザの再読み込みだけでは反映されません。7203を取得すると、冒頭の画像のように2022-03期から2026-03期までの5期が表示されます。グラフ、表、前期比、出典、CSVは、どれも期の数を決め打ちしていないので、そのまま5期分になります。

取得する期を増やしても、APIの呼び出しは1回のままです。limit は1回の応答に含める期の数で、呼び出しの回数ではありません。

limit の変更は、同じ関数を使うコマンドライン版の financials にも反映されます。テンプレートだけを変えたい場合は、snapshot のときだけ5にする条件を書いてください。

9. 連載のまとめ

全8回で、APIからデータを取得するところから、ブラウザで使う分析画面まで進みました。

回 扱ったこと
① Pythonで売上高・営業利益の推移をグラフにする
② 営業利益率を計算し、APIの値と照合する
③ 2社の売上高と営業利益率を比較する
④ 企業と指標を切り替えられる分析画面を作る
⑤ 財務表と出典をたどれる画面を作る
⑥ 決算期と前期比を分析画面に加える
⑦ PCとスマホで使いやすい分析画面に整える
⑧ ZENITH Codeの実データで分析画面を動かす(この記事)

どの回にも共通していたのは、期間・単位・欠損・出典を最後まで手放さないことです。数字を表示する前に、それがいつの、どの範囲の、どの書類の値かを確かめる。欠損を0にせず、比べられないものは比べない。この画面は、その確認を毎回手作業でしなくて済むように作っています。

この画面を出発点に、見たい指標を足したり、自分の業務に合わせて表示を変えたりしてみてください。

ZENITH Code — 日本企業の財務データを、APIで。サービスサイトを見る

ZENITH Codeを見る · APIドキュメント

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?