J-Quants のティックデータから1分足を組み直し、公式が配っている分足と 468,960本すべてで突き合わせました。高値・安値・出来高・売買代金は最初から合っていたのに、始値と終値だけが合いませんでした。
- 原因は並べ替え。CSVは時刻順に並んでいますが、約定順ではありません
- 同じ銘柄・同じ時刻(マイクロ秒まで一致)の約定が全体の4.1%あります
- 4,201銘柄のうち1,238銘柄(29.5%)で、始値か終値がずれました
- 直し方は
TransactionIdで並べ替えるだけ。それで全項目が完全一致します - その
TransactionIdは、公式のデータ項目表に載っていません
何をしたか
J-Quants の「株価 分足・ティック」アドオンでは、ティック(約定)と分足の両方が配られます。ティックがあるなら分足は自分で作れるはずで、自分で作ったものが公式の分足と一致するかは、検証環境を作るうえで最初に確かめるべきことです。ここが合わないなら、その先どんな成績を出しても意味がありません。
2026年9月18日の1日ぶん、約定431万件から1分足を組み直し、同じ日の分足CSV(468,960本・4,201銘柄)と全件突き合わせました。
素直に書くと、こうなります
import pandas as pd
df = pd.read_csv("equities_trades_20260918.csv.gz")
# 分の単位でまとめる
df["Min"] = df["Time"].str.slice(0, 5) # "09:16:59.833156" -> "09:16"
bars = df.groupby(["Code", "Min"]).agg(
O=("Price", "first"),
H=("Price", "max"),
L=("Price", "min"),
C=("Price", "last"),
Vo=("TradingVolume", "sum"),
)ティックを分でまとめて、最初の値段を始値、最後の値段を終値にする。何も変なことはしていません。
ところが、合いません
| 始値 | 高値 | 安値 | 終値 | 出来高 | 売買代金 | |
|---|---|---|---|---|---|---|
| 上のコードのまま | ずれ 2,100 (0.4478%) | 一致 | 一致 | ずれ 1,509 (0.3218%) | 一致 | 一致 |
本数は 468,960 本でぴったり一致し、こちらにしか無いバー・公式にしか無いバーはゼロ。高値・安値・出来高・売買代金も完全に一致します。始値と終値だけが合いません。
高値と安値が合っているのがヒントです。高値・安値・出来高は順番に関係ない計算(最大・最小・合計)ですが、始値と終値は順番で決まる値だからです。
原因:CSVは時刻順に並んでいるが、約定順ではない
1銘柄を取り出して、並び方を調べました。
| 調べたこと | 結果 |
|---|---|
Time は行順に増えているか | はい |
TransactionId は行順に増えているか | いいえ |
| 同じ時刻(マイクロ秒まで一致)の約定 | 9,857 件(1銘柄で) |
ファイルは時刻順に並んでいます。ただし同じ時刻の約定が複数あるとき、その中での順番が約定順とは限りません。
実例
ある銘柄の 09:16:59.833156 に、約定が2件ありました。
| 値段 | 数量 | TransactionId | |
|---|---|---|---|
| ファイルに並んでいる順 | 6586 | 200 | …924 |
| 6587 | 900 | …921 | |
| TransactionId 順(実際の約定順) | 6587 | 900 | …921 |
| 6586 | 200 | …924 |
公式の分足の終値は 6586 でした。つまり TransactionId 順にした最後が正しい。ファイルの行順をそのまま信じると、この分の終値を 6587 と取り違えます。
直し方は1行です
# 直し方は1行だけ
df = df.sort_values("TransactionId", kind="stable")
df["Min"] = df["Time"].str.slice(0, 5)
bars = df.groupby(["Code", "Min"]).agg(
O=("Price", "first"),
H=("Price", "max"),
L=("Price", "min"),
C=("Price", "last"),
Vo=("TradingVolume", "sum"),
)直すと、どうなったか
| 組み方 | 始値 | 高値 | 安値 | 終値 | 出来高 | 売買代金 |
|---|---|---|---|---|---|---|
| ファイルの行順のまま | ずれ 2,100 | 一致 | 一致 | ずれ 1,509 | 一致 | 一致 |
| TransactionId で並べ替え | 完全一致 | 一致 | 一致 | 完全一致 | 一致 | 一致 |
468,960本すべてが、全項目で一致しました。売買代金の差は最大でも 2.17e-16、浮動小数の丸めの範囲です。
どのくらい起きるのか
- 同じ銘柄・同じ時刻(マイクロ秒まで一致)の約定は、1日431万件のうち 176,381件=全体の4.1%
- 始値か終値がずれた銘柄は 4,201銘柄のうち 1,238銘柄(29.5%)
約3割の銘柄で起きます。流動性の高い一部の銘柄だけの話ではありません。同じ時刻に約定が重なるのは、板が厚い瞬間ならどの銘柄でも起こります。
なぜ見つけにくいのか
- ずれ幅が小さい。多くは1ティック分です。チャートに描いても気づけません
- 高値・安値・出来高は合っている。「データは取れている」と思ってしまいます
Timeで並べ替えても直りません。マイクロ秒まで同じなので、並べ替えても順序は変わりませんTransactionIdは公式のデータ項目表に載っていません。実際にファイルを開くまで、その列があることに気づけません
なぜ放っておけないのか
始値と終値は、バックテストで約定価格として使う値だからです。
「シグナルが出た次の分の始値で買う」「引け値で手仕舞う」——こういうルールを検証するとき、使っているのはまさにその始値・終値です。高値と安値がいくら正確でも、約定価格が1ティックずれていれば、その分だけ成績が変わります。しかもずれる方向は一定ではないので、「安全側に寄っているから大丈夫」とも言えません。
1回のずれは小さくても、売買回数の多い戦略ほど積み上がります。短期の検証では、これは無視できる誤差ではありません。
自分のコードを確かめる
すでにティックから足を作っている方は、これで30秒で確かめられます。
# 自分のコードが大丈夫か、30秒で確かめる
tid = df["TransactionId"].astype("int64")
print("TransactionId は行順に並んでいるか:", tid.is_monotonic_increasing)
print("同じ銘柄・同じ時刻の約定:", df.duplicated(subset=["Code", "Time"]).sum(), "件")1つ目が False で、2つ目が 0 より大きければ、同じずれが入っています。
まとめ
- J-Quants のティックCSVは時刻順に並んでいるが、約定順ではない
- 同じ時刻の約定が 4.1% あり、そこで順序が入れ替わる
- そのまま集約すると始値と終値がずれる(4,201銘柄中 1,238銘柄)
TransactionIdで並べ替えれば完全に一致する- 高値・安値・出来高が合っていても、合っている証拠にはならない
データが手に入るようになったことと、そのデータを正しく使えることは別の話です。当サイトでは、こういう「動くけれど間違っている」箇所を実測で潰していきます。
この記事の出どころ
- データ:J-Quants API「株価 分足・ティック」アドオン(ベースは Light プラン。合計 月7,150円)
- 対象:2026年9月18日/東証上場 4,201銘柄
- 突き合わせた量:ティック 4,313,905件 → 1分足 468,960本(公式の分足CSVと全件比較)
- 環境:Windows 11 / Python 3.12 / pandas 3.0 / pyarrow 25
- 測定日:2026年9月21日
- 注意:掲載したのは集計結果です。取得したデータそのものは配布していません