PとMでJQから金融データ取得

日本取引所JPXが2023年4月から株価や企業財務などの金融データのAPI提供を始めました。
[J-Quants](https://jpx-jquants.com/)

かつて筆者はMATLABで株価や指標データの取得を行ってテクニカル解析を行っていましたが（過去記事：[MATLABで株価データ自動取得](https://qiita.com/SacredTubes/items/4938157794e840cbc6e5)）度重なるWebページの仕様変更があったり、時間制限がかかったりしてメンテに手間がかかりすぎるため運用を諦めていました。

J-Quantsのサービスは若干のお金がかかるものの（無料プランもあり）、安定した運用ができそうです。
またAPI仕様およびPythonのサンプルコードが公開されているので、コード記述を試行錯誤して苦労することは無さそうです。

世の中にはPythonコードのサンプルが提供されていることが多くそれをそのまま使うときは便利なのですが、細かいデータのクレンジングなどを行おうとすると、やや面倒です。
筆者がPython初心者でMATLABにｈ慣れているため、慣れの問題も多分にあるとは思いますが。（pandasなどを使いこなせば簡単にできるのかもしれません・・・）


　
今回はPythonでJ-Quantsからデータ取得、MATLABでデータクレンジングや可視化を行いましたので、その一例を示します。

## Pythonモジュール作成
J-QuantsのWebページにあるPythonのサンプルコードを使って、データ取得のモジュールを作成しました。
なお、ID Tokenを取得するにはJ-Quantsでアカウントを作成しておく必要があります。Tokenの取得方法はこちらの[クイックスタートガイド](https://jpx.gitbook.io/j-quants-ja/api-reference)に記載があります。

以下にPythonコードを示します。
※▶をクリックすると折りたたまれたコード表示を展開できます。

<details><summary>Pythonモジュール: JQ.py</summary>

```Python:PQ.py
import json
import requests

def getIDToken():
    # J-Quantsの認証を行い、idTokenを取得する
    import tomllib
    
    API_URL = "https://api.jquants.com"
    # リフレッシュトークン及びidTokenを取得
    config_path = ("jquants-api-pass.toml")
    with open(config_path, mode="rb") as f:
        ret = tomllib.load(f)["jquants-api-client"]

    USER_DATA = {k: v for k, v in ret.items()}
    # refresh token取得
    try:
        res = requests.post(f"{API_URL}/v1/token/auth_user", data=json.dumps(USER_DATA))
        refresh_token = res.json()['refreshToken']
    except:
        print("RefreshTokenの取得に失敗しました。")
    else:
        # id token取得
        try:
            res = requests.post(f"{API_URL}/v1/token/auth_refresh?refreshtoken={refresh_token}")
            idToken = res.json()['idToken']
        except:
            print("idTokenの取得に失敗しました。")
        else:
            headers = {'Authorization': 'Bearer {}'.format(idToken)}
            # print("API使用の準備が完了しました。")    
    return idToken


def getQuote(stockCode, startDate, endDate, idToken):
    #日足株価データ取得

    #stockCode = "7203" # 4桁のコードでも5桁のコード72030でもよい。
    #startDate = "2023-03-01"
    #endDate = "2023-04-01"
    #idToken = getIDToken()
    daily_quotes_url = f"https://api.jquants.com/v1/prices/daily_quotes?code={stockCode}&from={startDate}&to={endDate}"
    headers = {"Authorization": f"Bearer {idToken}"}

    # Get daily quotes
    result = requests.get(daily_quotes_url, headers=headers)
    return result


def getFinState(stockCode):
    #財務情報を取得

    idToken = getIDToken()
    headers = {'Authorization': 'Bearer {}'.format(idToken)}

    #finStateData_url = f"https://api.jquants.com/v1/fins/statements?code={stockCode}&date={date}"
    finStateData_url = f"https://api.jquants.com/v1/fins/statements?code={stockCode}"
    result = requests.get(finStateData_url, headers=headers)
    return result

def getListed():
    #上場銘柄一覧を取得

    idToken = getIDToken()
    headers = {'Authorization': 'Bearer {}'.format(idToken)}
    result = requests.get("https://api.jquants.com/v1/listed/info", headers=headers)
    return result

```
</details>

## MATLABからPythonモジュールの呼び出し
MATLABからPythonモジュールおよび関数を呼び出す際の文法は
`>> out = py.moduleFile.funcName(in)
`

今回作成したPythonモジュール名がJQ.py、関数名がgetQuoteなので、
`>> stockPrice = py.JQ.getQuote("7203", "2023-12-25", "2024-02-01")
`
で株価データを取得できます。


## データクレンジングと可視化

J-Quantsから取得したデータには沢山のデータ項目が含まれており、これをテキストデータとして加工しようとするとそれなりに面倒です。

しかし、jsondecode関数を使うと構造体データに整理してくれます。また、Tableデータにするとデータの削除や可視化など後処理がしやすくなります。

例
`>> stockPriceDailyData = jsondecode(string(stockPrice.text));
`


株価データ取得、日本取引所に上場する銘柄リスト取得、財務データ取得を行った例を示します。

※▶をクリックすると折りたたまれたコード表示を展開できます。

<details><summary>取得したデータの処理を行うMATLABコード: JQuantsData.m</summary>

```matlab: JQuantsData.m
%% 株価データ取得 
stockPrice = py.JQ.getQuote("7203", "2023-12-25", "2024-02-01")
stockPriceDailyData = jsondecode(string(stockPrice.text));

%%
date = datetime({stockPriceDailyData.daily_quotes.Date})';
open = [stockPriceDailyData.daily_quotes.Open]';
high = [stockPriceDailyData.daily_quotes.High]';
low = [stockPriceDailyData.daily_quotes.Low]';
close = [stockPriceDailyData.daily_quotes.Close]';
volume = [stockPriceDailyData.daily_quotes.Volume]';

stockData = timetable(date, open, high, low, close, volume);

%%
figure
candleFast(stockData)   % これはカスタム関数
title(stockPriceDailyData.daily_quotes(1).Code(1:4))

%% 銘柄リスト取得
stockList = py.JQ.getListed();
stockListed = jsondecode(string(stockList.text));
stockListedTable=struct2table(stockListed.info);    % table形式に変換
% ProMarketやETFを除外
stockMarket1 = stockListedTable.MarketCodeName == "プライム";
stockMarket2 = stockListedTable.MarketCodeName == "スタンダード";
stockMarket3 = stockListedTable.MarketCodeName == "グロース";
toDelete = not(stockMarket1 | stockMarket2 | stockMarket3);
stockListedTable(toDelete, :) = [];

% Excelに保存
writetable(stockListedTable, ['stockListedTable_' char(datetime('now', 'Format','y_MM_dd')) '.xlsx'])

%% 財務データ取得
temp = py.JQ.getFinState("7203");
temp = jsondecode(string(temp.text));
finStateTable = struct2table(temp.statements);

xData = string(year(finStateTable.DisclosedDate))+"\_"+string(finStateTable.TypeOfCurrentPeriod);

figure
bar(xData, [str2double(finStateTable.NetSales), str2double(finStateTable.OperatingProfit), str2double(finStateTable.Profit)]);
legend('NetSales', 'OpProfit', 'Profit')

```
</details>

次のように取得したデータを可視化できました。

株価の日足データ（7203=トヨタ自動車）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/fdf41645-d2a6-4d26-ac54-4560e4efbf52.png)
（ローソク足チャートはPythonのmplfinanceを使用したほうが良い。Volumeも表示できるし。）

上場銘柄リスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/fc705d2c-7c0c-41f1-879d-aa34dc591165.png)

財務データ（売上、営業利益、最終益）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/34b1e4a3-ba42-4677-8ef8-9ae52cf2c77f.png)

## データ収集スピード
データ取得にかかった時間をMATLABのtic, tocコマンドを使って測定しました。この時間にはMATLABからPythonを呼び出してデータを受け渡す処理も含まれています。

1銘柄1年間の日足データ取得にかかった時間：0.4sec
上場全銘柄リストの取得にかかった時間：5sec
1銘柄の四半期ごと5年間の財務データ取得にかかった時間：0.94sec

以前行っていたWebページから取得するよりも劇的に速いです。
JPX上場全銘柄3900銘柄弱の1年間の株価データ取得が30分弱でできました。

大量のデータを取得する場合はレートリミットがかかる可能性があるため[ページング](https://jpx.gitbook.io/j-quants-ja/api-reference/attention)の処理を追加しておいたほうが良さそう。

手始めにとりあえずやってみましたが、さすがJPXが提供しているだけあって本格的に使えそうで展開が楽しみです。

EOF