訂單流指標中的主動方分類:報價規則、跳動規則與未知成交
Aggressor-Side Classification in Order-Flow Indicators: Quote Rule, Tick Rule, and Unknown Trades
訂單流指標可用報價規則或跳動規則推斷成交的主動方,並應把無法可靠分類的成交標記為未知,而非強行歸入買方或賣方。文章説明兩種方法的判定條件與限制,並建議記錄分類方法及來源,將未知成交量納入總成交量,明確計算 delta。
A trade print tells us that a transaction occurred at a particular price and size. It does not always tell us whether the buyer or seller initiated it. Yet footprint charts, bid/ask volume displays, and cumulative delta all depend on assigning trades to a side. A classification error made at ingestion can propagate into every downstream visualization.
There is no universally correct fallback for missing or stale quote data. The useful engineering question is not simply “Was this a buy or a sell?” It is “What evidence supports this classification, and what do we do when that evidence is incomplete?”
Aggressor side is not trade direction
Every executed trade has a buyer and a seller. “Buyer-initiated” generally means an incoming order crossed the spread to execute against resting sell liquidity; “seller-initiated” means the analogous action against resting buy liquidity. It does not mean that the buyer knows the future direction of the market or that a buy signal has appeared.
Some feeds provide an aggressor-side indicator. Others provide trades and quotes that let you infer one. Verify the feed's documented semantics before treating either as ground truth. Equities may require a consolidated best bid/offer; a futures feed may supply venue-specific inside quotes. These inputs are not interchangeable without a data-policy decision.
Method 1: The quote rule
When an appropriate bid/ask snapshot can be aligned with the trade, a common classification rule is:
- Trade at or above the contemporaneous ask: buyer-aggressed candidate.
- Trade at or below the contemporaneous bid: seller-aggressed candidate.
- Trade inside the spread, or with inconsistent quotes: unknown. “Contemporaneous” does a lot of work here. Quote and trade messages may be delivered on different channels, arrive out of order, or use timestamps with different precision. Comparing a trade with the quote that happens to be most recently received can silently contaminate the result. Validate source, ordering, quote age, and whether the quote was actually applicable at execution time.
If both comparisons appear true because the market is locked or crossed, do not let branch order arbitrarily pick a winner: flag the quote as unusable under your documented policy.
Method 2: The tick rule
When reliable quote context is unavailable, the tick rule uses the change from the previous trade price:
- Higher than the previous print → buy candidate.
- Lower than the previous print → sell candidate.
- Same price → apply an explicit zero-tick policy, such as retaining the previous nonzero price-change direction, or mark unknown. This is a heuristic, not a substitute for a contemporaneous quote or an exchange-provided aggressor flag. It can behave poorly when spreads are wide or the tape contains gaps or reordering. In a report or indicator settings panel, name the method rather than simply labeling the output “true buy volume.” A classification function with an honest third state The example below is standalone C#-style application logic, not a drop-in NinjaScript indicator. It illustrates a deliberately conservative quote policy, an optional tick-rule fallback, and a separate unknown outcome. enum Side { Buy, Sell, Unknown }
sealed class TradePrint
{
public double Price;
public DateTime TimeUtc;
}
sealed class Quote
{
public double Bid;
public double Ask;
public DateTime TimeUtc;
}
static Side Classify(
TradePrint trade,
Quote quote,
double? previousTradePrice,
Side previousTickDirection,
TimeSpan maxQuoteAge)
{
bool quoteUsable = quote != null
&& quote.Bid > 0
&& quote.Ask > quote.Bid
&& trade.TimeUtc >= quote.TimeUtc
&& trade.TimeUtc - quote.TimeUtc <= maxQuoteAge;
if (quoteUsable)
{
if (trade.Price >= quote.Ask) return Side.Buy;
if (trade.Price <= quote.Bid) return Side.Sell;
return Side.Unknown; // Inside spread: no forced side
}
if (!previousTradePrice.HasValue) return Side.Unknown;
if (trade.Price > previousTradePrice.Value) return Side.Buy;
if (trade.Price < previousTradePrice.Value) return Side.Sell;
return previousTickDirection; // Configurable zero-tick policy
}
The time threshold is a policy input, not a universal constant. Timestamp checks cannot fix unsynchronized providers or missing quote changes. Also, previousTickDirection must be maintained from the previous nonzero price change, not accidentally overwritten with Unknown on every tied print.
A production pipeline should preserve a classification method and ideally a confidence/provenance field alongside the side label. Otherwise, a chart may combine exchange flags, quote-rule guesses, and tick-rule guesses while making them look equally certain.
What should delta do with unknown volume?
For each print, aggregate its size into exactly one of three buckets: buy, sell, or unknown. Then define your displayed delta explicitly:
classified_delta = buy_volume - sell_volume
classified_volume = buy_volume + sell_volume
all_traded_volume = classified_volume + unknown_volume
Unknown volume should not disappear from total traded volume. Conversely, forcing all unknown prints into buy or sell overstates the certainty of delta. Showing an “unclassified %” beside delta can be more honest than a falsely precise histogram.
A useful diagnostic is to compare classification rates by instrument, session, feed, and replay/live mode. A sudden rise in unknown volume may indicate a quote-availability change or timestamp alignment issue—not a change in market sentiment.
Separate ingestion, classification, aggregation, and rendering
Keep these stages independent:
- Ingestion: validate timestamps, ordering, trade sizes, and quote provenance.
- Classification: apply a selected policy and record the method used.
- Aggregation: map prices to integer tick indices and maintain buy/sell/unknown counters without floating-point price-key drift.
- Rendering: consume consistent snapshots rather than mutable accumulation buffers. This separation lets you replace a fallback method without rewriting the footprint renderer. It also makes feed comparisons testable: run the same recorded prints through two classification policies and inspect where they disagree.
For wider context on order-book, tape, and delta concepts, see the ICF Market volume and order-flow education series. The classification policy described here is an educational design example, not a claim about any proprietary ICF Market implementation.
The test I would run before shipping
Build a small replay fixture with: normal at-bid and at-ask prints, an inside-spread trade, a locked quote, a stale quote, the first print of a session, and a sequence of same-price trades. Assert that all volume remains accounted for and that the output names its source policy.
Discussion: If you have both a feed-supplied aggressor flag and the quote rule, do you treat one as authoritative, record the disagreement, or surface both?
Disclosure: This article was prepared with AI assistance. The code is illustrative and has not been tested against a live market-data feed. This is educational material, not trading advice.
來源:DEV Community · dev.to