암호화폐 algorithmic trading을 시작하면서 가장 먼저 마주하는 벽이 바로 히스토리컬 오더북(orderbook) 데이터 확보입니다. 실시간 데이터는 Binance API만으로 쉽게 가져올 수 있지만, 과거 특정 시점의 오더북 스냅샷을 구하려면 추가적인 방법이 필요합니다.
저는 지난 2년간 Binance 데이터를 활용한 백테스팅 시스템을 구축하며 여러 데이터 소스를 테스트했습니다. 이 글에서는 무료부터 유료까지 모든 현실적인 옵션을 실제 사용 경험에 기반해 정리합니다.
Binance 공식 제공 데이터
1. Binance API 오더북
가장 기본적인 방법은 Binance Public API를 사용하는 것입니다. 이 방법은 실시간 데이터에 최적화되어 있어 과거 데이터 조회에는 제약이 있습니다.
import requests
import time
def get_orderbook(symbol='BTCUSDT', limit=100):
"""
Binance API에서 실시간 오더북 조회
limit: 5, 10, 20, 50, 100, 500, 1000, 5000 가능
"""
url = f"https://api.binance.com/api/v3/depth"
params = {'symbol': symbol, 'limit': limit}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
return {
'lastUpdateId': data['lastUpdateId'],
'bids': [[float(p), float(q)] for p, q in data['bids']],
'asks': [[float(p), float(q)] for p, q in data['asks']],
'timestamp': time.time()
}
except requests.exceptions.RequestException as e:
print(f"API 요청 실패: {e}")
return None
사용 예시
orderbook = get_orderbook('BTCUSDT', 100)
if orderbook:
print(f"매수최우선가: {orderbook['bids'][0][0]}")
print(f"매도최우선가: {orderbook['asks'][0][0]}")
print(f"스프레드: {float(orderbook['asks'][0][0]) - float(orderbook['bids'][0][0])}")
한계점: Binance Public API는 실시간 데이터만 제공합니다. 2024년 3월 이후로_historical orders_ 엔드포인트가 제한되어 있어, 과거 특정 시점의 오더북을 직접 조회할 수 없습니다.
2. Binance Futures Historical Data
Binance Futures는 공식적으로 히스토리컬 데이터를 다운로드할 수 있는 페이지를 제공합니다.
# Binance Futures klines(OHLCV) + 트레이드 데이터 다운로드 예시
import requests
import pandas as pd
from datetime import datetime
def get_futures_klines(symbol='BTCUSDT', interval='1m', start_time=None, limit=1000):
"""
Binance Futures 히스토리컬 캔들 데이터 조회
interval: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
"""
base_url = "https://api.binance.com"
endpoint = "/api/v3/klines"
params = {
'symbol': symbol,
'interval': interval,
'limit': limit
}
if start_time:
# start_time은 milliseconds 타임스탬프
params['startTime'] = start_time
try:
response = requests.get(f"{base_url}{endpoint}", params=params, timeout=30)
response.raise_for_status()
data = response.json()
# DataFrame 변환
df = pd.DataFrame(data, columns=[
'open_time', 'open', 'high', 'low', 'close', 'volume',
'close_time', 'quote_volume', 'trades', 'taker_buy_base',
'taker_buy_quote', 'ignore'
])
# 수치형 변환
for col in ['open', 'high', 'low', 'close', 'volume', 'quote_volume']:
df[col] = pd.to_numeric(df[col], errors='coerce')
df['open_time'] = pd.to_datetime(df['open_time'], unit='ms')
return df
except requests.exceptions.RequestException as e:
print(f"데이터 조회 실패: {e}")
return None
2024년 1월 1일부터 1시간봉 데이터 500개 조회
start_ts = int(datetime(2024, 1, 1).timestamp() * 1000)
klines = get_futures_klines('BTCUSDT', '1h', start_ts, 500)
print(klines.head())
무료 히스토리컬 오더북 데이터 소스
1. CCXT 라이브러리
CCXT는 cryptocurrency 거래소 API를 unified interface로 Wrapping하는 오픈소스 라이브러리입니다. Binance뿐 아니라 100개 이상의 거래소를 동일한 코드로 사용할 수 있습니다.
# CCXT 설치: pip install ccxt
import ccxt
import pandas as pd
from datetime import datetime, timedelta
class BinanceDataFetcher:
def __init__(self):
self.exchange = ccxt.binance({
'enableRateLimit': True,
'options': {'defaultType': 'future'}
})
def fetch_ohlcv(self, symbol='BTC/USDT', timeframe='1m', since=None, limit=1000):
"""
OHLCV 데이터 가져오기
timeframe: 1m, 5m, 15m, 1h, 4h, 1d
since: milliseconds timestamp
"""
try:
ohlcv = self.exchange.fetch_ohlcv(symbol, timeframe, since, limit)
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
return df
except Exception as e:
print(f"데이터 가져오기 실패: {e}")
return None
def fetch_trades(self, symbol='BTC/USDT', limit=1000):
"""
개별 트레이드 데이터 가져오기
오더북 복원에 활용 가능
"""
try:
trades = self.exchange.fetch_trades(symbol, limit=limit)
df = pd.DataFrame(trades)
return df
except Exception as e:
print(f"트레이드 데이터 조회 실패: {e}")
return None
사용 예시
fetcher = BinanceDataFetcher()
최근 1시간 데이터
since = int((datetime.now() - timedelta(hours=1)).timestamp() * 1000)
ohlcv = fetcher.fetch_ohlcv('BTC/USDT', '1m', since)
print(ohlcv.tail())
최근 트레이드
trades = fetcher.fetch_trades('BTC/USDT', 100)
print(trades[['price', 'amount', 'side', 'datetime']].head())
CCXT 장점: 단일 인터페이스로 여러 거래소 지원, Rate limit 자동 처리, 풍부한 커뮤니티 문서
CCXT 단점: 오더북 히스토리 미지원, 트레이드 데이터만 제공, 대규모 데이터 수집 시 속도 제한
2. Python-Rainmaker (오더북 시뮬레이션)
# ohlcv 데이터로 간단한 오더북 시뮬레이션
import numpy as np
import pandas as pd
def simulate_orderbook_from_ohlcv(df, levels=50):
"""
OHLCV 데이터 기반으로 스프레드 기반 오더북 시뮬레이션
실제 오더북과 차이가 있을 수 있어 주의 필요
"""
results = []
for _, row in df.iterrows():
mid_price = (float(row['high']) + float(row['low'])) / 2
# 볼라틸리티 기반 스프레드 추정
spread = (float(row['high']) - float(row['low'])) * 0.1
bids = []
asks = []
for i in range(1, levels + 1):
tick_size = 0.1 if mid_price > 10000 else 0.01
bid_price = round(mid_price - spread/2 - (i * tick_size), 2)
ask_price = round(mid_price + spread/2 + (i * tick_size), 2)
# 레벨이 깊어질수록 수량 증가 (근사치)
base_volume = np.random.uniform(0.5, 5.0)
bid_volume = base_volume * (1 + i * 0.1)
ask_volume = base_volume * (1 + i * 0.1)
bids.append([bid_price, bid_volume])
asks.append([ask_price, ask_volume])
results.append({
'timestamp': row['datetime'],
'bids': bids,
'asks': asks,
'mid_price': mid_price
})
return results
사용 예시
simulated = simulate_orderbook_from_ohlcv(ohlcv.head(10))
print(f"시뮬레이션된 오더북 수: {len(simulated)}")
print(f"첫 오더북 중간가: {simulated[0]['mid_price']}")
전문 백테스팅용 데이터 소스
1. HuggingSQL/Databento (권장)
실제 백테스팅에 적합한 정밀 오더북 데이터가 필요하다면 전문 데이터 벤더를 고려해야 합니다.
# Databento Historical API 예시 (유료)
pip install databento-python
from databento import Historical
import pandas as pd
class OrderbookBacktestFetcher:
def __init__(self, api_key='YOUR_DATABENTO_KEY'):
self.client = Historical(api_key=api_key)
def fetch_orderbook_snapshots(self, symbol, start, end):
"""
오더북 스냅샷 데이터 조회
스냅샷 간격: 1-hourly, 100ms, 1s, custom
"""
data = self.client.timeseries.get_range(
dataset='GLBX.MDP3',
start=start,
end=end,
symbols=[symbol],
schema='mbp-10', # Market by Price - 10 레벨
)
# 데이터프레임 변환
df = data.to_df()
return df
def get_orderbook_delta(self, symbol, start, end):
"""
오더북 델타(변화량) 데이터
초기 스냅샷 + 델타로 오더북 복원
"""
data = self.client.timeseries.get_range(
dataset='GLBX.MDP3',
start=start,
end=end,
symbols=[symbol],
schema='mbp-10',
stype_in='continuous'
)
return data.to_df()
사용 예시
fetcher = OrderbookBacktestFetcher('your-api-key')
df = fetcher.fetch_orderbook_snapshots(
symbol='BTC.DMAP',
start='2024-01-01T00:00:00',
end='2024-01-02T00:00:00'
)
2. Binance Historical Data下载工具
# Unofficial Binance Data Downloader (GitHub)
https://github.com/Drakkar-Software/OctoBot-Backtesting
"""
실용적 대안: Binance_historical_trade_data 라이브러리
"""
import requests
import gzip
import json
from datetime import datetime
def download_binance_trades(symbol='btcusdt', date='2024-01-01'):
"""
Binance CSV 트레이드 데이터 다운로드
일별 단위 트레이드 데이터
"""
base_url = "https://data.binance.vision/data/futures/um"
url = f"{base_url}/historical/trades/{symbol}/trades_{symbol}_{date.replace('-', '')}.csv.gz"
try:
response = requests.get(url, stream=True, timeout=60)
response.raise_for_status()
# gzip解压
with gzip.open(response.raw, 'rt') as f:
lines = f.readlines()[:100] # 처음 100개만 읽기
trades = []
for line in lines:
parts = line.strip().split(',')
trades.append({
'id': parts[0],
'price': float(parts[1]),
'qty': float(parts[2]),
'time': datetime.fromtimestamp(int(parts[0])/1000),
'is_buyer_maker': parts[5] == 'True'
})
return trades
except Exception as e:
print(f"다운로드 실패 ({date}): {e}")
return []
테스트
trades = download_binance_trades('btcusdt', '2024-01-01')
print(f"다운로드된 트레이드 수: {len(trades)}")
if trades:
print(f"첫 트레이드: {trades[0]}")
백테스팅 파이프라인 구축
"""
완전한 백테스팅 파이프라인 예시
OHLCV + 트레이드 데이터로 오더북 근사 복원
"""
import pandas as pd
import numpy as np
from collections import defaultdict
import time
class OrderbookReconstructor:
"""
트레이드 스트림에서 오더북 상태 추정
"""
def __init__(self, tick_size=0.1, lot_size=0.001):
self.tick_size = tick_size
self.lot_size = lot_size
self.bids = defaultdict(float) # price -> quantity
self.asks = defaultdict(float)
def process_trade(self, trade):
"""
트레이드 처리하여 오더북 업데이트
trade: {'price': float, 'quantity': float, 'side': 'buy'|'sell', 'timestamp': int}
"""
price = round(trade['price'] / self.tick_size) * self.tick_size
if trade['side'] == 'buy':
self.asks[price] -= trade['quantity']
if self.asks[price] <= 0:
del self.asks[price]
else:
self.bids[price] -= trade['quantity']
if self.bids[price] <= 0:
del self.bids[price]
return self.get_snapshot()
def get_snapshot(self, levels=20):
"""현재 오더북 스냅샷 반환"""
sorted_bids = sorted(self.bids.items(), key=lambda x: -x[0])[:levels]
sorted_asks = sorted(self.asks.items(), key=lambda x: x[0])[:levels]
return {
'bids': [[price, qty] for price, qty in sorted_bids],
'asks': [[price, qty] for price, qty in sorted_asks],
'mid_price': (sorted_bids[0][0] + sorted_asks[0][0]) / 2 if sorted_bids and sorted_asks else None,
'spread': sorted_asks[0][0] - sorted_bids[0][0] if sorted_bids and sorted_asks else None
}
def backtest_market_making(trades_df, fee_rate=0.0004, spread_pct=0.001):
"""
시장 제조 전략 간단 백테스트
"""
reconstructor = OrderbookReconstructor()
pnl = 0
position = 0
trades_count = 0
for _, trade in trades_df.iterrows():
snapshot = reconstructor.process_trade({
'price': trade['price'],
'quantity': trade['qty'],
'side': 'buy' if trade['is_buyer_maker'] else 'sell',
'timestamp': trade['id']
})
if snapshot['mid_price'] and snapshot['spread']:
# 시장 제조: 매도-매수 스프레드 활용
trade_pnl = trade['qty'] * snapshot['spread'] - (trade['qty'] * snapshot['mid_price'] * fee_rate * 2)
pnl += trade_pnl
position += trade['qty'] if not trade['is_buyer_maker'] else -trade['qty']
trades_count += 1
return {
'total_pnl': pnl,
'total_trades': trades_count,
'avg_pnl_per_trade': pnl / trades_count if trades_count > 0 else 0,
'final_position': position
}
실행 예시
trades_df = pd.DataFrame(trades)
results = backtest_market_making(trades_df)
print(results)
데이터 소스 비교표
| 데이터 소스 | 오더북 히스토리 | 트레이드 히스토리 | 비용 | 시간 범위 | 지연 | 적합 목적 |
|---|---|---|---|---|---|---|
| Binance Public API | ❌ 실시간만 | ✅ 최근 500개 | 무료 | 오늘 | <100ms | 라이브 트레이딩 |
| CCXT 라이브러리 | ❌ 실시간만 | ✅ 제한적 | 무료 | 오늘 | <100ms | 다거래소 연동 |
| Binance Futures Download | ❌ 없음 | ✅ CSV | 무료 | 2020~ | - | 캔들 기반 백테스트 |
| Databento | ✅ 10레벨 스냅샷 | ✅ 전체 | $0.003/GB~ | 2018~ | - | 전문 백테스팅 |
| Hudson River Trading | ✅ 전체 레벨 | ✅ 전체 | 문의 | 전체 | - | 기관급 백테스트 |
| CBOE BZX (비트코인) | ✅ 10레벨 | ✅ 전체 | 무료 | 2019~ | - | 학술 연구 |
실전 권장 사항
목적별 최적 선택
- 단순 전략 백테스트: Binance OHLCV 데이터 + 시뮬레이션 오더북 → 무료로 충분
- 스프레드 기반 전략: CBOE BZX 무료 오더북 데이터 활용
- 시장 제조 전략: Databento 유료 구독 고려 ($500~/월)
- 기관급 정밀도: Hudson River Trading, TickData LLC 문의
자주 발생하는 오류 해결
오류 1: "API request timeout"
# 문제: Binance API 타임아웃 (rate limit 초과)
해결: Exponential backoff + rate limit 준수
import time
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session_with_retry(max_retries=5):
session = requests.Session()
retry_strategy = Retry(
total=max_retries,
backoff_factor=1, # 1초, 2초, 4초, 8초, 16초
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "OPTIONS"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
사용
session = create_session_with_retry()
response = session.get(url, params=params)
오류 2: "Symbol not found" / 빈 데이터 반환
# 문제: 심볼 명칭 불일치
Binance Futures vs Spot 심볼 차이
Binance Spot: BTCUSDT
Binance Futures: BTCUSDT ( perp), BTCUSD (inverse)
CCXT: BTC/USDT (unified)
해결: 심볼 매핑 확인
import ccxt
exchange = ccxt.binance({'enableRateLimit': True})
사용 가능한 심볼 목록
markets = exchange.load_markets()
Futures 심볼만 필터링
futures_symbols = [s for s in markets.keys() if 'USDT' in s and exchange.markets[s].get('future')]
print(f"선물 심볼 예시: {futures_symbols[:5]}")
또는 Binance 공식 Naming 규칙 확인
https://developers.binance.com/docs/simple-earn/history
오류 3: "Decompression error" / gzip 데이터 파싱 실패
# 문제: Binance Vision CSV 압축 해제 실패
해결: 파일 형식 확인 후 적절한 파싱
import requests
import gzip
import io
def download_with_auto_detect(url):
"""파일 형식 자동 감지 후 다운로드"""
response = requests.get(url, stream=True, timeout=60)
# Content-Type 또는 URL 기반 형식 감지
content_type = response.headers.get('Content-Type', '')
if '.gz' in url or 'gzip' in content_type:
# gzip解压
with gzip.open(io.BytesIO(response.content), 'rt') as f:
content = f.read()
else:
content = response.content.decode('utf-8')
return content
또는 수동으로 encoding 지정
def download_csv_robust(url):
"""여러 인코딩 시도"""
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
for encoding in encodings:
try:
response = requests.get(url, timeout=60)
response.encoding = encoding
return response.text
except UnicodeDecodeError:
continue
raise ValueError("지원하지 않는 인코딩")
결론 및 다음 단계
Binance 히스토리컬 오더북 데이터는 용도에 따라 적절한 소스 선택이 중요합니다. 단순 백테스트라면 Binance 공식 OHLCV + 시뮬레이션으로 충분하지만, 스프레드 기반 마켓메이킹 전략을 백테스트하려면 전문 데이터 벤더가 필요합니다.
저의 경험상, 시작은 Binance Public API + CCXT 조합으로 전략의 기본 효율성을 검증한 후, 정교한 백테스트가 필요한 경우 Databento로 마이그레이션하는 것이 비용 대비 효율적입니다.
참고 자료
- Binance API Docs: https://developers.binance.com
- CCXT 공식 문서: https://docs.ccxt.com
- Databento 무료 평가판: https://databento.com
백테스팅 데이터 확보에 관해 추가 질문이 있으시면评论区에서 알려주세요.