📝

Draft article

This draft is visible to admins and superusers only. Sign in with an authorized account.

← Zurück zu den Artikeln
August 23, 2026
5 min read

Trade Classification When You Have No Side Flag: Tick, Quote, Lee-Ready, BVC

Trade Classification When You Have No Side Flag: Tick, Quote, Lee-Ready, BVC
#microstructure
#trade-classification
#Lee-Ready
#order-flow
#deep-learning

Beginnen wir damit, die Version dieses Problems zurückzuweisen, mit der die meisten Lehrbücher zur Mikrostruktur starten. Auf den Handelsplätzen, auf denen dieser Blog tatsächlich handelt, muss die Handelsrichtung nicht abgeleitet werden — sie ist ein Feld. Binance aggTrades liefert isBuyerMaker; der Trade-Stream von Bybit liefert side. Der Artikel zur Bar-Konstruktion übernimmt die Aggressor-Seite direkt aus diesem Flag und führt keinen Klassifikator aus:

def on_trade(self, timestamp: int, price: float, qty: float, is_buyer_maker: bool):
    side = -1 if is_buyer_maker else 1

Das ist die Ground Truth — kostenlos und mit Nanosekundenauflösung. Wenn du aus einem modernen Krypto-CEX-Feed einen signierten Orderflow aufbaust, brauchst du diesen Artikel nicht.

Du brauchst ihn in vier Situationen, und sie kommen häufig genug vor, um relevant zu sein:

  1. Du hast nur Bars. Historisches OHLCV ist das, was die meisten Anbieter verkaufen und die meisten Backtests verwenden; der Trade-Stream wurde nie archiviert oder kostet mehr, als die Recherche wert ist. Das ist das folgende BVC-Problem.
  2. Der Handelsplatz liefert kein Seiten-Flag. Kleinere CEXs, TAQ-artige Aktiendatensätze und fast alle Archive vor 2017 geben dir nur Preis und Größe.
  3. DEX- und On-Chain-Flow. Ein Swap-Ereignis sagt, welcher Token sich in welche Richtung bewegt hat, aber für die Zuordnung der Aggressorseite bei einem gerouteten Multi-Hop-Trade oder einem Aggregator gibt es kein Flag als Rückfallebene.
  4. Du replizierst veröffentlichte Forschung. Schätzungen von Kyles Lambda, VPIN und Spread-Zerlegungen — fast die gesamte Mikrostruktur-Literatur basiert auf Lee-Ready-klassifizierten Daten. Vergleichbarkeit bedeutet daher, den Klassifikator der Forschung auszuführen, statt die echte Seite abzulesen.

Es folgen die Regeln, der wichtigste Fehlermodus, die Bulk-Volume-Abkürzung und ein Protokoll, um alles zu messen — denn Krypto liefert dir die Labels, die der Aktienliteratur nie zur Verfügung standen.

Der Tick-Test

Ereignisfolge des Tick-Tests

Vergleiche den aktuellen Trade-Preis PtP_t mit dem vorherigen:

