[코인 거래로 알아보는 매칭엔진] #2. 오더북(Order Book)의 구조

이 글은 코인 거래로 알아보는 매칭엔진 시리즈의 2번째 글입니다.

개요

앞 편에서는 오더북을 "아직 체결되지 않은 주문의 목록"이라는 한 문장으로만 소개했습니다. 매칭엔진이 주문을 받을 때마다 들여다보는 것이 바로 이 장부입니다. 그래서 매칭 알고리즘으로 넘어가기 전에 오더북이 실제로 어떻게 생겼는지부터 확인해 두려고 합니다.

이 글에서는 Upbit, Coinbase Exchange, Binance, Kraken의 공식 API 문서를 근거로 세 가지를 살펴봅니다.

  • 매수 호가와 매도 호가가 쌓이는 모양, 그리고 그 사이의 스프레드
  • 지정가 주문과 시장가 주문이 오더북과 만나는 방식
  • 오더북의 깊이가 시장가 주문의 체결 가격을 어떻게 바꾸는지

오더북은 어떻게 생겼나


Bid와 Ask, 그리고 스프레드

오더북은 두 쪽으로 나뉩니다. 사겠다는 주문이 모인 Bid(매수 호가)와 팔겠다는 주문이 모인 Ask(매도 호가)입니다.

두 쪽은 정렬 방향이 반대입니다. Bid는 높은 가격부터, Ask는 낮은 가격부터 늘어섭니다. 비싸게 사겠다는 매수 주문과 싸게 팔겠다는 매도 주문이 체결에 가장 가깝기 때문입니다. Kraken 문서도 오더북 검증값을 계산할 때 ask는 "sorted by price low to high", bid는 "sorted by price high to low"로 정렬하라고 적고 있습니다.

실제 값으로 보겠습니다. 아래는 Upbit 호가 정보 조회 API 문서에 실린 KRW-BTC 응답 예시의 앞 두 호가입니다.

구분1호가2호가
매도 호가 (ask_price)148,520,000원148,590,000원
매도 잔량 (ask_size)0.0134662 BTC0.00075646 BTC
매수 호가 (bid_price)148,490,000원148,480,000원
매수 잔량 (bid_size)0.04296774 BTC0.00336561 BTC

Upbit는 호가를 쌍으로 묶어 내려줍니다. count 파라미터 설명에 따르면 응답은 "최고 매수 호가 - 최저 매도 호가의 쌍을 기준으로" 지정한 개수만큼 반환되고, 기본값은 30입니다. 그러니 1호가는 가장 높은 매수 호가와 가장 낮은 매도 호가의 쌍, 즉 최우선 호가입니다.

최우선 매도 호가와 최우선 매수 호가의 차이를 Spread(스프레드)라고 부릅니다. 위 예시에서는 148,520,000 − 148,490,000 = 30,000원입니다.

이 간격 안에서는 거래가 일어나지 않습니다. 사려는 사람 중 가장 후한 사람도 148,490,000원까지만 내겠다고 했고, 팔려는 사람 중 가장 싸게 부른 사람도 148,520,000원 아래로는 팔지 않겠다고 했기 때문입니다. 누군가 이 간격을 넘어오는 주문을 넣어야 체결이 일어나고, 그 주문이 앞 편에서 본 Taker 주문입니다.


가격 레벨로 모아 보기

위 표의 148,520,000원 매도 잔량 0.0134662 BTC는 한 사람의 주문일 수도 있고 여러 주문의 합일 수도 있습니다. 호가창은 보통 같은 가격의 주문을 하나로 합친 Price Level(가격 레벨) 단위로 보여 줍니다.

Coinbase Exchange의 오더북 조회 API는 이 합치기 수준을 level 파라미터로 고르게 합니다.

level문서의 설명의미
1"The best bid, ask and auction info"최우선 호가만
2"Full order book (aggregated) and auction info"가격 레벨별로 합친 전체 오더북
3"Full order book (non aggregated) and auction info"개별 주문 단위의 전체 오더북

