Trade Classification When You Have No Side Flag: Tick, Quote, Lee-Ready, BVC
まず、多くのマーケット・マイクロストラクチャーの教科書が冒頭で扱う、この問題の形を退けるところから始めます。このブログが実際に取引する市場では、取引方向は推定するものではなく、フィールドとして与えられます。Binance の aggTrades には isBuyerMaker があり、Bybit の取引ストリームには side があります。バー構築の記事では、そのフラグからアグレッサー側を直接取得し、分類器を実行しません。
def on_trade(self, timestamp: int, price: float, qty: float, is_buyer_maker: bool):
side = -1 if is_buyer_maker else 1
これは無料のグラウンドトゥルースで、ナノ秒精度です。最新の暗号資産 CEX フィードから符号付き注文フローを構築するなら、この記事は必要ありません。
必要になるのは次の4つの状況です。いずれも十分に一般的です。
- バーしかない。 ヒストリカル OHLCV はほとんどのベンダーが販売し、ほとんどのバックテストが使うデータです。取引ストリームが保存されていないか、研究に見合わないコストがかかります。以下の BVC 問題です。
- 市場がサイドをフラグしない。 小規模 CEX、株式 TAQ 型データセット、そして 2017 年以前のほぼすべてのアーカイブでは、価格とサイズしか得られません。
- DEX とオンチェーンフロー。 swap イベントはどのトークンがどちらに動いたかを示しますが、ルーティングされたマルチホップ取引やアグリゲーターでアグレッサーを割り当てるためのフラグはありません。
- 公開研究を再現する。 Kyle の lambda 推定、VPIN、スプレッド分解など、マイクロストラクチャー文献のほぼ全体が Lee-Ready 分類データに基づいています。比較可能性のためには真のサイドを読むのではなく、その分類器を実行する必要があります。
以下では、ルール、最も重要な失敗モード、バルクボリュームの近道、そしてすべてを測定するプロトコルを説明します。暗号資産は、株式文献が持てなかったラベルを無料で提供してくれるからです。
ティックテスト

現在の取引価格 と直前の価格を比較します。
直感はこうです。買い主導の取引はアスクを持ち上げて価格を押し上げ、売り主導の取引はビッドを叩いて価格を押し下げます。ゼロティックでは直前の符号を繰り越します。ここに誤差の大部分があります。1ティックのスプレッドを持つ流動性の高い銘柄では、連続する取引の大半が同じ価格で成立します。
sign = np.sign(np.diff(prices, prepend=prices[0]))
sign[sign == 0] = np.nan
directions = pd.Series(sign).ffill().fillna(1).astype(int).values
同じ繰り越し意味論の本番実装は、時間バーを超えてのティック・出来高不均衡バー生成器内に _tick_sign() としてすでにあります。2つ目のコピーを保守せず、これを再利用してください。
クォートルール

連続する取引を比較する代わりに、取引価格をその時点のミッドポイント と比較します。
def quote_rule(trade_prices: np.ndarray,
bid_prices: np.ndarray,
ask_prices: np.ndarray) -> np.ndarray:
"""Classify trades using the quote rule.
Returns +1 (buy), -1 (sell), or 0 (unclassified) for midpoint trades.
"""
midpoints = (bid_prices + ask_prices) / 2.0
directions = np.zeros(len(trade_prices), dtype=int)
directions[trade_prices > midpoints] = 1
directions[trade_prices < midpoints] = -1
return directions
クォートルールはティックテストより多くの情報を使いますが、別の場所で失敗します。ミッドで成立した取引について何も言えないのです。狭いスプレッドでティックに制約された銘柄では、これは例外ではなくテープの大きな割合を占めます。ルールにはフォールバックが必要で、それが Lee-Ready です。
Lee-Ready:ハイブリッドと古いクォートの仮定