dttick={+1if Pt>Pt11if Pt<Pt1dt1tickif Pt=Pt1d_t^{\text{tick}} = \begin{cases} +1 & \text{if } P_t > P_{t-1} \\ -1 & \text{if } P_t < P_{t-1} \\ d_{t-1}^{\text{tick}} & \text{if } P_t = P_{t-1} \end{cases}

Die Intuition: Käuferinitiierte Trades heben den Ask an und treiben den Preis nach oben; verkäuferinitiierte Trades treffen den Bid und drücken ihn nach unten. Bei Null-Ticks wird das vorherige Vorzeichen weitergetragen — dort entsteht der größte Teil des Fehlers: Bei einem liquiden Instrument mit einem Tick Spread werden die meisten aufeinanderfolgenden Trades zum selben Preis ausgeführt.

sign = np.sign(np.diff(prices, prepend=prices[0]))
sign[sign == 0] = np.nan
directions = pd.Series(sign).ffill().fillna(1).astype(int).values

Eine Produktionsimplementierung mit derselben Carry-Forward-Semantik gibt es bereits als _tick_sign() in den Tick- und Volume-Imbalance-Bar-Generatoren in Beyond Time Bars. Verwende sie, statt eine zweite Kopie zu pflegen.

Die Quote-Regel

Trades innerhalb der Bid-Ask-Geometrie

Vergleiche statt aufeinanderfolgender Trades den Trade-Preis mit dem aktuellen Midpoint Mt=(At+Bt)/2M_t = (A_t + B_t)/2:

dtquote={+1if Pt>Mt1if Pt<Mtundefinedif Pt=Mtd_t^{\text{quote}} = \begin{cases} +1 & \text{if } P_t > M_t \\ -1 & \text{if } P_t < M_t \\ \text{undefined} & \text{if } P_t = M_t \end{cases}
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

Die Quote-Regel nutzt mehr Informationen als der Tick-Test, scheitert aber an einer anderen Stelle: Trades genau am Midpoint werden nicht klassifiziert. Bei einem engen Spread und tick-beschränkten Instrument ist das kein Sonderfall, sondern ein großer Teil des Tapes. Die Regel braucht einen Fallback — genau das liefert Lee-Ready.

Lee-Ready: der Hybrid und die Annahme veralteter Quotes

Trade-Stream und verzögerte Quote-Oberfläche

Lee und Ready (1991) kombinierten beides: Verwende die Quote-Regel und falle bei Midpoint-Trades auf den Tick-Test zurück.

dtLR={dtquoteif PtMtdttickif Pt=Mtd_t^{\text{LR}} = \begin{cases} d_t^{\text{quote}} & \text{if } P_t \neq M_t \\ d_t^{\text{tick}} & \text{if } P_t = M_t \end{cases}
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

Der interessantere Teil des Papers ist der, der meist ohne seine Begründung reproduziert wird. Lee und Ready schlugen nicht nur einen Hybrid vor, sondern den Vergleich jedes Trades mit dem fünf Sekunden zuvor geltenden Quote. Diese Zahl ist keine Modellierungsentscheidung über Märkte. Sie korrigiert ein Übermittlungsartefakt: Auf dem konsolidierten NYSE-Tape der 1980er wurden Trades und Quotes über unterschiedliche Wege mit unterschiedlichen Verzögerungen verbreitet, und Quotes trafen häufig vor den zugehörigen Trades ein. Ein Vergleich mit dem zeitgleichen gedruckten Quote verglich den Trade daher mit einem Quote, der ihn bereits enthielt — und drehte das Vorzeichen gerade bei den Trades um, die den Markt bewegten.

Für die Übertragung auf Krypto folgen daraus zwei Dinge.

Die Fünf-Sekunden-Regel ist veraltet, und ihre Anwendung schadet aktiv. Sie korrigiert eine Übermittlungsverzögerung, die nicht mehr existiert. Aktienmärkte schlossen diese Lücke bis Mitte der 1990er; die Trade- und Book-Delta-Streams einer Kryptobörse teilen sich einen Sequenzraum und werden von derselben Matching Engine mit Mikrosekundenauflösung versehen. Fünf Sekunden bei BTCUSDT sind keine Synchronisationstoleranz, sondern Hunderte von Book-Updates. Quotes um fünf Sekunden zu verzögern behebt veraltete Quotes nicht, sondern erzeugt sie. Verwende quote_lag=0, sofern du nicht in deinem eigenen Capture einen echten Offset gemessen hast.

Das zugrunde liegende Problem bleibt aber in neuer Form bestehen. Quote-Staleness ist jetzt ein Fehler deiner Pipeline, nicht der Börse. Wenn du das Book aus einem WebSocket-Delta-Stream rekonstruierst, Trades nach Wall-Clock-Timestamp verknüpfst und damit eine Verzögerung einführst, hängen Vorzeichen und Größe von Buffering, Snapshot-Resync-Policy und deiner Uhr ab. Miss das, bevor du einem Klassifikator vertraust, der das Book liest: Spiele ein Fenster erneut ab, verknüpfe Trades mit dem Book-Zustand und prüfe, welcher Anteil außerhalb des verknüpften [bid, ask] liegt. Ein Trade oberhalb des verknüpften Asks beweist, dass das Book, mit dem du ihn verknüpft hast, veraltet war.

Die Korrektur unterscheidet sich von der von Lee und Ready; der Fehlermodus ist derselbe.

Bulk-Volume-Klassifikation

Volumencluster nach Richtungs-Klasse

Easley, Lopez de Prado und O'Hara (2012) greifen ein anderes Problem an: Was, wenn du einzelne Trades nie siehst? BVC klassifiziert das aggregierte Volumen innerhalb einer Bar und teilt es mithilfe der Standardnormal-CDF der standardisierten Preisänderung:

Vtbuy=VtΦ(ΔPtσΔP),Vtsell=VtVtbuyV_t^{\text{buy}} = V_t \cdot \Phi\left(\frac{\Delta P_t}{\sigma_{\Delta P}}\right), \qquad V_t^{\text{sell}} = V_t - V_t^{\text{buy}}

wobei ΔPt=PtclosePtopen\Delta P_t = P_t^{\text{close}} - P_t^{\text{open}} und σΔP\sigma_{\Delta P} die Standardabweichung der Preisänderungen der Bar ist.

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 ist kein schlechteres, sondern ein anderes Werkzeug — drei Eigenschaften erklären warum. Es klassifiziert keine Trades, sondern erzeugt einen Anteil. Eine unverändert schließende Bar erhält daher eine 50/50-Aufteilung statt eines Münzwurfs für jeden darin enthaltenen Trade. Wenn dein Verbraucher ein Vorzeichen pro Trade benötigt, kann BVC es nicht liefern. Es arbeitet mit Bars statt mit dem Tape; genau darin liegt sein Zweck: Es ist die einzige Methode hier, die ohne Trade-Level-Daten funktioniert. Und σ\sigma ist ein freier Parameter, der die Ergebnisse verschiebt: eine Vollstichproben-Schätzung ist Lookahead, eine kurze rollierende Schätzung lässt Φ\Phi sättigen und drückt jede Bar in Richtung 0/100. Diese Wahl verdient einen Sensitivitätssweep, keinen Default.

Das Original-Paper berichtet, dass BVC die Trade-Level-Klassifikation für VPIN-Schätzungen ausreichend eng verfolgt. Konkrete Genauigkeitswerte hängen stark von Bar-Größe, Instrument und σ\sigma-Schätzer ab, und die Zahlen aus Sekundärquellen nennen selten alle drei. Übernimm keinen Wert aus einer Aktien-Studie — miss es selbst.

Das eine klassifikationsspezifische Merkmal

Hervorgehobenes Mikrostruktur-Signal

Wenn du dafür ein Modell trainierst, statt eine Regel auszuführen, ist der größte Teil des Feature-Sets bereits veröffentlicht: Spread, Depth-Imbalance, Trade-Flow-Imbalance und rollierende realisierte Volatilität sind in Spread-Modellierung mit Machine Learning tabelliert und implementiert, die mehrstufigen Orderbuch-Features in DeepLOB. Verwende diese.

Das für dieses Problem spezifische Merkmal, das in keinem der beiden Artikel vorkommt, ist die Position des Trades innerhalb des Spreads:

relative_pricet=PtBtAtBt\text{relative\_price}_t = \frac{P_t - B_t}{A_t - B_t}

Ein Trade am Bid wird auf 0, am Ask auf 1 und am Mid auf 0,5 abgebildet. Das ist die kontinuierliche Verallgemeinerung der Quote-Regel: Statt am Midpoint zu thresholden, gibst du dem Modell den Abstand, sodass es lernen kann, dass ein Trade bei 0,95 fast sicher ein Kauf ist, während 0,55 kaum Information enthält. Werte außerhalb von [0,1][0, 1] sind die Stale-Quote-Diagnose aus dem vorherigen Abschnitt und sollten protokolliert, nicht abgeschnitten werden.

Für eine Boosting-Baseline über diesen Features ist die Pipeline aus Spread-Modellierung mit Machine Learning bereits veröffentlicht; ersetze nur das Ziel durch ein binäres. Dazu gehört ausdrücklich auch die Purge- und Embargo-Walk-Forward-CV. Bewerte einen Mikrostruktur-Klassifikator nicht mit einem einfachen TimeSeriesSplit: Dieser Artikel und Walk-Forward-Optimierung erklären, warum die resultierende Zahl optimistisch ist. Für Sequenzmodelle über Book-Snapshots ist DeepLOB die Referenzarchitektur, und der Abschnitt zu Trainingsüberlegungen gilt unverändert.

Richtig messen: Krypto liefert die Labels

Beschrifteter Krypto-Marktdatenstrom

Jede Genauigkeitszahl in der Literatur zur Trade-Klassifikation von Aktien wird gegen einen Proxy für die Wahrheit geschätzt — TORQ, Audit-Trail-Stichproben oder interne Aufzeichnungen eines einzelnen Handelsplatzes — weil die echte Aggressor-Seite nie veröffentlicht wurde. Krypto veröffentlicht sie. isBuyerMaker in Binance-aggTrades ist für jeden Trade kostenlos das Label. Das Experiment steht daher jedem offen, und ich habe es für Krypto nicht veröffentlicht gefunden. Das Protokoll:

Daten. Ein Monat BTCUSDT-aggTrades plus Book-Ticker-Stream, so verknüpft, dass jeder Trade den unmittelbar davor geltenden Bid und Ask trägt. Wiederhole das auf einem dünnen Altcoin, denn die Frage ist, was bei einem breiten Spread passiert.

Label. direction = -1 if is_buyer_maker else +1. Danach verwirfst du das Flag und führst jede Methode nur mit Preis- und Quote-Daten aus.

Methoden. Tick-Test, Quote-Regel, Lee-Ready mit quote_lag=0, Lee-Ready mit dem historischen Fünf-Sekunden-Lag (um den Schaden der veralteten Regel zu quantifizieren) und BVC bei mehreren Bar-Größen gegen den echten Buy-Volume-Anteil pro Bar.

Aufschlüsselungen. Die Gesamtgenauigkeit ist die uninteressanteste Zahl. Schlüssle nach Spread-Regime auf (ein Tick gegenüber breit — der Carry-Forward des Tick-Tests sollte dort zusammenbrechen, wo der Spread feststeckt), danach, ob der Trade am Mid ausgeführt wurde (zeigt, wo die Quote-Regel aussetzt und Lees-Readys Fallback die gesamte Arbeit übernimmt), nach Trade-Größe (aggTrades-Einträge sind Sweeps; große Trades treiben den Impact und kosten bei falscher Signierung am meisten) und nach Volatilitätsregime (BVC schätzt σ\sigma, daher ist sein Fehler konstruktionsbedingt regimespezifisch).

Methode Gesamt Am Mid Enger Spread Breiter Spread Große Trades
Tick-Test
Quote-Regel
Lee-Ready (Lag 0)
Lee-Ready (5-Sek.-Lag)
BVC (Anteil pro Bar) n/a n/a

Genauigkeit ist nicht das Ziel

Klassifikationsgrenze und Ausführungs-Impact

Übernimm die disziplinierte Einordnung aus DeepLOB: Klassifikationsgenauigkeit ist kein Ergebnis, sondern ein Input für eines. Niemand handelt das Vorzeichen eines Trades — gehandelt wird etwas, das aus einem ganzen Strom von Vorzeichen berechnet wird, und Fehler propagieren dabei nicht neutral. Ein Klassifikator, der bei 10% der Trades zufällig falsch liegt, und einer, der systematisch bei den 10% großen, preisbewegenden Trades falsch liegt, erzeugen bei gleicher Genauigkeit sehr unterschiedlichen signierten Orderflow. Der Carry-Forward-Fehler des Tick-Tests korreliert gerade mit flachen Preisen — dem Regime, in dem signierter Flow informativ sein soll. Deshalb sind die obigen Aufschlüsselungen und nicht die Schlagzeilenzahl das eigentliche Ergebnis.

Mit Ground-Truth-Labels ist die nachgelagerte Frage direkt messbar: Schätze die gewünschte Größe aus den echten Vorzeichen, schätze sie aus den Vorzeichen jedes Klassifikators erneut und berichte die Differenz.

  • Price Impact. Regressiere Mid-Preisänderungen in festen Fenstern auf signierten Netto-Taker-Flow — der Schätzer ist in Slippage-Kostenmodellen festgelegt, und eine funktionierende Implementierung auf öffentlichen Binance-Daten findet sich in Almgren-Chriss: optimale Ausführung. Definiere ihn hier nicht neu; führe ihn zweimal aus und vergleiche den geschätzten Koeffizienten.
  • Order-Flow-Imbalance als Prädiktor. Berechne die Korrelation zwischen OFI und Forward Returns mit echten Vorzeichen sowie mit klassifizierten Vorzeichen. Der Rückgang dieser Korrelation ist die Kosten der Fehlklassifikation, ausgedrückt in der einzigen relevanten Einheit.
  • Toxizitätsmetriken. VPIN und die Trade-Flow-Imbalance-Features verbrauchen klassifiziertes Volumen; beide werden in Spread-Modellierung mit Machine Learning behandelt. Ihre Sensitivität gegenüber Klassifikationsfehlern ist auf dieselbe Weise messbar.

Was daraus folgt

Aufgelöste Orderflow-Karte

Wenn dein Handelsplatz ein Aggressor-Flag liefert, verwende es. Das gilt meistens für den größten Teil des Kryptomarkts.

Wenn nicht, ist Lee-Ready mit zeitgleichen Quotes der richtige Default — nicht weil es besonders genau wäre, sondern weil es der Standard ist und die Vergleichbarkeit mit drei Jahrzehnten veröffentlichter Schätzungen mehr wert ist als einige Genauigkeitspunkte, die du nicht überprüfen kannst. Die Fünf-Sekunden-Regel ist ein Fossil der Tape-Latenz der 1980er; ihre Reproduktion auf modernen Daten führt genau die Staleness ein, die sie beseitigen sollte. Wenn du nur Bars hast, ist BVC die einzige überhaupt funktionierende Option, und sie beantwortet eine andere Frage: eine Volumenaufteilung, kein Trade-Vorzeichen.

Miss in jedem Fall gegen die Labels, die Krypto kostenlos bereitstellt, schlüssle das Ergebnis nach Spread-Regime und Trade-Größe auf und berichte, was der Fehler nachgelagert kostet, statt nur den Prozentsatz korrekter Klassifikationen zu nennen.

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Dem Markt einen Schritt voraus

Abonniere unseren Newsletter für exklusive KI-Trading-Einblicke, Marktanalysen und Plattform-Updates.

Wir respektieren deine Privatsphäre. Jederzeit abbestellbar.