📝

Draft article

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

← 기사 목록으로
August 23, 2026
5분 소요

사이드 플래그가 없을 때의 거래 분류: 틱, 호가, Lee-Ready, BVC

사이드 플래그가 없을 때의 거래 분류: 틱, 호가, Lee-Ready, BVC
#microstructure
#trade-classification
#Lee-Ready
#order-flow
#deep-learning

먼저 대부분의 미시구조 교과서가 이 문제를 시작하는 방식부터 바로잡자. 이 블로그가 실제로 거래하는 거래소에서는 거래 방향을 추론할 필요가 없다. 방향이 필드로 제공되기 때문이다. 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 피드에서 부호가 있는 주문 흐름을 만들고 있다면 이 글은 필요 없다.

이 글이 필요한 경우는 네 가지이며, 모두 흔한 상황이다.

  1. 바 데이터만 있는 경우. 과거 OHLCV는 대부분의 공급업체가 판매하고 대부분의 백테스트가 사용하는 데이터다. 거래 스트림이 아예 보관되지 않았거나 연구 가치보다 비용이 큰 경우다. 아래에서 다룰 BVC 문제가 여기에 해당한다.
  2. 거래소가 매수·매도 측을 표시하지 않는 경우. 소규모 CEX, 주식 TAQ 형식 데이터셋, 2017년 이전 아카이브 대부분은 가격과 수량만 제공한다.
  3. DEX 및 온체인 흐름. 스왑 이벤트는 어느 토큰이 어느 방향으로 이동했는지는 알려주지만, 라우팅된 멀티홉 거래나 애그리게이터에서 공격자 측을 판별할 플래그는 없다.
  4. 공개 연구를 재현하는 경우. Kyle 람다 추정, VPIN, 스프레드 분해 등 미시구조 문헌 대부분은 Lee-Ready로 분류한 데이터에 기반한다. 따라서 비교 가능성을 확보하려면 실제 측을 읽는 대신 해당 분류기를 실행해야 한다.

이제 규칙 세트, 가장 중요한 실패 모드, 대량 거래량 지름길, 그리고 전체를 측정하는 프로토콜을 살펴본다. 암호화폐는 주식 문헌에 없던 레이블을 제공하기 때문이다.

틱 테스트

틱 테스트 이벤트 시퀀스

현재 거래 가격 PtP_t를 직전 거래 가격과 비교한다.

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}

직관은 간단하다. 매수 주도 거래는 매도호가를 들어 올려 가격을 올리고, 매도 주도 거래는 매수호가를 때려 가격을 내린다. 가격 변화가 0인 틱은 이전 부호를 이어간다. 대부분의 오류가 여기서 생긴다. 틱 스프레드가 한 단위뿐인 유동성 높은 상품에서는 연속 거래 대부분이 같은 가격에 체결되기 때문이다.

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()으로 제공된다. 두 번째 구현을 유지하지 말고 이를 재사용하라.

호가 규칙

매수·매도호가 구조 안의 거래

연속 거래를 비교하는 대신 거래 가격을 당시의 중간가격 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

호가 규칙은 틱 테스트보다 확실히 많은 정보를 사용하지만 다른 지점에서 실패한다. 정확히 중간가격에 체결된 거래에 대해서는 아무것도 말해주지 않는다. 스프레드가 좁고 틱 단위로 제한된 상품에서는 이것이 예외가 아니라 체결 기록의 큰 비중을 차지한다. 규칙에는 대체 방법이 필요하며, 그것이 Lee-Ready다.

Lee-Ready: 하이브리드와 오래된 호가 가정

거래 스트림과 지연된 호가 표면

Lee와 Ready(1991)는 두 방법을 결합했다. 호가 규칙을 적용하고, 중간가격에 체결된 거래에는 틱 테스트를 대체로 사용한다.

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

