오더북(호가창)이란?
오더북(Order Book)은 특정 자산의 매수·매도 주문이 가격대별로 쌓여 있는 실시간 데이터입니다. 호가창이라고도 불리며, 시장의 수급 구조를 가장 직접적으로 보여주는 데이터입니다. 퀀트 트레이더와 자동매매 시스템에서 오더북 분석은 단타·스캘핑 전략의 핵심입니다.
오더북에서 읽을 수 있는 정보:
- 매수벽(Bid Wall): 특정 가격에 대량 매수 주문이 걸려 있는 지지선
- 매도벽(Ask Wall): 특정 가격에 대량 매도 주문이 걸려 있는 저항선
- 스프레드(Spread): 최우선 매수호가와 매도호가의 차이
- 유동성 불균형: 매수/매도 물량의 비대칭
파이썬으로 오더북 데이터 수집
ccxt 라이브러리를 사용하면 주요 거래소의 오더북 데이터를 간편하게 가져올 수 있습니다:
import ccxt
import pandas as pd
exchange = ccxt.binance()
def fetch_orderbook(symbol: str = "BTC/USDT", limit: int = 50) -> dict:
"""오더북 데이터 수집 및 DataFrame 변환"""
ob = exchange.fetch_order_book(symbol, limit=limit)
bids_df = pd.DataFrame(ob["bids"], columns=["price", "quantity"])
asks_df = pd.DataFrame(ob["asks"], columns=["price", "quantity"])
bids_df["side"] = "bid"
asks_df["side"] = "ask"
bids_df["cumulative"] = bids_df["quantity"].cumsum()
asks_df["cumulative"] = asks_df["quantity"].cumsum()
return {
"bids": bids_df,
"asks": asks_df,
"spread": ob["asks"][0][0] - ob["bids"][0][0],
"mid_price": (ob["asks"][0][0] + ob["bids"][0][0]) / 2,
"timestamp": ob["timestamp"],
}
data = fetch_orderbook()
print(f"Mid Price: {data['mid_price']:,.2f}")
print(f"Spread: {data['spread']:.2f}")
print(f"n매수 상위 5호가:")
print(data["bids"].head())
print(f"n매도 상위 5호가:")
print(data["asks"].head())
오더북 불균형(Order Book Imbalance) 지표
오더북 불균형은 가장 기본적이면서도 강력한 단기 방향 예측 지표입니다. 매수 물량이 매도 물량보다 많으면 단기 상승 압력, 반대면 하락 압력으로 해석합니다:
import numpy as np
def order_book_imbalance(bids_df, asks_df, depth: int = 10) -> dict:
"""
오더북 불균형(OBI) 계산
OBI = (bid_vol - ask_vol) / (bid_vol + ask_vol)
범위: -1(매도 압도) ~ +1(매수 압도)
"""
bid_vol = bids_df["quantity"].iloc[:depth].sum()
ask_vol = asks_df["quantity"].iloc[:depth].sum()
obi = (bid_vol - ask_vol) / (bid_vol + ask_vol)
# 가중 OBI: 가격이 가까운 호가에 더 높은 가중치
weights = np.exp(-np.arange(depth) * 0.3)
w_bid = (bids_df["quantity"].iloc[:depth].values * weights).sum()
w_ask = (asks_df["quantity"].iloc[:depth].values * weights).sum()
weighted_obi = (w_bid - w_ask) / (w_bid + w_ask)
return {
"obi": round(obi, 4),
"weighted_obi": round(weighted_obi, 4),
"bid_volume": round(bid_vol, 4),
"ask_volume": round(ask_vol, 4),
"interpretation": "매수 우세" if obi > 0.1 else
"매도 우세" if obi < -0.1 else "중립"
}
result = order_book_imbalance(data["bids"], data["asks"])
for k, v in result.items():
print(f"{k:>16}: {v}")
매수벽·매도벽 탐지 알고리즘
대량 주문이 걸린 가격대(벽)를 자동 탐지하면 지지선과 저항선을 데이터 기반으로 파악할 수 있습니다:
def detect_walls(df: pd.DataFrame, std_multiplier: float = 2.0) -> pd.DataFrame:
"""
평균 + N*표준편차 이상의 대량 주문을 벽으로 탐지
"""
mean_qty = df["quantity"].mean()
std_qty = df["quantity"].std()
threshold = mean_qty + std_multiplier * std_qty
walls = df[df["quantity"] >= threshold].copy()
walls["strength"] = walls["quantity"] / mean_qty
walls["strength_label"] = walls["strength"].apply(
lambda x: "🔴 초강력" if x > 5 else "🟠 강력" if x > 3 else "🟡 보통"
)
return walls.sort_values("quantity", ascending=False)
bid_walls = detect_walls(data["bids"])
ask_walls = detect_walls(data["asks"])
print("=== 매수벽 (지지선) ===")
print(bid_walls[["price", "quantity", "strength_label"]].to_string(index=False))
print("n=== 매도벽 (저항선) ===")
print(ask_walls[["price", "quantity", "strength_label"]].to_string(index=False))
실시간 오더북 스트리밍과 스냅샷
WebSocket을 통한 실시간 오더북 스트리밍으로 더 빠른 데이터 수집이 가능합니다:
import asyncio
import json
import websockets
from collections import deque
from datetime import datetime
class OrderBookStream:
"""바이낸스 WebSocket 오더북 실시간 수집"""
def __init__(self, symbol: str = "btcusdt", buffer_size: int = 1000):
self.symbol = symbol
self.url = f"wss://stream.binance.com:9443/ws/{symbol}@depth20@100ms"
self.snapshots = deque(maxlen=buffer_size)
self.running = False
async def start(self, duration_sec: int = 60):
self.running = True
async with websockets.connect(self.url) as ws:
start = datetime.now()
while self.running:
msg = json.loads(await ws.recv())
snapshot = {
"ts": datetime.now().isoformat(),
"bids": msg["bids"][:10],
"asks": msg["asks"][:10],
"bid_total": sum(float(b[1]) for b in msg["bids"][:10]),
"ask_total": sum(float(a[1]) for a in msg["asks"][:10]),
}
snapshot["obi"] = (
(snapshot["bid_total"] - snapshot["ask_total"])
/ (snapshot["bid_total"] + snapshot["ask_total"])
)
self.snapshots.append(snapshot)
elapsed = (datetime.now() - start).seconds
if elapsed >= duration_sec:
break
return list(self.snapshots)
# 사용법
# stream = OrderBookStream("btcusdt")
# snapshots = asyncio.run(stream.start(duration_sec=30))
OBI 기반 자동매매 전략
오더북 불균형을 신호로 사용하는 단기 매매 전략을 자동매매 봇에 통합할 수 있습니다:
class OBIStrategy:
"""오더북 불균형 기반 스캘핑 전략"""
def __init__(self, obi_threshold: float = 0.3,
confirmation_count: int = 3,
take_profit_pct: float = 0.001,
stop_loss_pct: float = 0.0005):
self.obi_threshold = obi_threshold
self.confirmation_count = confirmation_count
self.take_profit_pct = take_profit_pct
self.stop_loss_pct = stop_loss_pct
self.obi_history = deque(maxlen=20)
self.position = None
def on_orderbook(self, obi: float, mid_price: float, spread: float):
"""오더북 업데이트마다 호출"""
self.obi_history.append(obi)
if len(self.obi_history) < self.confirmation_count:
return None
recent = list(self.obi_history)[-self.confirmation_count:]
# 포지션이 없을 때: 진입 신호
if self.position is None:
if all(v > self.obi_threshold for v in recent):
self.position = {
"side": "long", "entry": mid_price,
"tp": mid_price * (1 + self.take_profit_pct),
"sl": mid_price * (1 - self.stop_loss_pct),
}
return {"action": "BUY", "price": mid_price, "reason": f"OBI {obi:.3f}"}
elif all(v < -self.obi_threshold for v in recent):
self.position = {
"side": "short", "entry": mid_price,
"tp": mid_price * (1 - self.take_profit_pct),
"sl": mid_price * (1 + self.stop_loss_pct),
}
return {"action": "SELL", "price": mid_price, "reason": f"OBI {obi:.3f}"}
# 포지션이 있을 때: 익절/손절
elif self.position:
if self.position["side"] == "long":
if mid_price >= self.position["tp"]:
self.position = None
return {"action": "CLOSE_LONG", "price": mid_price, "reason": "TP"}
elif mid_price <= self.position["sl"]:
self.position = None
return {"action": "CLOSE_LONG", "price": mid_price, "reason": "SL"}
return None
오더북 분석 시 주의사항
- 스푸핑(Spoofing) 주의: 대량 주문을 걸었다가 체결 직전에 취소하는 허위 주문이 존재합니다. 벽이 자주 사라지는 가격대는 스푸핑일 수 있습니다.
- 빙산 주문(Iceberg Order): 대형 기관은 주문을 잘게 쪼개서 오더북에 드러나지 않게 체결합니다. 오더북에 보이는 것이 전부가 아닙니다.
- 거래소별 차이: 같은 자산이라도 거래소마다 오더북 구조가 다릅니다. 슬리피지 최적화와 함께 거래소별 유동성을 비교하세요.
- 레이턴시: 오더북 데이터는 밀리초 단위로 변합니다. REST API보다 WebSocket 스트리밍을 사용해야 합니다.
- OBI 단독 사용 금지: 오더북 불균형은 이동평균 같은 가격 기반 지표와 결합했을 때 신뢰도가 높아집니다.
오더북 깊이 시각화
import matplotlib.pyplot as plt
def plot_orderbook_depth(bids_df, asks_df, title="Order Book Depth"):
"""오더북 깊이 차트 시각화"""
fig, ax = plt.subplots(figsize=(12, 6))
ax.fill_between(bids_df["price"], bids_df["cumulative"],
alpha=0.4, color="green", label="Bids (매수)")
ax.fill_between(asks_df["price"], asks_df["cumulative"],
alpha=0.4, color="red", label="Asks (매도)")
ax.set_xlabel("Price")
ax.set_ylabel("Cumulative Volume")
ax.set_title(title)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("orderbook_depth.png", dpi=150)
plt.show()
# plot_orderbook_depth(data["bids"], data["asks"])
핵심 정리
오더북 분석은 가격 데이터만으로는 보이지 않는 시장 수급 구조를 파악하는 핵심 기법입니다. OBI(오더북 불균형) 지표로 단기 방향성을 예측하고, 벽 탐지 알고리즘으로 주요 지지·저항선을 데이터 기반으로 도출할 수 있습니다. 실전 자동매매에서는 반드시 스푸핑 필터링, 빙산 주문 고려, 다중 지표 확인을 병행해야 합니다.