Level 1과 2에서 가격 레벨 하나가 무엇을 담는지도 명시되어 있습니다.

The size field is the sum of the size of the orders at that price, and num-orders is the count of orders at that price; size should not be multiplied by num-orders.

(size 필드는 해당 price에 있는 주문들의 수량 합계이고, num-orders는 그 price에 있는 주문의 개수입니다. size에 num-orders를 곱하면 안 됩니다.)

가격 레벨 하나는 가격, 잔량 합계, 주문 개수 세 값으로 요약됩니다. 반면 Level 3는 개별 주문을 전부 내려주기 때문에 문서는 WebSocket으로 실시간 오더북을 유지하려는 사용자에게만 권하고, polling으로 남용하면 접근이 제한될 수 있다고 경고합니다.

다른 거래소도 기본은 같습니다. Binance의 GET /api/v3/depth는 bids와 asks를 [가격, 수량] 쌍의 배열로 반환합니다. 조회 개수(limit)는 기본 100, 최대 5000인데, 1~100개는 요청 가중치(weight)가 5이고 1001~5000개는 250입니다. 오더북을 깊이 볼수록 조회 비용도 커지는 구조입니다. Upbit는 한 걸음 더 나아가 원화 마켓에서 level 파라미터로 가격 레벨을 더 굵게 묶어 볼 수 있게 합니다. 문서의 예시대로 KRW-BTC에 level=100000을 주면 10만 원 단위로 호가를 묶고, 그 구간에 있는 주문량의 합계를 size로 돌려줍니다.


오더북은 계속 바뀐다

REST API로 조회한 오더북은 한 순간을 찍은 사진입니다. 주문이 들어오고 체결되고 취소될 때마다 오더북은 바뀌기 때문에, 실시간 호가창은 다른 방식으로 유지합니다.

Kraken의 WebSocket v2 book 채널이 대표적인 예입니다. 처음 구독하면 현재 오더북 전체가 snapshot으로 오고, 이후로는 바뀐 가격 레벨만 update로 옵니다. 문서는 레벨이 사라지는 경우를 이렇게 규정합니다.

An update with "qty": 0 means that price level should be removed.

("qty": 0인 update는 해당 가격 레벨을 제거해야 한다는 뜻입니다.)

구독한 깊이 밖으로 밀려난 레벨은 qty: 0이 따로 오지 않으므로, 클라이언트가 매 update 뒤에 직접 잘라내야 합니다. 또 update마다 상위 10개 가격 레벨로 계산한 CRC32 checksum이 함께 오는데, 문서의 표현으로는 "Validating it confirms your local book is correctly synchronised with the exchange", 즉 내 오더북이 거래소와 맞게 동기화됐는지 확인하는 용도입니다.

여기서 눈여겨볼 점은 외부로 공개되는 갱신 단위가 개별 주문이 아니라 가격 레벨이라는 것입니다. 매칭엔진 안에서는 한 가격에 여러 주문이 줄을 서 있지만(Coinbase의 Level 3), 시장 참여자 대부분이 보는 오더북은 가격 레벨로 합쳐진 모습입니다.


주문 유형 — 지정가와 시장가


지정가 주문

Limit Order(지정가 주문)는 가격과 수량을 모두 정해서 내는 주문입니다. Upbit 문서의 정의는 다음과 같습니다.

지정가 주문은 사용자가 직접 설정한 매수/매도 단가, 또는 더 유리한 가격에 호가가 도달한 경우에만 체결되는 주문 유형입니다. 체결 단가의 상한/하한을 통제할 수 있지만, 시장 가격이 지정한 단가에 도달하지 않을 수 있으므로 체결을 보장할 수 없습니다.