논문에서 더 흥미로운 부분은 정당화 없이 자주 재현되는 대목이다. Lee와 Ready는 단순히 하이브리드를 제안한 것이 아니라 각 거래를 5초 전 유효했던 호가와 비교하라고 했다. 이 수치는 시장에 대한 모델링 선택이 아니라 보고 지연이라는 인공물을 보정하기 위한 것이다. 1980년대 NYSE 통합 테이프에서는 거래와 호가가 서로 다른 경로와 지연으로 전파되었고, 호가는 자신이 속한 거래보다 먼저 도착하는 경향이 있었다. 따라서 거래를 동시에 인쇄된 호가와 비교하면 이미 그 거래를 반영한 호가와 비교하게 되어, 시장을 움직인 바로 그 거래의 부호를 뒤집게 된다.

이를 암호화폐에 이식할 때 따라오는 결론은 두 가지다.

5초 규칙은 낡았으며 적용하면 오히려 해롭다. 이 규칙은 이제 존재하지 않는 전파 지연을 보정한다. 주식시장은 1990년대 중반 무렵 이 간극을 줄였다. 암호화폐 거래소의 거래 스트림과 호가 델타 스트림은 같은 시퀀스 공간을 공유하고 동일한 매칭 엔진이 마이크로초 단위로 타임스탬프를 찍는다. BTCUSDT에서 5초는 동기화 허용 오차가 아니라 수백 번의 호가 업데이트다. 호가를 5초 늦추는 것은 오래된 호가를 고치는 게 아니라 만들어내는 일이다. 직접 수집한 데이터에서 실제 오프셋을 측정한 경우가 아니라면 quote_lag=0을 사용하라.

그러나 근본 문제는 새로운 형태로 남는다. 이제 호가의 낡음은 거래소가 아니라 데이터 파이프라인의 책임이다. WebSocket 델타 스트림에서 호가를 재구성하고 벽시계 타임스탬프로 거래를 결합하면, 버퍼링·스냅샷 재동기화 정책·시계에 따라 부호와 크기가 달라지는 지연이 생긴다. 호가를 읽는 분류기를 믿기 전에 측정하라. 구간을 재생하고 거래를 호가 상태에 결합한 뒤, 결합된 [bid, ask] 바깥에 놓인 거래의 비율을 확인한다. 결합된 매도호가보다 높은 가격에 체결된 거래는 매칭에 사용한 호가가 낡았음을 증명한다.

수정 방법은 Lee와 Ready의 방법과 다르지만 실패 모드는 같다.

대량 거래량 분류

방향별 클래스의 거래량 군집

Easley, Lopez de Prado, O'Hara(2012)는 다른 문제를 다룬다. 개별 거래를 전혀 볼 수 없다면 어떻게 할 것인가? BVC는 바 안의 집계 거래량을 표준화한 가격 변화의 표준정규 CDF로 나눠 분류한다.

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}}

여기서 ΔPt=PtclosePtopen\Delta P_t = P_t^{\text{close}} - P_t^{\text{open}}이고 σΔP\sigma_{\Delta P}는 바 가격 변화의 표준편차다.

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는 더 나쁜 도구가 아니라 다른 도구다. 세 가지 특성이 그 이유를 말해준다. BVC는 개별 거래를 분류하지 않고 비율을 만든다. 따라서 변화 없이 마감한 바는 그 안의 모든 거래에 동전 던지기를 하는 대신 50/50으로 나뉜다. 사용자가 거래별 부호를 필요로 한다면 BVC는 이를 제공할 수 없다. BVC는 테이프가 아니라 바에서 작동하며, 바로 그것이 핵심이다. 거래 단위 데이터가 없을 때 작동하는 이 글의 유일한 방법이다. 또한 σ\sigma는 결과를 바꾸는 자유 파라미터다. 전체 표본 추정은 미래를 보는 셈이고, 짧은 롤링 추정은 Φ\Phi를 포화시켜 모든 바를 0/100 쪽으로 민다. 이 선택은 기본값으로 둘 일이 아니라 민감도 분석을 해야 한다.

