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

方向の無いティックで買いと売りを分ける|半分は決められませんでした

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

J-Quants のティックには売り買いの方向が入っていません。値段の動きから推定するのが定番ですが、実際にやってみたら約定の 54.0% は前と同じ値段で、判定できませんでした。そしてそもそも方向が定義できない約定が、出来高の 13.7% ありました。

  • 前の約定と比べて上 23.0% / 下 23.0% / 同値 54.0%
  • 同値の扱いを変えると、買い比率が最大 3.18 ポイント動きます
  • よくある書き方は系統的に低く出ます(48.36% 対 50.1%)
  • 板寄せが出来高の 13.74%。大引けだけで 10.13%、ある銘柄は 28.30%
  • 答え合わせはできません。気配が配られていないので正解が無い

そもそも、なぜ推定が要るのか

J-Quants のティックの列はこの7つです。

Date, Code, Time, SessionDistinction, Price, TradingVolume, TransactionId

「この約定は買い手が仕掛けたのか、売り手が仕掛けたのか」は入っていません。板の情報(最良買い気配・売り気配)も配られていないので、約定した値段と出来高だけから推定することになります。

これができると、板を見ずに「今どちらが押しているか」が数字になります。だから多くの人が試みます。では、どのくらいできるのか。

定番の方法:ティックルール

いちばん素直なやり方です。前の約定より高ければ買い、安ければ売り。

import numpy as np

# ティックルール:前の約定より高ければ買い主導、安ければ売り主導
d = d.sort_values(["Date", "Tid"])          # ★時刻ではなく TransactionId 順
pr = d["Price10"].to_numpy(np.int64)

diff = np.concatenate([[0], np.diff(pr)])
newday = d["Date"].to_numpy() != np.concatenate([[0], d["Date"].to_numpy()[:-1]])
diff[newday] = 0                            # 日をまたぐところは比較しない

side = np.sign(diff)                        # +1 買い / -1 売り / 0 決められない
print(f"決められない割合 {(side == 0).mean() * 100:.1f}%")

並べ替えは時刻ではなく TransactionId です。同じマイクロ秒に複数の約定が並ぶので、時刻で並べると順番が狂い、推定そのものが壊れます

★ 半分以上は、前と同じ値段だった

売買代金の大きい12銘柄について、14営業日ぶんのティックで数えました。

銘柄約定数上がった下がった同値
A(最大手)1,433,59421.6%21.7%56.7%
B1,823,99425.6%25.5%48.9%
C413,39817.6%17.6%64.9%
D1,629,69731.1%31.3%37.7%
E187,80726.3%26.7%47.0%
F498,81624.1%24.1%51.8%
G338,12916.1%16.0%67.9%
H1,093,08721.4%21.5%57.1%
I226,34926.0%26.0%48.0%
J958,50726.1%26.3%47.6%
K477,08424.0%24.0%51.9%
L204,30215.9%16.0%68.1%
平均773,73023.0%23.0%54.0%
銘柄名は伏せています(取得データそのものの公開にあたらないようにするため)。値幅制限や呼値の単位で「同値」の出やすさが変わります

上がった 23.0% と下がった 23.0% がきれいに対称なのは、計算が正しいことの確認になります。値段は行って戻るからです。

問題は残りです。54.0%、半分以上が「前と同じ値段」でした。ティックルールはここで何も言えません。銘柄によっては68.1%、つまり7割近くが判定不能です。

同値が多いのは、呼値の単位(値段の刻み)が粗い銘柄です。刻みが粗いほど同じ値段が続きます。「流動性が高いほど推定しやすい」とは限りません。上の表で同値が最も少ないのは D(37.7%)で、最大手の A(56.7%)より判定しやすい。

同値をどう扱うかで、答えが動く

よく使われる3通りで、14営業日ぶんの買い比率(出来高ベース)を出しました。

  • A:直前の判定を引き継ぐ(教科書的なティックルール)
  • B:同値は捨てる(分母からも外す)
  • C:同値は半分ずつ(方向不明として 0.5 を配る)
# 同値のところに、直前の判定を引き継ぐ
carry, last = side.copy(), 0
for i in range(len(carry)):
    if newday[i]:
        last = 0                            # 日の最初は引き継ぎ元が無い
    if carry[i] == 0:
        carry[i] = last
    else:
        last = carry[i]

# ★ここで carry == 0 が残る(その日まだ一度も値が動いていない区間)
buy_ratio = vol[carry > 0].sum() / vol.sum()    # ← 分母に未確定が入ったまま
銘柄A 引き継ぎB 捨てるC 半分最大差
B51.99%54.73%52.78%2.74
D48.89%49.65%49.77%0.89
H45.54%46.34%48.22%2.67
J49.84%52.58%51.54%2.74
F49.82%50.98%50.54%1.17
L45.38%47.26%48.56%3.18
G47.35%48.53%49.39%2.04
C48.05%50.59%50.27%2.54
平均48.36%50.08%50.13%2.25