지정가 주문이 오더북에 들어오면 두 갈래로 나뉩니다. 앞의 Upbit 예시 오더북(최우선 매수 148,490,000원, 최우선 매도 148,520,000원)에 매수 주문을 넣는다고 해 보겠습니다.

  • 148,500,000원 매수: 가장 싼 매도 호가(148,520,000원)보다 낮으므로 체결될 상대가 없습니다. 주문은 오더북에 남아 새 최우선 매수 호가가 되고, 스프레드는 20,000원으로 줄어듭니다. 오더북에 유동성을 더했으니 Maker 주문입니다.
  • 148,600,000원 매수: 148,600,000원 이하의 매도 호가가 있으므로 바로 체결됩니다. 낮은 가격의 매도 호가부터 차례로 체결하고, 수량이 남으면 그 나머지는 뒤에서 볼 IOC·FOK 같은 조건을 붙이지 않은 한 148,600,000원 매수 호가로 오더북에 남습니다. 즉시 체결된 부분은 Taker, 오더북에 남은 부분은 Maker입니다.

즉 지정가 주문이라고 해서 항상 Maker가 되는 것은 아닙니다. 가격이 스프레드를 넘어가면 지정가 주문도 Taker가 됩니다. 반드시 Maker로만 들어가고 싶을 때 쓰는 옵션이 post-only입니다. 세 거래소 모두 즉시 체결될 상황이면 주문을 거부하거나 취소하는 방식으로 구현합니다.

거래소이름문서의 설명
Coinbase Exchangepost_only"If any part of the order results in taking liquidity, the order will be rejected and no part of it will execute."
BinanceLIMIT_MAKER"a LIMIT order that will be rejected if the order immediately matches and trades as a taker"
Upbitpost_only"지정가 조건으로 부분 또는 전체에 대해 즉시 체결 가능한 상황인 경우 주문을 실행하지 않고 취소합니다."

시장가 주문

Market Order(시장가 주문)는 가격을 정하지 않고 지금 오더북에 있는 호가로 즉시 체결하는 주문입니다. Coinbase Exchange 문서는 지정가 주문과의 차이를 이렇게 정리합니다.

Market orders differ from limit orders in that they provide no pricing guarantees. ... Market orders execute immediately and no part of the market order goes on the open order book. Market orders are always considered takers and incur taker fees.

(시장가 주문은 가격을 보장하지 않는다는 점에서 지정가 주문과 다릅니다. ... 시장가 주문은 즉시 체결되며 어떤 부분도 오더북에 올라가지 않습니다. 시장가 주문은 항상 taker로 간주되어 taker 수수료가 부과됩니다.)

지정가 주문은 체결을 보장하지 않는 대신 가격을 보장하고, 시장가 주문은 가격을 보장하지 않는 대신 즉시 체결됩니다. 두 주문 유형은 이 교환 관계의 양 끝에 있습니다.

가격을 정하지 않는 대신 시장가 주문은 "얼마나"를 정해야 합니다. 여기에는 수량으로 정하는 방법과 금액으로 정하는 방법이 있고, 거래소마다 노출 방식이 조금씩 다릅니다.

거래소수량 지정금액 지정
Coinbase Exchangesizefunds
BinancequantityquoteOrderQty
Upbit매도만 (ord_type=market, volume)매수만 (ord_type=price, price에 총액)

Coinbase와 Binance는 매수와 매도 모두 두 방식 중 하나를 고를 수 있습니다. Upbit는 시장가 매수는 총액으로, 시장가 매도는 수량으로만 받습니다. "100만 원어치 사기"와 "0.01 BTC 팔기"만 가능한 구조입니다.

금액으로 지정하면 실제로 받는 수량은 오더북 상태에 따라 달라집니다. Binance 문서는 quoteOrderQty를 쓰면 "the correct quantity will be determined based on the market liquidity and quoteOrderQty"라고 설명합니다. 같은 금액이라도 오더북이 얼마나 두터운지에 따라 손에 쥐는 코인의 양이 달라진다는 뜻이고, 이것이 다음 장의 주제입니다.

참고로 Upbit에는 둘 사이에 있는 최유리 지정가 주문(ord_type=best)도 있습니다. "현재 시장에서 가장 유리한 상대 호가를 가격으로 하는 주문 유형"으로, 반드시 IOC나 FOK 조건과 함께 써야 합니다.


얼마나 기다릴 것인가 — GTC·IOC·FOK

