【J-Quants】取引カレンダーAPIを用いた株価のバッチ取得

## はじめに

はじめまして！J-Quants運営チームです。

個人投資家向けのAPIデータ配信サービスである[J-Quants](https://jpx-jquants.com/)を利用する上での、技術的なTipsなどの技術記事を投稿していきたいと思います！

この記事では、東証および大阪取引所の営業日・休業日・祝日取引（祝日取引は大阪取引所のみ）の有無を取得できる取引カレンダーAPIを用いて、株価を日次バッチで取得するPythonでの方法をご紹介します。

## （参考）J-Quantsとは

J-Quantsとは、ヒストリカル株価データ・財務データなどの金融データを取得できる、個人投資家向けのAPIデータ配信サービスです。投資にまつわるデータを分析しやすい形式で提供し、個人投資家の皆様がデータを活用して取引できるようになることを目的としております。

個人投資家がデータ分析を用いた投資分析を行う際の大きな障壁は、整形された金融データの取得が難しいことでは？という理由から、2022年7月にベータ版をリリースいたしました。ご好評の声を頂いたこともあり、2023年4月より正式にリリースしております。

## 取引カレンダーAPI

上場銘柄の株価を日々取得したいなど、金融データを毎日取得する際には、市場が空いていない日には取得処理をスキップする処理を書きたくなります。月曜日から金曜日は通常は営業日ですが、祝日や年末年始を考慮するのが手間です。JPXからは[休業日一覧](https://www.jpx.co.jp/corporate/about-jpx/calendar/index.html)や[祝日取引実施日](https://www.jpx.co.jp/corporate/about-jpx/calendar/index.html)が公表されていますが、コードを書いてデータを取得する上ではこのページをスクレイピングする必要があります。そうした手間を削減したい！というご意見から、J-Quantsでは取引カレンダーAPIをリリースいたしました。

以下にデータを取得する際のコードの例を示します。J-QuantsのAPIから金融データを取得する際には、まず最初に登録したメールアドレスとパスワードをbodyに入れてPOSTすることでリフレッシュトークンを取得し、そのリフレッシュトークンをクエリストリングに入れてPOSTすることでIDトークンを取得します。

`MAIL_ADDRESS`と`PASSWORD`はご自身のメールアドレスとパスワードを入れてください。セキュリティの観点から、これらはコード中には記載せずに環境変数に設定しておき、スクリプトの先頭で`MAIL_ADDRESS = os.environ["MAIL_ADDRESS"]`などとして読み出すことを推奨します。

なお、記事中で用いたPythonとライブラリのバージョンは以下の通りです。

- Python 3.11.0
- pandas 1.5.3
- requests 2.28.2

``` python
import json

import pandas as pd
import requests

resp = requests.post(
    "https://api.jquants.com/v1/token/auth_user",
    data=json.dumps({"mailaddress": MAIL_ADDRESS, "password": PASSWORD})
)
REFRESH_TOKEN = resp.json()["refreshToken"]

resp = requests.post(
    "https://api.jquants.com/v1/token/auth_refresh",
    params={"refreshtoken": REFRESH_TOKEN}
)
ID_TOKEN = resp.json()["idToken"]
```

そうしたら、今取得したIDトークンをヘッダーに入れて取引カレンダーAPIをリクエストします。

``` python
resp = requests.get(
    "https://api.jquants.com/v1/markets/trading_calendar",
    headers={"Authorization": f"Bearer {ID_TOKEN}"}
)
calendar = resp.json()["trading_calendar"]
```

試しに取得したcalendarのlistの先頭5個の要素を見てみます。

``` python
calendar[0:5]
```

    [{'Date': '2008-01-01', 'HolidayDivision': '0'},
     {'Date': '2008-01-02', 'HolidayDivision': '0'},
     {'Date': '2008-01-03', 'HolidayDivision': '0'},
     {'Date': '2008-01-04', 'HolidayDivision': '2'},
     {'Date': '2008-01-05', 'HolidayDivision': '0'}]

HolidayDivisionが”0”は非営業日、“1”は営業日、“2”は東証の半日立会日、“3”は祝日取引のある非営業日となります（詳細は[API仕様書](https://jpx.gitbook.io/j-quants-ja/api-reference/trading_calendar/holiday_division)を参照）。

2023年の非営業日を見てみましょう。見やすくするため、pandasのDataFrameに変換して表示することにします。

``` python
df = (
    pd.DataFrame(calendar)
    .assign(Date=lambda d: pd.to_datetime(d["Date"], format="%Y-%m-%d"))
    .loc[lambda d: (d["Date"].dt.year == 2023) & (d["HolidayDivision"] == "0")]
)
print(df)
```

               Date HolidayDivision
    5479 2023-01-01               0
    5480 2023-01-02               0
    5485 2023-01-07               0
    5486 2023-01-08               0
    5487 2023-01-09               0
    ...         ...             ...
    5829 2023-12-17               0
    5835 2023-12-23               0
    5836 2023-12-24               0
    5842 2023-12-30               0
    5843 2023-12-31               0

    [108 rows x 2 columns]

## 活用例

いま、当日の株価を取得したいとします。

J-Quantsでは日次の株価四本値を配信する株価APIが提供されています。以下のようにクエリストリングに日付を与えると、その日に東証に上場している全銘柄の株価調整前・株価調整後の株価四本値（前場・後場・日通し）と取引高・売買代金を取得することができます。毎日17:00頃にその日の株価データが追加されます。

``` python
resp = requests.get(
    "https://api.jquants.com/v1/prices/daily_quotes",
    params={"date": "2023-06-28"},
    headers={"Authorization": f"Bearer {ID_TOKEN}"}
)
daily_quotes = resp.json()["daily_quotes"]
```

中身はこんな感じです。

    {'Date': '2023-06-28',
     'Code': '13010',
     'Open': 3615.0,
     'High': 3650.0,
     'Low': 3610.0,
     'Close': 3650.0,
     'UpperLimit': '0',
     'LowerLimit': '0',
     'Volume': 20300.0,
     'TurnoverValue': 73805000.0,
     'AdjustmentFactor': 1.0,
     'AdjustmentOpen': 3615.0,
     'AdjustmentHigh': 3650.0,
     'AdjustmentLow': 3610.0,
     'AdjustmentClose': 3650.0,
     'AdjustmentVolume': 20300.0,
     'MorningOpen': 3615.0,
     'MorningHigh': 3630.0,
     'MorningLow': 3610.0,
     'MorningClose': 3630.0,
     'MorningUpperLimit': '0',
     'MorningLowerLimit': '0',
     'MorningVolume': 5800.0,
     'MorningTurnoverValue': 21005500.0,
     'MorningAdjustmentOpen': 3615.0,
     'MorningAdjustmentHigh': 3630.0,
     'MorningAdjustmentLow': 3610.0,
     'MorningAdjustmentClose': 3630.0,
     'MorningAdjustmentVolume': 5800.0,
     'AfternoonOpen': 3630.0,
     'AfternoonHigh': 3650.0,
     'AfternoonLow': 3620.0,
     'AfternoonClose': 3650.0,
     'AfternoonUpperLimit': '0',
     'AfternoonLowerLimit': '0',
     'AfternoonVolume': 14500.0,
     'AfternoonTurnoverValue': 52799500.0,
     'AfternoonAdjustmentOpen': 3630.0,
     'AfternoonAdjustmentHigh': 3650.0,
     'AfternoonAdjustmentLow': 3620.0,
     'AfternoonAdjustmentClose': 3650.0,
     'AfternoonAdjustmentVolume': 14500.0}

その日が営業日の場合は当日の全銘柄の株価を全て取得したいという場合、営業日の判定に取引カレンダーAPIが使えますから、以下のような感じで書けます。なお、無料プランでは12週間前までのデータとなりますので、加入されているプランが無料プランですと当日の株価は取得できないことにご留意ください。

``` python
import datetime
import json

import requests

# リフレッシュトークンの取得
resp = requests.post(
    "https://api.jquants.com/v1/token/auth_user",
    data=json.dumps({"mailaddress": MAIL_ADDRESS, "password": PASSWORD})
)
REFRESH_TOKEN = resp.json()["refreshToken"]

# IDトークンの取得
resp = requests.post(
    "https://api.jquants.com/v1/token/auth_refresh",
    params={"refreshtoken": REFRESH_TOKEN}
)
ID_TOKEN = resp.json()["idToken"]

# 営業日の判定
TODAY = datetime.datetime.now().date().strftime("%Y-%m-%d")
# paramsの指定によって、取引カレンダーをTODAYの日だけ取ってくる
resp = requests.get(
    "https://api.jquants.com/v1/markets/trading_calendar",
    params={"from": TODAY, "to": TODAY},
    headers={"Authorization": f"Bearer {ID_TOKEN}"}
)
calendar = resp.json()["trading_calendar"]

# 現物株は祝日取引を行っていないため、営業日 ("1") か半日立会日 ("2") が株価を取得する日
is_TSE_business_day = calendar[0]["HolidayDivision"] in ["1", "2"]
# 営業日ならその日の株価を取得してJSONで出力する
if not is_TSE_business_day:
    pass
else:
    resp = requests.get(
        url="https://api.jquants.com/v1/prices/daily_quotes",
        params={"date": TODAY},
        headers={"Authorization": f"Bearer {ID_TOKEN}"}
    )
    daily_quotes = resp.json()["daily_quotes"]
```

このコードをcronなどで毎日18:00頃に実行すれば、営業日かの判定をcronなどのスケジューラ側で行うことなく株価を取得することができます。あとは株価をファイルで出力するなりDBに入れるなりすればよいでしょう。
なお、コードを実行する日（`TODAY`）が営業日であればその日の株価を取得するというロジックですので、このコードを毎日25:00など日付が変わった後に実行すると株価を正常に取得できないことがあります。

実運用に乗せる際は、以下を実装することを推奨します。

- エラーハンドリング
  - 通信エラーの際のリトライ
    - 個人的にはtenacityというライブラリがお気に入りです
  - エラー時の通知（Slackなど）
- ログファイルの出力
  - エラー時に原因を調査できるよう、適切にログを出力する必要があります

## おわりに

この記事では取引カレンダーAPIを用いて株価をバッチ取得する方法についてご紹介いたしました。

本記事中ではrequestsを用いて一から実装しましたが、[jquants-api-client-python](https://github.com/J-Quants/jquants-api-client-python)というPythonライブラリもありますので、ぜひご活用ください。

また、J-Quants APIを使い方や分析例をColab
Notebookで作成しているものが、このリポジトリの[examples](https://github.com/J-Quants/jquants-api-client-python/tree/main/examples)にあります！お時間がございましたら是非覗いてみてください。

## ディスクレーマー

当記事は、J-Quantsを利用した金融データ分析に関する技術的事項の共有を目的としたものであり、株式などの投資商品への投資の推奨を目的とするものではありません。また、本記事の内容をもとに投資等を行った結果損害等が生じた場合においても、一切の責任を負わないものとします。

## 参考リンク

- [J-Quants](https://jpx-jquants.com/)
- [jquants-api-client-python](https://github.com/J-Quants/jquants-api-client-python)