J-Quants の分足は、約定が無かった分のレコードを返しません。だから1分足は歯抜けになります。これを詰めて並べると、飛んでいることが見えなくなります。実測すると、隣り合う2本のうち本当に1分なのは 69.1% だけでした。
- 1日327分すべてそろう銘柄・日は 3.22%。中央値は69分
- 隣り合う2本が本当に1分なのは 69.14%。30.86% は1分ではない
- 「5本後」がちょうど5分後なのは 42.56%。平均は 12.3分
- 「20本後」は平均 39.1分。本来の約2倍
- 隣り合う2本が 最大326分離れていた例もあります
公式の記述
「当APIは取引のティックデータを1分単位で集約したデータを提供しており、その1分間に取引が存在しない時間帯のデータは返却値に含まれません」
つまり歯抜けです。ここまでは仕様に書いてあります。問題はどれくらい抜けるのか、そして抜けていることに気づけるのかです。
まず、上限は327分
東証の立会時間は前場と後場に分かれ、さらに15:25〜15:29 の5分は板寄せ中で約定が成立しません。数えると1日の上限は327本です。
# 立会時間の格子を作る(327分)
grid = (list(range(9*60, 11*60+30 + 1)) + # 09:00〜11:30 151分
list(range(12*60+30, 15*60+24 + 1)) + # 12:30〜15:24 175分
[15*60 + 30]) # 15:30(大引け) 1分
assert len(grid) == 327
# 分 → 格子の位置。これで昼休みも 15:25〜15:29 も「穴」に数えずに済む
idx = {m: i for i, m in enumerate(grid)}昼休み(11:30→12:30)を「穴」に数えてはいけません。
素直に分の差を取ると、両方の場で商いのある銘柄すべてに60分の穴が1つあることになります。実際、最初に測ったときこれを数えてしまい、「31〜60分の飛びが1.05%」と出ました。格子に直すと0.66%です。15:25〜15:29 も同じで、そもそも足が存在しません。
1日に何分そろうのか
14営業日・のべ58,761件(銘柄×日)で数えました。
| 約定があった分(上限327分) | 件数 | 割合 |
|---|---|---|
| 327分すべてそろっている | 1,890 | 3.22% |
| 90%以上(294分超) | 4,923 | 8.38% |
| 75〜90% | 3,206 | 5.46% |
| 50〜75% | 6,819 | 11.60% |
| 10〜50% | 21,200 | 36.08% |
| 10%未満(32分以下) | 20,723 | 35.27% |
すきまなく埋まっているのは 3.22% だけです。そして3件に1件は、1日のうち32分以下しか約定していません。
厚さで見るとはっきりする
| 売買代金 | 約定のあった分の中央値 | 327分そろう率 | 半分未満の日 |
|---|---|---|---|
| 最も薄い | 7分 | 0.0% | 99.9% |
| 薄い | 24分 | 0.0% | 99.4% |
| 中 | 68分 | 0.0% | 95.1% |
| 厚い | 152分 | 0.0% | 57.0% |
| 最も厚い | 306分 | 16.1% | 5.2% |
最も厚い層でも、全部そろうのは16.1%です。「大型株なら埋まっている」とは言えません。
★ 詰めて描くと、飛びが見えなくなる
ここが本題です。歯抜けの足をそのまま左から並べると、画面上は連続した足に見えます。隣り合う2本が、立会時間で実際に何分離れているかを数えました。
# 隣り合う2本が、立会時間で何分ぶん離れているか
bars["i"] = bars["Min"].map(idx)
gap = bars["i"].diff()
print(f"本当にとなり合わせ {(gap == 1).mean() * 100:.1f}%")
# → 69.1%| 隣り合う2本の実際の間隔 | 組数 | 割合 |
|---|---|---|
| 1分(本当にとなり) | 4,462,744 | 69.143% |
| 2分 | 765,991 | 11.868% |
| 3〜5分 | 694,637 | 10.762% |
| 6〜30分 | 463,317 | 7.178% |
| 31〜60分 | 42,652 | 0.661% |
| 1時間超 | 25,027 | 0.388% |
画面で隣り合っている2本のうち、30.86% は1分ではありません。
そして8.23% は5分より離れています。チャートを見て「ここで反発した」と読んだ2本が、実際には2時間空いていることがあります。
★ 「5本後に手仕舞い」は、5分後ではない
見た目の話だけではありません。本数で数えるコードは、全部これに当たります。「5本後に手仕舞い」が実際には何分後になるか、数えました。
| 「5本後」(本来5分) | 「20本後」(本来20分) | |
|---|---|---|
| ちょうどその分数だった | 42.56% | 30.11% |
| 中央値 | 6分 | 25分 |
| 平均 | 12.3分 | 39.1分 |
| 本来の3倍超だった割合 | 6.75%(30分超) | 3.89%(120分超) |
| 最大 | 326分 | 326分 |
「20本後」は、平均すると39.1分後でした。本来の約2倍です。移動平均の「直近20本」も同じで、20分ぶんの平均を計算しているつもりが、実際は39分ぶんになります。
薄い銘柄では、これがもっと極端になります。1日に7分しか約定しない銘柄で「直近20本」を計算すると、3営業日ぶんの平均を見ていることになります。
どうするか
- 足に実時刻を必ず持たせる。「i番目の足」だけで持ち回すと、この問題は最後まで見えません
- 本数ではなく時刻で数える。「5本後」ではなく「5分後以降の最初の足」
- 埋めるか空けたままかを決めて、決めたほうを結果に書く。どちらでもいいが、書いていないと後から再現できません
- 描くときは飛びに印を付ける。縦線でも「35分」の文字でもいい。詰めたまま何も出さないのが一番悪い
- 約定の少ない日は最初から外す。「立会の半分も約定が無い日は検証に使わない」と決めておく
# ★ 本数で数えない。時刻で数える
entry_i = bars["i"].iloc[k]
exit_rows = bars[bars["i"] >= entry_i + 5] # 「5分後以降の最初の足」
if exit_rows.empty:
skipped += 1 # 引けまでに来なかった
else:
ctx.close()
# 飛んでいる足を描くときは、印を付ける
if gap > 1:
draw_gap_marker(f"{gap}分")「5分後以降の最初の足」にすると、引けまでに来ない場合が出ます。
そのとき黙って落とすと、薄い日だけが消えて成績が良く見えます。落とした件数を必ず出してください。
まとめ
1分足の歯抜けは仕様に書いてあります。それでも見落とすのは、詰めて並べると見えなくなるからです。
実測では、隣り合う2本のうち3割は1分ではなく、「20本後」は平均39分後でした。どちらもコードの上では何も間違っていないのに、意味だけが静かにずれています。
本数で数えるのをやめて、時刻で数える。直し方はそれだけです。
この記事の出どころ
- データ:J-Quants API V2 の bulk ティックから組んだ1分足。Light プラン+分足・ティックアドオン
- 期間:2026-09-01 〜 2026-09-18 の14営業日
- 対象:のべ58,761件(銘柄×日)。隣り合う足の組 6,454,368/「5本後」622万組/「20本後」551万組
- 数え方:立会327分(前場151+後場176)の格子に直してから間隔を取り、昼休みと 15:25〜15:29 を穴に数えないようにした。格子の外に落ちた分は0件で、実際に15:25〜15:29 の約定は無かった
- 環境:Windows 11 / Python 3.12 / pandas 3.0 / numpy 2.5 / pyarrow 21
- 測定日:2026年9月21日
- 注意:掲載したのは集計結果です。取得したデータそのものは配布していません