지정가 주문에는 체결되지 않은 수량을 어떻게 할지 정하는 Time in Force(체결 조건)가 붙습니다. 가장 흔한 조건은 세 가지입니다.

조건체결되지 않은 수량Binance 문서(enums.md)의 설명
GTC (Good Til Canceled)취소할 때까지 오더북에 남는다"An order will be on the book unless the order is canceled."
IOC (Immediate Or Cancel)즉시 체결 가능한 만큼만 체결하고 나머지는 취소한다"An order will try to fill the order as much as it can before the order expires."
FOK (Fill or Kill)전량 즉시 체결될 수 없으면 전부 취소한다"An order will expire if the full order cannot be filled upon execution."

Coinbase Exchange는 여기에 GTT(Good Till Time)를 더해 네 가지를 지원하고, 지정하지 않으면 GTC가 기본값입니다. Upbit는 time_in_force를 선택 옵션으로 두고 ioc, fok, post_only 중에서 고르게 합니다. 설명은 Binance와 같아서 IOC는 "즉시 체결 가능한 수량만 부분 체결하고, 잔여 수량은 취소"하고, FOK는 "주문량 전량 체결 가능할 때만 주문을 실행하고, 아닌 경우 전량 주문 취소"합니다.

IOC와 FOK가 붙은 지정가 주문은 오더북에 남지 않습니다. 결국 주문 유형을 가르는 본질적인 질문은 "이 주문이 오더북에 남을 수 있는가"입니다. 지금까지 본 주문 중 오더북에 남아 다른 사람의 거래 상대가 될 수 있는 것은 IOC나 FOK가 붙지 않은 지정가 주문뿐입니다.


깊이와 슬리피지


Depth와 유동성

각 가격 레벨에 쌓인 잔량의 분포를 오더북의 Depth(깊이)라고 부릅니다. 최우선 호가 근처에 잔량이 많으면 오더북이 깊다고 하고, 적으면 얕다고 합니다. Upbit 호가 조회 응답도 호가별 잔량과 별도로 total_ask_size(전체 매도 잔량 합계)와 total_bid_size(전체 매수 잔량 합계)를 함께 내려줍니다.

스프레드와 깊이는 유동성의 서로 다른 면을 보여 줍니다. 스프레드는 아주 작은 수량을 지금 바로 거래할 때 치르는 비용이고, 깊이는 큰 수량을 거래할 때 가격이 얼마나 밀리는지를 결정합니다. 스프레드가 좁아도 최우선 호가의 잔량이 적으면 조금만 큰 주문이 들어와도 여러 가격 레벨을 넘나들게 됩니다. 앞의 Upbit 예시에서도 2호가 매도 잔량은 0.00075646 BTC뿐입니다.


시장가 주문이 호가를 먹어 들어가는 과정

설명을 위해 만든 가상의 오더북으로 시장가 매수 주문이 어떻게 체결되는지 따라가 보겠습니다.

구분가격잔량
매도100,300,000원0.5 BTC
매도100,200,000원0.3 BTC
매도100,100,000원0.2 BTC
매수100,000,000원0.4 BTC
매수99,900,000원0.6 BTC

최우선 매도 호가는 100,100,000원, 스프레드는 100,000원입니다. 여기에 0.6 BTC 시장가 매수 주문이 들어오면 매칭엔진은 가장 싼 매도 호가부터 차례로 체결합니다.

체결 가격체결 수량체결 금액
100,100,000원0.2 BTC20,020,000원
100,200,000원0.3 BTC30,060,000원
100,300,000원0.1 BTC10,030,000원
합계0.6 BTC60,110,000원

평균 체결가는 60,110,000 ÷ 0.6 ≈ 100,183,333원입니다. 주문을 넣기 전 호가창에 보이던 최우선 매도 호가(100,100,000원)보다 BTC당 약 83,333원, 비율로 약 0.083% 비싸게 샀습니다. 0.6 BTC를 전부 100,100,000원에 샀다면 60,060,000원이었을 테니 총 50,000원을 더 낸 셈입니다. 이렇게 주문 전에 보이던 가격과 실제 평균 체결가의 차이를 Slippage(슬리피지)라고 부릅니다.

