見出し画像

J-Quants API 銘柄コード指定での株価情報の取得

ご注意:2023/04/03 より正式に運用が開始されました。正式版の接続URLは、β版と異るので読み替えて下さい。また、仕様もいろいろアップデートされている様です。
なお β版の運用は、2023/04/10で終了とのことです。


株価情報取得の仕様

仕様のページは、こちらになります。

日足データが取得できる様です。期間は2017年1月からで、5年超のデータが取得できます。これは有難いですね。

1銘柄の銘柄コードを指定して、全期間または、from to の期間指定のどちらもできる様です。

全銘柄のダウンロードは、日付け指定で期間指定はできないようです。
銘柄リストを組み合わせれば、全銘柄で全期間の取得ができてしまいますね(汗
サーバーの処理と通信帯域に負荷がかかるでしょうから、1回実施したらあとは日別に差分の取得・・・でしょうか。

サンプルコード

公式サンプルコードを見てみると、財務情報の取得と同じ形式のようです。

import requests
import json

idToken = "YOUR idToken"
headers = {'Authorization': 'Bearer {}'.format(idToken)}
r = requests.get("https://api.jpx-jquants.com/v1/prices/daily_quotes?code=86970", headers=headers)
r.json()

URLの https://api.jpx-jquants.com/v1/ 以降が、
株価 prices/daily_quotes?
財務 fins/statements?
と目的別に指定されています。

? 以降は、
株価 code=86970", headers=headers)
財務 code=86970", headers=headers)
まったく同じです。

流し込むURLを変えれば、財務情報で利用したAPIに問い合わせる部分が流用出来そうです。

# ---------------------------------------------
# 機能 : J-Quants API に株価情報、財務情報を問い合わせる。
# 引数1: IDトークンの値(string型)
# 引数1: 問合せURL(string型)
# 返値 : 財務データ(List型)
# ---------------------------------------------
def func_query_api(str_idToken, str_url):
    headers = {'Authorization': 'Bearer {}'.format(str_idToken)}
    resp = requests.get(str_url, headers=headers)
    
    if resp.status_code == 200 :
        # 正常に銘柄情報を取得
        return resp
    else :
        # 正しく取得できなかった場合
        # --- Message -----------------------------2022.10.03--
        # エラーで、403の場合のmessageは'M'と大文字になっているので注意。
        # エラーは400,401,403と3種類有る
        # 400: 未確認。これは何で起こるのしょう。
        # 401: {"message":"The incoming token has expired"}
        # 403: {"Message":"Access Denied"}
        # -----------------------------------------
        print('status_code:', resp.status_code)
        if resp.status_code == 400 or resp.status_code == 401 :
            print('message    :', dic_resp.get('message'))
        elif resp.status_code == 403 :
            print('message    :', dic_resp.get('Message'))
            print(resp.text)
        else :
            print(resp.text)
        quit()  # 終了


Bearer認証

公式サンプルコードの5行目にある Bearer が何を意味するか分からず検索してみました。
上位の結果を読むとRFCで定義された「Authorizationヘッダを用いた認証」というもののようです。不勉強なもので初めて知りました。

問い合わせ結果 '400' が再現できず、どういう場合に起こるのか分からなかったのですが、このBearer認証の解説ページによると
「リクエストパラメータが不正なケース(400 Bad Request)」
のようです。一つ賢くなれました。

コード

基本的には財務情報の取得と同じなので、ハマることはありませんでした。
一つだけ挙げるとすれば、CodeとDate以外の
4 Open 始値(調整前) Number 1683
5 Close 終値(調整前) Number 1692
6 Low 安値(調整前) Number 1673
7 High 高値(調整前) Number 1704
8 Volume 取引高(調整前) Number 1646200
9 TurnoverValue 取引代金 Number 2778858800
10 AdjustmentFactor 調整係数 Number 0.5(株式分割1:2の例)
11 AdjustmentOpen 調整済み始値 Number 841.5
12 AdjustmentClose 調整済み終値 Number 846
13 AdjustmentLow 調整済み安値 Number 836.5
14 AdjustmentHigh 調整済み高値 Number 852
15 AdjustmentVolume 調整済み取引高 Number 3292400
のvalueは ” で囲まれておらず、数値として読み込まれるため、 str関数でstring に変換する必要がありました。

Githubにアップしました。セットで使うリフレッシュトークン取得とIDトークン取得も一緒にリンクしておきます。

Github
J-Quants API 銘柄コード指定で株価情報を取得 こちら
J-Quants API リフレッシュトークンの取得 こちら
J-Quants API IDトークンの取得 こちら

ファイル名は、jq_api_get_price_code.py です。
使い方は
./jq_api_get_price_code.py code=[12345]
です。

# -*- coding: utf-8 -*-
# 2022.10.13 coded by yo.
# MIT License
# Python 3.6.8 / centos7.4

import urllib3
import requests
import datetime
import json
import sys


# J-Quants API 株価情報取得項目
# 
# 1 変数名	説明	型	例
# 2 Code	銘柄コード	String	86970
# 3 Date	日付	String	20170113
# 4 Open	始値(調整前)	Number	1683
# 5 Close	終値(調整前)	Number	1692
# 6 Low	安値(調整前)	Number	1673
# 7 High	高値(調整前)	Number	1704
# 8 Volume	取引高(調整前)	Number	1646200
# 9 TurnoverValue	取引代金	Number	2778858800
# 10 AdjustmentFactor	調整係数	Number	0.5(株式分割1:2の例)
# 11 AdjustmentOpen	調整済み始値	Number	841.5
# 12 AdjustmentClose	調整済み終値	Number	846
# 13 AdjustmentLow	調整済み安値	Number	836.5
# 14 AdjustmentHigh	調整済み高値	Number	852
# 15 AdjustmentVolume	調整済み取引高	Number	3292400


# ---------------------------------------------
# 機能: コマンドライン入力のパラメーターをチェックする。
# 引数1: コマンドライン入力のパラメーター(list型)
# 引数2: 出力ファイル名(string型)
# 返値: なし
# ---------------------------------------------
def func_parse_parameter(sys_argv):
    if len(sys_argv) == 2 :
        if sys_argv[1] == '-h' \
            or sys_argv[1] == '--help':
            print(sys_argv[0], ' code=[12345]')
            print()
            exit()
        else :
            pass
        
    elif len(sys_argv) == 1 or len(sys_argv) > 5 :
            print(sys_argv[0], ' code=[12345]')
            exit()
    else:
        pass


# ---------------------------------------------
# 機能: コマンドライン入力のパラメーターを取得する。
# 引数1: コマンドライン入力のパラメーター(list型)
#
# 返値: 引数セット(辞書型)
# ---------------------------------------------
def func_get_parameter(sys_argv) :
    str_start = ''
    str_end = ''
    date_start = ''
    date_end = '' 
    for i in range(len(sys_argv)) :
        if i > 0 :
            if sys_argv[i][:5] == 'code=' :
                if len(sys_argv[i][5:]) == 4 :
                    str_code = sys_argv[i][5:] + '0'
                elif len(sys_argv[i][5:]) == 5 :
                    str_code = sys_argv[i][5:]
                else :
                    print('銘柄コードは4桁又は5桁で指定して下さい。')
                    print('例: code=1301 又は code=13010')
                    exit()
            elif sys_argv[i][:5] == 'from=' :
                if len(sys_argv[i][5:]) == 8 :
                    str_start = sys_argv[i][5:]
                else :
                    print('開始日付けを正しく指定してください。')
                    print('form=[yyyymmdd] to=[yyyymmdd]')
                    exit()
            elif sys_argv[i][:3] == 'to=' :
                if len(sys_argv[i][3:]) == 8 :
                    str_end = sys_argv[i][3:]
                else :
                    print('終了日付けを正しく指定してください。')
                    print('form=[yyyymmdd] to=[yyyymmdd]')
                    exit()
            else :
                print('入力パラメーターの形式が正しくありません。')
                print('sys_rgv[', i,']:', sys.argv[i])
                print('日付け指定のみできます。')
                print('form=[yyyymmdd] to=[yyyymmdd]')
                exit()
    if len(str_code) == 4 or len(str_code) == 5:
        pass
    elif len(str_start) == 8 and len(str_end) == 8 :
        date_start = datetime.datetime.strptime(str_start, '%Y%m%d')
        date_end   = datetime.datetime.strptime(str_end, '%Y%m%d')
        if date_start > date_end :
            date_tmp = date_start
            date_start = date_end
            date_end = date_tmp

    elif len(str_start) == 8 and len(str_end) == 0 :
        date_start = datetime.datetime.strptime(str_start, '%Y%m%d')
        date_end   = date_start
    elif len(str_start) == 0 and len(str_end) == 8 :
        date_end = datetime.datetime.strptime(str_end, '%Y%m%d')
        date_start = date_end
    else :
        print('入力パラメーターの形式が正しくありません。')
        print('sys_rgv[', i,']:', sys.argv[i])
        print('code=[12345]')
        exit()
    
    dic_argv = {'code=':str_code, 'from=':date_start, 'to=':date_end}
    return dic_argv



# ---------------------------------------------
# 機能 : 起動したディレクトリでファイルに書き込む。
# 引数1: 出力ファイル名(string型)
# 引数2: 出力文字列(string型)
# 返値 : 無し
# ---------------------------------------------
def func_read_from_file(str_fname):
    str_read = ''
    try:
        with open(str_fname, 'r', encoding = 'utf_8') as fin:
            while True:
                line = fin.readline()
                if not len(line):
                    break
                str_read = str_read + line
        return str_read

    except IOError as e:
        print('Can not read!!!')
        print(type(e))



# ---------------------------------------------
# 機能 : 起動したディレクトリでファイルに書き込む。
# 引数1: 出力ファイル名(string型)
# 引数2: 出力文字列(string型)
# 返値 : 無し
# ---------------------------------------------
def func_write_to_file(str_fname_output, str_text):
    try:
        with open(str_fname_output, 'w', encoding = 'utf_8') as fout:
            fout.write(str_text)     

    except IOError as e:
        print('Can not write!!!')
        print(type(e))




# ---------------------------------------------
# 機能 : ファイルに保存してあるIDトークンを読み出す。
# 引数1: IDトークン保存ファイル名(string型)
# 返値 : IDトークン(string型)
# 備考 : IDトークン保存ファイルのデータ形式は、
#   {"time_idtoken":"value","idToken":"value"}
# ---------------------------------------------
def func_read_idtoken(str_fname_idtoken):
    # IDトークンの読み出し
    str_id_json = func_read_from_file(str_fname_idtoken)

    dic_idtoken = json.loads(str_id_json)
    str_idtoken = dic_idtoken.get('idToken')
    # IDトークンを取得できない場合
    if str_idtoken is None :
        print('IDトークンが取得できません。')
        quit()

    # IDトークンの取得時間を表示
    str_time_idtoken = dic_idtoken.get('time_idToken')
    time_idtoken = datetime.datetime.strptime(str_time_idtoken, '%Y-%m-%d %H:%M:%S.%f')
    print('[ id token ]')
    print('time stamp :', time_idtoken)

    # IDトークンの有効期限を表示(有効期限24時間)
    span_expire = datetime.timedelta(days=1)
    time_expire = time_idtoken + span_expire
    print('expiry date:', time_expire)
    time_remain = time_expire - datetime.datetime.now()
    print('remaining time:', time_remain)
    if time_remain > datetime.timedelta(days=0) :
        print('IDトークンの有効期間は24時間です。')
    else :
        print('IDトークンは、無効です。有効期間を過ぎました。')
        
    print()
    return str_idtoken





# ---------------------------------------------
# 機能 : 出力ファイルにタイトル行を書き込む。
# 引数1: 出力ファイル名(string型)
# 返値 : 無し
# ---------------------------------------------
def func_write_title(str_fname_output):
    # csvで保存
    try:
        with open(str_fname_output, 'w', encoding = 'utf_8') as fout:
            # 1行目 タイトル行
            str_text = ''
            str_text = str_text + '"' + '銘柄コード' + '"' + ','	# 1
            str_text = str_text + '"' + '日付' + '"' + ','	# 2
            str_text = str_text + '"' + '始値(調整前)' + '"' + ','	# 3
            str_text = str_text + '"' + '終値(調整前)' + '"' + ','	# 4
            str_text = str_text + '"' + '安値(調整前)' + '"' + ','	# 5
            str_text = str_text + '"' + '高値(調整前)' + '"' + ','	# 6
            str_text = str_text + '"' + '取引高(調整前)' + '"' + ','	# 7
            str_text = str_text + '"' + '取引代金' + '"' + ','	# 8
            str_text = str_text + '"' + '調整係数' + '"' + ','	# 9
            str_text = str_text + '"' + '調整済み始値' + '"' + ','	# 10
            str_text = str_text + '"' + '調整済み終値' + '"' + ','	# 11
            str_text = str_text + '"' + '調整済み安値' + '"' + ','	# 12
            str_text = str_text + '"' + '調整済み高値' + '"' + ','	# 13
            str_text = str_text + '"' + '調整済み取引高' + '"' + '\n'	# 14

            # タイトル2行目 英文        
            str_text = str_text + '"' + 'Code' + '"' + ','	# 
            str_text = str_text + '"' + 'Date' + '"' + ','	# 
            str_text = str_text + '"' + 'Open' + '"' + ','	# 
            str_text = str_text + '"' + 'Close' + '"' + ','	# 
            str_text = str_text + '"' + 'Low' + '"' + ','	# 
            str_text = str_text + '"' + 'High' + '"' + ','	# 
            str_text = str_text + '"' + 'Volume' + '"' + ','	# 
            str_text = str_text + '"' + 'TurnoverValue' + '"' + ','	# 
            str_text = str_text + '"' + 'AdjustmentFactor' + '"' + ','	# 
            str_text = str_text + '"' + 'AdjustmentOpen' + '"' + ','	# 
            str_text = str_text + '"' + 'AdjustmentClose' + '"' + ','	# 
            str_text = str_text + '"' + 'AdjustmentLow' + '"' + ','	# 
            str_text = str_text + '"' + 'AdjustmentHigh' + '"' + ','	# 
            str_text = str_text + '"' + 'AdjustmentVolume' + '"' + '\n'	# 
            
            fout.write(str_text)
            fout.close

    except IOError as e:
        print('Can not write!!!')
        print(type(e))



# ---------------------------------------------
# 機能 : J-Quants API に株価情報、財務情報を問い合わせる。
# 引数1: IDトークンの値(string型)
# 引数1: 問合せURL(string型)
# 返値 : 財務データ(List型)
# ---------------------------------------------
def func_query_api(str_idToken, str_url):
    headers = {'Authorization': 'Bearer {}'.format(str_idToken)}
    resp = requests.get(str_url, headers=headers)
    
    if resp.status_code == 200 :
        # 正常に銘柄情報を取得
        return resp
    else :
        # 正しく取得できなかった場合
        # --- Message -----------------------------2022.10.03--
        # エラーで、403の場合のmessageは'M'と大文字になっているので注意。
        # エラーは400,401,403と3種類有る
        # 400: 未確認。これは何で起こるのしょう。
        # 401: {"message":"The incoming token has expired"}
        # 403: {"Message":"Access Denied"}
        # -----------------------------------------
        print('status_code:', resp.status_code)
        if resp.status_code == 400 or resp.status_code == 401 :
            print('message    :', dic_resp.get('message'))
        elif resp.status_code == 403 :
            print('message    :', dic_resp.get('Message'))
            print(resp.text)
        else :
            print(resp.text)

        quit()  # 終了




# =============================================
# 機能 : 保存してあるIDトークンを使い、銘柄指定で株価情報を取得し、保存する。
# 引数1: 開始日 書式 code=[12345] 
# 引数1: 開始日 書式 from=[yyyymmdd] 
# 引数3: 終了日 書式 to=[yyyymmdd] 
# 返値 : 無し
# 備考 : 出力のファイル名、IDトークンを保存してあるファイル名は適宜変更してください。
#       1銘柄ごとの取得になります。
#       財務データが複数返されます(複数ある場合)。
# ---------------------------------------------

# IDトークン保存ファイル名
str_fname_idtoken = 'jq_idtoken.json'

# IDトークンの読み出し
str_idtoken = func_read_idtoken(str_fname_idtoken)
# IDトークン保存ファイルのデータ形式は、
#   {"time_idtoken":"value","idToken":"value"}

# 入力パラメーターの書き出し
for i in range(len(sys.argv)):
    print('sys.argv[', i,']:', sys.argv[i])
print()

# パラメーターチェック
func_parse_parameter(sys.argv)

dic_argv = func_get_parameter(sys.argv)
str_code = dic_argv.get('code=')
date_from = dic_argv.get('from=')
date_to = dic_argv.get('to=')

# 出力ファイル名
str_fname_output = 'jq_price_' + str_code + '.csv'

# ファイルにタイトル行を書き込む
func_write_title(str_fname_output)

# 財務情報取得
str_parameter = 'code=' + str_code
str_url = 'https://api.jpx-jquants.com/v1/prices/daily_quotes?' + str_parameter #+ '"'
resp = func_query_api(str_idtoken, str_url)
dic_resp = json.loads(resp.text)    # jsonをパースして辞書型に変換
list_resp = dic_resp.get('daily_quotes')       # "info"のvalueを取り出す。リスト型。

# 日付順にソート
list_resp = sorted(list_resp, key=lambda x:x['Date'])

try :
    if len(list_resp) > 0 :
        with open(str_fname_output, 'a', encoding = 'utf_8') as fout:
            # データ行
            for i in range(len(list_resp)):
                str_text = ''
                str_text = str_text + '"' + list_resp[i].get('Code') + '",'	# 
                str_text = str_text + '"' + list_resp[i].get('Date') + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('Open')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('Close')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('Low')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('High')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('Volume')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('TurnoverValue')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('AdjustmentFactor')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('AdjustmentOpen')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('AdjustmentClose')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('AdjustmentLow')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('AdjustmentHigh')) + '",'	# 
                str_text = str_text + '"' + str(list_resp[i].get('AdjustmentVolume')) + '"\n'	# 

                fout.write(str_text)     
        fout.close
        print('outpu file:', str_fname_output)
        print(str_parameter,' data数:', i + 1 )  # 0からカウントしているので1加算
    else :
        print(str_parameter,' data数: 0')
              
except IOError as e:
    print('Can not write!!!')
    print(type(e))

最後にこっそり宣伝です(笑
前職で関係のあったe支店APIでは、20年分の日足データが取得できます。簡単な説明記事をあげてあります。
https://note.com/alfa_246/n/n13d232f82c72

本記事上の私が作成したプログラムは自由にご使用ください。但し、この記事のソフトウェアを使用したことによって生じたすべての障害・損害・不具合等に関して、私と私の関係者および私の所属するいかなる団体・組織とも、一切の責任を負いません。各自の責任においてご使用ください。

この記事が気に入ったらサポートをしてみませんか?