ログオフ
開発2026-06-04

J-Quants APIの無料プランで詰まった2つのバグ:日付範囲とasyncpgの型変換

範囲外の日付で400が空リストになる・asyncpgがDATE型の文字列を変換しない。どちらも公式ドキュメントには明記がなかった。

J-Quants API(日本株の株価データAPI)の無料プランでデータ取得を実装したときに踏んだ2つのバグ。どちらも挙動が直感に反する。

バグ1:範囲外の日付で400が「空リスト」として処理される

事象

無料プランで取得できるデータには範囲制限がある(2026年時点で「直近12週を除いた過去2年分程度」)。この範囲外を to_date に指定するとAPIは400を返すが、エラーに気づかずデータ0件として処理が正常終了してしまう。

# ❌ 問題のあるコード
response = requests.get(url, headers=headers, params=params)
data = response.json().get("daily_quotes", [])
# 400が返っても .get() のデフォルト値 [] が使われ、0件扱いになる

原因

レスポンスのステータスコードを確認せず、ボディを直接 .get("daily_quotes", []) で読んでいるため。400のとき daily_quotes キーが無く、デフォルトの空リストにフォールバックする。データが取れていないのに例外も立たないので、原因特定に時間がかかる。

解決策

ステータスコードを先に検証する。

# ✅ 4xx/5xx で例外を投げる
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
data = response.json().get("daily_quotes", [])

raise_for_status() を入れれば400が例外として浮上する。取得可能な日付範囲は実装前に確認しておく。

バグ2:asyncpg が DATE 型の文字列を自動変換しない

事象

J-Quantsが返す日付("2024-03-15" のような文字列)を、asyncpg 経由でPostgreSQLの DATE 型カラムに保存しようとすると失敗する。

# ❌ 動かないコード
await conn.execute(
    "INSERT INTO stock_prices (date, close) VALUES ($1, $2)",
    row["Date"],   # "2024-03-15" という文字列
    row["Close"],
)
# asyncpg.exceptions.DataError: invalid input for query argument $1

原因

asyncpg は型に厳格で、DATE 型には Python の datetime.date オブジェクトを要求する。文字列からの暗黙変換はしない。psycopg2 は暗黙変換するため、そこから移行してきた場合に気づきにくい。

解決策

渡す前に datetime.date へ変換する。

from datetime import datetime

await conn.execute(
    "INSERT INTO stock_prices (date, close) VALUES ($1, $2)",
    datetime.strptime(row["Date"], "%Y-%m-%d").date(),
    row["Close"],
)

まとめて処理するなら、変換をパイプラインの手前に寄せると見通しがよい。

def parse_quote(row: dict) -> dict:
    return {
        "date": datetime.strptime(row["Date"], "%Y-%m-%d").date(),
        "close": row["Close"],
    }

quotes = [parse_quote(r) for r in raw_data]

補足:無料プランのデータ範囲

2026年時点の目安で、取得できるのは「2024年3月〜2026年3月(直近12週除外)」程度。この範囲は時間とともに動く。実装前に to_date へ今日の日付を入れてリクエストし、400が返るか確認しておくと混乱を防げる。

関連記事

開発 一覧へ