Lee と Ready(1991)は2つを組み合わせました。クォートルールを適用し、ミッドポイントの取引ではティックテストにフォールバックします。
def lee_ready(trade_prices: np.ndarray,
bid_prices: np.ndarray,
ask_prices: np.ndarray,
quote_lag: int = 0) -> np.ndarray:
"""Lee-Ready classification.
quote_lag : number of quote observations to lag (0 = contemporaneous).
Set > 0 only if your quote feed is known to trail the trade
feed; see the discussion of the 5-second rule below.
Depends on tick_test() with carry-forward semantics — the `_tick_sign()`
implementation from /blog/beyond-time-bars-candle-construction.
"""
if quote_lag > 0:
bid = np.concatenate([bid_prices[:quote_lag], bid_prices[:-quote_lag]])
ask = np.concatenate([ask_prices[:quote_lag], ask_prices[:-quote_lag]])
else:
bid, ask = bid_prices, ask_prices
directions = quote_rule(trade_prices, bid, ask)
unclassified = directions == 0
if unclassified.any():
directions[unclassified] = tick_test(trade_prices)[unclassified]
return directions
論文のより興味深い部分は、根拠なしに再現されがちです。Lee と Ready は単にハイブリッドを提案したのではなく、各取引を5秒前に有効だったクォートと比較することを提案しました。この数字は市場についてのモデリング選択ではありません。報告上のアーティファクトを補正するためのものです。1980年代の NYSE 集約テープでは、取引とクォートが遅延の異なる経路で配信され、クォートが対応する取引より先に到着する傾向がありました。そのため取引を同時刻の印字クォートと比較すると、すでにその取引を反映したクォートと比較することになり、市場を動かした取引の符号をちょうど反転させていました。
これを暗号資産へ移植する際には、2つの帰結があります。
5秒ルールは時代遅れで、適用すると有害です。 これはもはや存在しない配信遅延を補正します。株式市場は1990年代半ばまでに差を埋めました。暗号資産市場の取引ストリームと板差分ストリームは同じシーケンス空間を共有し、同じマッチングエンジンがマイクロ秒精度でタイムスタンプを付けます。BTCUSDT の5秒は同期許容幅ではなく、数百回の板更新です。クォートを5秒遅らせても古いクォートは直らず、それを作り出すだけです。自分の取得データで実際のオフセットを測定していない限り、quote_lag=0 を使ってください。
しかし根本問題は新しい形で残ります。 クォートの古さは取引所ではなく、あなたのパイプラインの責任です。WebSocket 差分ストリームから板を再構築し、壁時計のタイムスタンプで取引を結合すると、バッファリング、スナップショット再同期方針、クロックに依存する遅延を導入します。板を読む分類器を信頼する前に測定してください。ウィンドウをリプレイし、取引を板状態に結合し、結合後の [bid, ask] の外に落ちる割合を確認します。結合したアスクを上回って成立した取引は、照合した板が古かったことを証明します。
修正方法は Lee と Ready のものとは異なりますが、失敗モードは同じです。
バルクボリューム分類

Easley、Lopez de Prado、O'Hara(2012)は別の問題に取り組みました。個々の取引を一度も見られない場合はどうするか、です。BVC はバー内の集計出来高を分類し、標準化した価格変化に標準正規 CDF を適用して分割します。
ここで 、 はバーの価格変化の標準偏差です。
from scipy.stats import norm
def bulk_volume_classification(open_prices: np.ndarray,
close_prices: np.ndarray,
volumes: np.ndarray,
sigma: float | None = None) -> tuple:
"""Split bar volume into buy/sell using the BVC rule.
Returns (buy_volume, sell_volume) as continuous, not integer, splits.
"""
delta_p = close_prices - open_prices
if sigma is None:
sigma = max(float(np.std(delta_p)), 1e-8)
buy_fraction = norm.cdf(delta_p / sigma)
return volumes * buy_fraction, volumes * (1.0 - buy_fraction)
BVC は異なるツールであり、悪いツールではありません。3つの性質が理由を示します。BVC は取引を分類せず、比率を出力します。そのため変化なく終わったバーは、その中の各取引をコイントスするのではなく 50/50 に分割されます。利用者が取引ごとの符号を必要とするなら、BVC は提供できません。テープではなくバーで動くことが本質です。取引レベルのデータが存在しない場合に機能する唯一の方法が BVC だからです。そして は結果を動かす自由パラメータです。全サンプル推定は先読みになり、短いローリング推定は を飽和させ、すべてのバーを 0/100 に近づけます。この選択にはデフォルトではなく感度分析が必要です。
原論文は、BVC が VPIN 推定に使える程度に取引レベル分類を追跡すると報告しています。具体的な精度はバーサイズ、銘柄、 推定量に大きく依存し、二次資料の数字は3つすべてを示していないことが多いです。株式研究の数字を持ち込まず、測定してください。
分類に固有の唯一の特徴

ルールを実行する代わりにモデルを学習させる場合、特徴量の大部分はすでに公開されています。スプレッド、板の深さの不均衡、取引フロー不均衡、ローリング実現ボラティリティは機械学習によるスプレッドモデリングに表と実装があり、DeepLOBには多層の板特徴があります。これらを使ってください。
この問題に固有で、どちらにも現れない特徴は、スプレッド内の取引の位置です。
ビッドでの取引は0、アスクは1、ミッドは0.5に写像されます。これはクォートルールの連続版です。ミッドポイントで閾値を切る代わりに距離をモデルへ渡すため、モデルは0.95の取引がほぼ確実に買いで、0.55はほとんど情報を持たないと学習できます。 の外の値は前節の古いクォート診断であり、クリップせずログに残す価値があります。
これらの特徴によるブースト基線には、機械学習によるスプレッドモデリングで公開済みのパイプラインを使い、目的だけを二値に変更します。重要なのは、パージとエンバーゴを含むウォークフォワード CV も維持することです。マイクロストラクチャー分類器を通常の TimeSeriesSplit で評価しないでください。その数字が楽観的になる理由は、同記事とウォークフォワード最適化で説明しています。板スナップショット上の系列モデルには DeepLOBが参照アーキテクチャであり、そこでの学習上の注意はそのまま適用できます。
正しく測る:暗号資産がラベルをくれる