원 논문은 VPIN 추정을 위해 BVC가 거래 단위 분류를 충분히 가깝게 추적한다고 보고한다. 구체적인 정확도는 바 크기, 상품, σ\sigma 추정기에 크게 좌우되며, 2차 자료의 수치는 세 가지를 모두 밝히는 경우가 드물다. 주식 연구의 수치를 그대로 가져오지 말고 측정하라.

분류에 특화된 유일한 특징

강조된 미시구조 신호

규칙 대신 이 작업을 하도록 모델을 학습한다면 대부분의 특징 집합은 이미 공개되어 있다. 스프레드, 깊이 불균형, 거래 흐름 불균형, 롤링 실현 변동성은 머신러닝을 이용한 스프레드 모델링에 표로 정리되어 구현되어 있고, DeepLOB에는 다중 레벨 호가 특징이 있다. 그것들을 사용하라.

이 문제에만 해당하고 두 자료 모두에는 없는 특징은 스프레드 안에서 거래가 차지하는 위치다.

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

매수호가에서 체결되면 0, 매도호가에서는 1, 중간가격에서는 0.5가 된다. 이는 호가 규칙의 연속적인 일반화다. 중간가격을 임계값으로 삼는 대신 거리를 모델에 제공하므로, 모델은 0.95의 거래가 거의 확실한 매수인 반면 0.55는 정보량이 거의 없음을 학습할 수 있다. [0,1][0, 1] 밖의 값은 앞 절에서 설명한 낡은 호가 진단 신호이므로 잘라내지 말고 기록할 가치가 있다.

이 특징들로 부스팅 기준선을 만들려면 머신러닝을 이용한 스프레드 모델링에 이미 공개된 파이프라인에서 목적함수만 이진 분류로 바꾸면 된다. 특히 정화·엠바고가 포함된 walk-forward CV도 그대로 사용한다. 일반 TimeSeriesSplit으로 미시구조 분류기를 평가하지 말라. 해당 글과 워크포워드 최적화가 결과가 낙관적으로 나오는 이유를 설명한다. 호가 스냅샷을 대상으로 한 시퀀스 모델에서는 DeepLOB가 기준 아키텍처이며, 그 글의 학습 고려사항도 그대로 적용된다.

제대로 측정하기: 암호화폐가 레이블을 제공한다

레이블이 있는 암호화폐 시장 데이터 스트림

주식 거래 분류 문헌의 모든 정확도는 진실의 대리값—TORQ, 감사 추적 하위표본, 단일 거래소의 내부 기록—을 기준으로 추정된다. 진짜 공격자 측이 공개되지 않았기 때문이다. 암호화폐는 그것을 공개한다. Binance aggTradesisBuyerMaker는 모든 거래에 대한 레이블이며 무료다. 따라서 누구나 이 실험을 수행할 수 있고, 암호화폐 대상으로 출판된 결과는 아직 찾지 못했다. 프로토콜은 다음과 같다.

데이터. 한 달치 BTCUSDT aggTrades와 호가 티커 스트림을 결합해 각 거래에 직전의 매수·매도호가를 붙인다. 스프레드가 넓을 때 어떻게 되는지가 핵심이므로 유동성이 얇은 알트코인에서도 반복한다.

레이블. direction = -1 if is_buyer_maker else +1. 그런 다음 플래그를 버리고 가격과 호가 데이터만으로 각 방법을 실행한다.

방법. 틱 테스트, 호가 규칙, quote_lag=0의 Lee-Ready, 역사적 5초 지연을 적용한 Lee-Ready(낡은 규칙이 만드는 손상을 정량화), 그리고 여러 바 크기의 BVC를 실제 바별 매수 거래량 비율과 비교한다.

