日本株の検証を、実測で。

J-Quantsのティックから1分足を組むと、始値と終値だけがずれる

  • 2026年9月21日
  • 2026年9月21日
  • データ

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銘柄)と全件突き合わせました。

素直に書くと、こうなります

naive.py
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
ファイルに並んでいる順6586200…924
6587900…921
TransactionId 順(実際の約定順)6587900…921
6586200…924

公式の分足の終値は 6586 でした。つまり TransactionId 順にした最後が正しい。ファイルの行順をそのまま信じると、この分の終値を 6587 と取り違えます。

直し方は1行です

fixed.py
# 直し方は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. ずれ幅が小さい。多くは1ティック分です。チャートに描いても気づけません
  2. 高値・安値・出来高は合っている。「データは取れている」と思ってしまいます
  3. Time で並べ替えても直りません。マイクロ秒まで同じなので、並べ替えても順序は変わりません
  4. TransactionId は公式のデータ項目表に載っていません。実際にファイルを開くまで、その列があることに気づけません

なぜ放っておけないのか

始値と終値は、バックテストで約定価格として使う値だからです。

「シグナルが出た次の分の始値で買う」「引け値で手仕舞う」——こういうルールを検証するとき、使っているのはまさにその始値・終値です。高値と安値がいくら正確でも、約定価格が1ティックずれていれば、その分だけ成績が変わります。しかもずれる方向は一定ではないので、「安全側に寄っているから大丈夫」とも言えません。

1回のずれは小さくても、売買回数の多い戦略ほど積み上がります。短期の検証では、これは無視できる誤差ではありません。

自分のコードを確かめる

すでにティックから足を作っている方は、これで30秒で確かめられます。

check.py
# 自分のコードが大丈夫か、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日
  • 注意:掲載したのは集計結果です。取得したデータそのものは配布していません