체결 뒤의 오더북도 달라집니다. 100,100,000원과 100,200,000원 레벨은 잔량이 0이 되어 사라지고(Kraken이라면 qty: 0 update가 나갈 자리입니다), 100,300,000원 레벨에는 0.4 BTC가 남습니다. 최우선 매도 호가가 100,300,000원으로 올라가면서 스프레드는 300,000원으로 벌어집니다. 큰 시장가 주문 하나가 호가창의 한쪽을 깎아 내고 지나간 것입니다.

같은 주문이라도 오더북이 깊으면 결과가 다릅니다. 100,100,000원 레벨에 1 BTC가 쌓여 있었다면 0.6 BTC는 전부 그 가격에 체결되고 슬리피지는 0입니다. 주문 크기와 깊이의 비율이 슬리피지를 정합니다.

슬리피지를 제한하고 싶다면 앞에서 본 지정가 주문과 체결 조건을 조합하면 됩니다. 100,200,000원 IOC 지정가 매수로 0.6 BTC를 주문하면 100,200,000원 이하의 0.5 BTC만 체결되고 나머지 0.1 BTC는 취소됩니다. 같은 조건을 FOK로 넣으면 0.6 BTC 전량을 채울 수 없으므로 주문 전체가 취소됩니다. 시장가 주문의 즉시성을 얻으면서 최악의 가격에 상한을 거는 방법입니다.

마지막으로, 오더북은 앞 장에서 본 것처럼 쉬지 않고 갱신됩니다. 내가 호가창을 본 시점과 주문이 매칭엔진에 도착한 시점의 오더북은 같지 않을 수 있습니다. 가격이 빠르게 움직이는 시장에서는 이 시간차도 슬리피지에 더해집니다.


마무리

오더북은 Bid와 Ask 두 줄로 된 장부이고, 그 모양이 곧 시장의 유동성입니다.

  • Bid는 높은 가격부터, Ask는 낮은 가격부터 정렬되고, 두 최우선 호가의 차이가 스프레드입니다.
  • 거래소가 공개하는 오더북은 대부분 같은 가격의 주문을 합친 가격 레벨 단위이고, 실시간 오더북은 snapshot과 레벨 단위 update로 유지됩니다.
  • 지정가 주문은 가격을 보장하고 체결을 보장하지 않습니다. 스프레드를 넘지 않으면 오더북에 남아 Maker가 되고, 넘으면 Taker가 됩니다.
  • 시장가 주문은 체결을 보장하고 가격을 보장하지 않습니다. 가격 대신 수량이나 금액을 정하고, 오더북에는 남지 않습니다.
  • 시장가 주문이 여러 가격 레벨을 넘나들며 체결되면 평균 체결가가 최우선 호가에서 멀어지고, 그 차이가 슬리피지입니다. 오더북이 깊을수록 슬리피지는 작아집니다.

다음 편 예고

이번 편에서는 오더북을 가격 레벨 단위로, 말하자면 바깥에서 봤습니다. 그런데 Coinbase의 num-orders가 2 이상인 가격 레벨, 즉 같은 가격에 주문이 여러 개 줄 서 있을 때 시장가 주문이 들어오면 누구의 주문부터 체결될까요?

다음 편에서는 이 질문에 답하는 매칭 알고리즘을 다룹니다. 앞 편에서 소개한 Price-Time Priority를 자세히 들여다보고, 물량에 비례해 배분하는 방식 같은 다른 규칙과 비교해 보겠습니다.


References

전체 목차:

  1. [코인 거래로 알아보는 매칭엔진] #1. 매칭엔진의 역사와 진화
  2. [코인 거래로 알아보는 매칭엔진] #2. 오더북(Order Book)의 구조
  3. [코인 거래로 알아보는 매칭엔진] #3. 매칭 알고리즘
  4. [코인 거래로 알아보는 매칭엔진] #4. CEX vs DEX 매칭엔진 비교