분해. 전체 정확도는 가장 덜 흥미로운 수치다. 스프레드 레짐별(한 틱 대 넓은 스프레드 — 스프레드가 고정된 곳에서는 틱 테스트의 전달이 무너져야 한다), 중간가격 체결 여부별(호가 규칙이 보류하고 Lee-Ready의 대체가 모든 일을 하는 구간을 분리), 거래 규모별(aggTrades 항목은 스윕이며 큰 거래는 가격 충격을 만들고 부호를 잘못 붙였을 때 비용이 가장 크다), 변동성 레짐별(BVC의 σ\sigma는 추정되므로 오차가 구조적으로 레짐에 의존한다)로 나눠라.

방법 전체 중간가격 좁은 스프레드 넓은 스프레드 대규모 거래
틱 테스트
호가 규칙
Lee-Ready (지연 0)
Lee-Ready (지연 5초)
BVC (바별 비율) 해당 없음 해당 없음

정확도가 목표는 아니다

분류 경계와 실행 영향

DeepLOB의 문제 설정 방식을 빌리면, 분류 정확도는 결과가 아니라 결과를 위한 입력이다. 누구도 거래의 부호 자체를 거래하지 않는다. 전체 부호 스트림에서 계산한 것을 거래하며, 오류는 중립적으로 전파되지 않는다. 10%의 거래를 무작위로 틀리는 분류기와, 가격을 움직이는 대규모 거래 10%를 체계적으로 틀리는 분류기는 정확도가 같아도 전혀 다른 부호 있는 주문 흐름을 만든다. 틱 테스트의 전달 실패는 가격이 평평한 구간과 특별히 상관된다. 바로 그 구간에서 부호 있는 흐름이 유용해야 한다. 그러므로 위의 분해가 헤드라인 수치보다 진짜 결과다.

실제 레이블이 있으면 하류의 질문을 직접 측정할 수 있다. 실제 부호로 관심 있는 양을 추정하고, 각 분류기의 부호로 다시 추정한 뒤 차이를 보고하라.

  • 가격 충격. 고정된 구간에서 부호가 있는 순 테이커 흐름으로 중간가격 변화를 회귀한다. 추정량은 슬리피지 비용 모델에 이미 정의되어 있고 공개 Binance 데이터의 구현은 Almgren-Chriss 최적 실행에 있다. 여기서 재정의하지 말고 두 번 실행해 적합 계수를 비교하라.
  • 예측변수로서의 주문 흐름 불균형. 실제 부호와 분류 부호 각각에서 OFI와 미래 수익률의 상관관계를 계산한다. 상관관계의 저하 자체가 오분류 비용이며, 유일하게 중요한 단위로 표시된다.
  • 독성 지표. VPIN과 거래 흐름 불균형 특징은 분류된 거래량을 사용하는 지표다. 둘 다 머신러닝을 이용한 스프레드 모델링에 설명되어 있다. 분류 오류에 대한 민감도도 같은 방식으로 측정할 수 있다.

이것이 의미하는 바

해결된 주문 흐름 지도

거래소가 공격자 플래그를 제공한다면 그것을 사용하라. 대부분의 경우 암호화폐 거래소는 그렇게 한다.

그렇지 않다면 동시 호가를 사용하는 Lee-Ready가 올바른 기본값이다. 정확해서가 아니라 표준이기 때문이다. 검증할 수 없는 몇 퍼센트의 정확도보다 30년간 발표된 추정치와 비교 가능하게 만드는 편이 가치 있다. 5초 규칙은 1980년대 시세 테이프 지연의 화석이다. 현대 데이터에 이를 재현하면 제거하려 했던 바로 그 낡은 호가를 주입한다. 바 데이터만 있다면 BVC가 유일하게 작동하는 선택지이며, 거래 부호가 아니라 거래량 분할이라는 다른 질문에 답한다.

어떤 경우든 암호화폐가 무료로 제공하는 레이블과 비교해 측정하고, 스프레드 레짐과 거래 규모별로 결과를 나눠, 몇 퍼센트가 맞았는지가 아니라 오류가 하류에 초래한 비용을 보고하라.

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

시장에서 앞서 나가세요

뉴스레터를 구독하여 독점적인 AI 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.