14営業日を丸ごと合計すれば、どのルールでもおよそ半々に落ち着きます。当たり前のようですが、これも確認になります。1日の中で買いと売りは同じ数だけ約定するので、大きく 50% を離れる結果が出たら、それは実態ではなく処理の間違いです。

★ Aだけ 1.7 ポイント低い

表をよく見てください。Aの平均が 48.36%、BとCは 50.1%。Aだけ系統的に低く出ています。

理由は引き継いでも決まらない約定が残るからです。その日まだ一度も値段が動いていない区間には、引き継ぐ元がありません。上のコードの vol[carry > 0].sum() / vol.sum() は、この未確定ぶんを分母に入れたまま分子に入れていません

出来高に占める割合
引き継いでも決まらなかった平均 3.54%(最大 6.39%)

3.54% が丸ごと「買いではない」側に数えられるので、買い比率が下がります。差の 1.7 ポイントは、ほぼこれで説明がつきます。

これは推定の精度の話ではなく、割り算の間違いです。
「買い比率が 48% だった、売り優勢だ」と読んでしまうと、存在しない偏りを見ていることになります。

# 未確定を分母からも外す
m = carry != 0
buy_ratio = vol[m & (carry > 0)].sum() / vol[m].sum()

# そして「板寄せ」は最初から除く(方向という概念がない)
is_auction = first_of_day | last_of_session
m &= ~is_auction

★ そもそも方向が無い約定が、出来高の 13.7%

ここがいちばん大事な点です。日本株の寄付・前引け・大引けは板寄せで、売り注文と買い注文を突き合わせて一本の値段を付けます。

板寄せの約定に「買いが仕掛けた/売りが仕掛けた」という区別はありません。全部が同時に、同じ値段で約定します。ここをティックルールで分類するのは、意味の無い作業です。

銘柄寄付前引け大引け合計
B3.01%0.07%5.79%8.88%
D1.14%0.04%2.18%3.36%
H3.07%0.06%4.82%7.94%
J2.99%0.07%9.32%12.38%
F1.93%0.05%5.01%6.99%
L6.39%0.17%28.30%34.85%
G6.18%0.11%15.59%21.89%
C3.56%0.07%10.00%13.63%
平均3.53%0.08%10.13%13.74%
出来高に占める割合。各セッションの最初と最後の約定を板寄せとみなした

大引けだけで出来高の 10.13%。ある銘柄では 28.30%、つまり1日の出来高の4分の1以上がたった1本の約定で付いています。指数に連動する資金が大引けに集中するためです。

一方、約定の「本数」で見れば板寄せは 0.005% しかありません。本数で数えるか出来高で数えるかで、見え方がまったく変わります。

前引け(11:30)は 0.08% しかありません。板寄せだから大きい、のではなく、大引けにだけ資金が集まっているのが実態です。

答え合わせができない、という限界

ここまで「推定のばらつき」は測れました。では、推定は当たっているのか。

確かめられません。正解が手元に無いからです。判定の正解を知るには、約定した瞬間の最良買い気配・売り気配が要りますが、私の契約(Light + 分足・ティックアドオン)で配られるデータに気配は含まれていません。

だから、この手の記事でよく見る「ティックルールの的中率は◯%」という数字は、このデータだけでは出せません。出ているとしたら、別のデータ源で答え合わせをしているはずです。出典を確かめてください。


使うなら、こうする

  1. 並べ替えは TransactionId で。時刻では順番が狂います
  2. 板寄せは最初に除く。寄付・前引け・大引けの約定。出来高の13.7%です
  3. 未確定は分母からも外す。入れたままだと系統的に低く出ます
  4. 同値の扱いを明記する。A・B・Cのどれかで2ポイント動きます
  5. 合計が 50% 近辺になることを毎回確かめる。大きく外れたら処理の間違いです
  6. 「的中率」を名乗らない。このデータでは検証できません

まとめ

方向の無いティックから買いと売りを分ける作業は、半分以上が判定不能という前提から始まります。そこに、扱い方で2ポイント動く裁量と、そもそも方向が無い13.7%の出来高が乗ります。

できないと言っているのではありません。何をどう決めたかを書いておけば、使える数字になります。書いていない数字は、あとから自分でも再現できません。

そして的中率は、このデータでは測れません。測れないものを測れたことにしないのが、検証の最低条件だと思っています。

この記事の出どころ

  • データ:J-Quants API V2 の bulk ティック(equities/trades)。Light プラン+分足・ティックアドオン
  • 期間:2026-09-01 〜 2026-09-18 の14営業日
  • 対象:最終日の売買代金 上位12銘柄。約定数 合計 9,284,764 本
  • 板寄せの判定:各セッションの最初と最後の約定を板寄せとみなした(Session=1 は 09:00〜11:30、Session=2 は 12:30〜15:30 を実測で確認)
  • 環境:Windows 11 / Python 3.12 / pandas 3.0 / numpy 2.5 / pyarrow 21
  • 測定日:2026年9月21日
  • 注意:掲載したのは集計結果です。銘柄名は伏せ、取得したデータそのものは配布していません