株式の取引分類文献にある精度はすべて、真値の代理に対して推定されています。TORQ、監査証跡のサブサンプル、単一市場の内部記録などです。真のアグレッサー側が公開されなかったからです。暗号資産はそれを公開します。Binance aggTrades の isBuyerMaker が、すべての取引に対する無料のラベルです。したがって誰でも実験でき、暗号資産での結果が発表されているのを私は見つけていません。プロトコルは次の通りです。
データ。 1か月分の BTCUSDT aggTrades と板 ticker ストリームを使い、各取引に直前のビッドとアスクを付けて結合します。スプレッドが広いと何が起きるかが問題の核心なので、薄い alt でも繰り返します。
ラベル。 direction = -1 if is_buyer_maker else +1。その後フラグを捨て、価格とクォートだけで各手法を実行します。
手法。 ティックテスト、クォートルール、quote_lag=0 の Lee-Ready、歴史的な5秒ラグの Lee-Ready(古いルールの害を定量化)、そして複数のバーサイズでの BVC を、真のバーごとの買い出来高比率と比較します。
内訳。 全体精度は最も興味の薄い数字です。スプレッド状態(1ティック対ワイド。スプレッドが固定される場所ではティックテストの繰り越しが崩れるはず)、ミッドで成立したか(クォートルールが棄権し、Lee-Ready のフォールバックが働く場所を分離)、取引サイズ(aggTrades のエントリーはスイープで、大口取引は影響とコストを動かすため、誤った符号を付けたときの損失が大きい)、ボラティリティ状態(BVC の は推定値なので、構造上その誤差は状態依存)で分解します。
| 手法 | 全体 | ミッド | タイトスプレッド | ワイドスプレッド | 大口取引 |
|---|---|---|---|---|---|
| ティックテスト | — | — | — | — | — |
| クォートルール | — | — | — | — | — |
| Lee-Ready(ラグ0) | — | — | — | — | — |
| Lee-Ready(5秒ラグ) | — | — | — | — | — |
| BVC(バーごとの比率) | — | 該当なし | — | — | 該当なし |
精度は目的ではない

DeepLOBの枠組みから学びましょう。分類精度は結果ではなく、結果への入力です。誰も取引1件の符号を取引するわけではありません。符号のストリーム全体から計算したものを取引し、誤りは中立には伝播しません。10%の取引でランダムに間違える分類器と、大口で価格を動かす10%の取引を体系的に間違える分類器は、同じ精度でもまったく異なる符号付き注文フローを生みます。ティックテストの繰り越し失敗は価格がフラットな状態と相関しますが、そこは符号付きフローが最も有益であるはずの状態です。だから見出しの数字ではなく、上の内訳こそが真の出力なのです。
グラウンドトゥルースのラベルがあれば、下流の問いを直接測れます。真の符号から必要な量を推定し、各分類器の符号から再推定して、その差を報告します。
- 価格インパクト。 固定ウィンドウで符号付き純テイカー・フローに対してミッド価格変化を回帰します。推定量はスリッページコストモデルで指定され、公開 Binance データでの実装はAlmgren-Chriss 最適執行にあります。ここで再定義せず、2回実行して係数を比較します。
- 予測変数としての注文フロー不均衡。 真の符号と分類符号で、OFI と先物リターンの相関を計算します。その相関の低下こそが分類誤差のコストであり、重要な唯一の単位で表されます。
- 毒性指標。 VPIN と取引フロー不均衡特徴は分類済み出来高を使います。どちらも機械学習によるスプレッドモデリングで説明されています。分類誤差への感度も同じ方法で測定できます。
ここから何が得られるか

市場がアグレッサーフラグを提供するなら、それを使ってください。暗号資産の大半は、ほとんどの場合そうです。
提供されないなら、同時刻のクォートを使う Lee-Ready が正しいデフォルトです。正確だからではなく標準だからです。検証できない数ポイントの精度より、30年分の公開推定値との比較可能性に価値があります。5秒ルールは1980年代のテープ遅延の化石です。現代のデータで再現すると、除去するために作られた古さを自ら注入します。バーしかないなら、BVC はそもそも動く唯一の選択肢であり、異なる問いに答えます。取引符号ではなく出来高の分割です。
どの場合でも、暗号資産が無料で提供するラベルに対して測定し、スプレッド状態と取引サイズで結果を分解し、何パーセント正しかったかではなく誤差が下流でいくらのコストになるかを報告してください。
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.