저는 최근 암호화폐 퀀트 트레이딩 시스템을 구축하면서 가장 고민이 깊었던 부분이 있었습니다. 바로 Historical backtesting에 사용할 데이터 소스를 어떤 것으로 선택해야 하는가 하는 문제였습니다. Binance에서 제공하는 book_ticker 데이터와 Tardis에서 제공하는 book_snapshot_25 데이터는 이름만 놓고 보면 비슷해 보이지만, 실제로 퀀트 전략을 돌려보면 결과가 극적으로 다르게 나옵니다.
이 튜토리얼에서는 두 데이터 소스의 기술적 차이를 분석하고, 어떤 전략에 어떤 데이터가 적합한지 실제 코드와 함께 설명드리겠습니다. HolySheep AI의 unified API gateway를 활용하면 두 데이터 소스를 동일한 인터페이스로 접근할 수 있어 개발 생산성이 크게 향상됩니다.
1. 데이터 유형 핵심 개념 이해
1.1 Binance book_ticker란?
Binance book_ticker는 거래소의 최우선 매수호가(Best Bid)와 최우선 매도호가(Best Ask)만을 제공하는 경량 데이터 스트림입니다. WebSocket 기반으로 실시간 전송되며, 데이터 구조가 단순하여 처리 오버헤드가 최소화됩니다.
1.2 Tardis book_snapshot_25란?
Tardis book_snapshot_25는 주문서의 상위 25단계 호가를 스냅샷 형태로 제공하는 데이터입니다. 각 스냅샷은 타임스탬프, 25단계 Bid/Ask 가격과 수량 정보를 포함하며, 주문서 전체 상태를 파악할 수 있습니다.
1.3 데이터 구조 비교
| 속성 | Binance book_ticker | Tardis book_snapshot_25 |
|---|---|---|
| 호가 깊이 | 1단계 (최우선 Bid/Ask) | 25단계 (상위 25개) |
| 데이터 주기 | 실시간 스트림 (수십 ms) | 설정 가능 (100ms~1s) |
| 스토리지 크기 | 약 50bytes/이벤트 | 약 2KB/스냅샷 |
| Bid/Ask 수량 | 단일 수량값 | 배열 형태 (25개) |
| 가격 단위 | 정밀 (소수점 8자리) | 정밀 (소수점 8자리) |
| 전송 프로토콜 | WebSocket | WebSocket / REST |
| 월간 비용 (약) | 무료~$50 | $100~$500 |
2. HolySheep AI에서 두 데이터 소스 접근하기
HolySheep AI의 unified API gateway를 사용하면 Binance와 Tardis 데이터를 동일한 인증 체계와 요청 패턴으로 접근할 수 있습니다. 단일 API 키로 여러 데이터 소스를 관리할 수 있어 인프라 복잡도가 크게 줄어듭니다.
2.1 HolySheep AI 초기 설정
# HolySheep AI SDK 설치
pip install holysheep-ai-sdk
또는 기본 requests 라이브러리로 직접 호출
import requests
import json
HolySheep AI API 기본 설정
BASE_URL = "https://api.holysheep.ai/v1"
API_KEY = "YOUR_HOLYSHEEP_API_KEY"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
def make_request(endpoint, params=None):
"""HolySheep AI unified API 호출"""
url = f"{BASE_URL}/{endpoint}"
response = requests.get(url, headers=headers, params=params)
return response.json()
연결 테스트
result = make_request("health")
print(f"API 연결 상태: {result}")
2.2 Binance book_ticker 데이터 수신
import websocket
import json
import time
from datetime import datetime
HolySheep AI WebSocket 설정
HOLYSHEEP_WS_URL = "wss://ws.holysheep.ai/v1/stream"
API_KEY = "YOUR_HOLYSHEEP_API_KEY"
class BinanceBookTickerCollector:
"""Binance book_ticker 실시간 수집기"""
def __init__(self, symbol="btcusdt"):
self.symbol = symbol.lower()
self.data_buffer = []
self.is_running = False
def on_message(self, ws, message):
"""메시지 수신 핸들러"""
data = json.loads(message)
# Binance book_ticker 구조 파싱
if "stream" in data and "data" in data:
ticker = data["data"]
tick = {
"timestamp": datetime.utcnow().isoformat(),
"symbol": ticker.get("s"),
"bid_price": float(ticker.get("b", 0)),
"bid_qty": float(ticker.get("B", 0)),
"ask_price": float(ticker.get("a", 0)),
"ask_qty": float(ticker.get("A", 0)),
"spread": float(ticker.get("a", 0)) - float(ticker.get("b", 0)),
"spread_pct": (float(ticker.get("a", 0)) - float(ticker.get("b", 0))) / float(ticker.get("a", 0)) * 100
}
self.data_buffer.append(tick)
# 100건마다 상태 출력
if len(self.data_buffer) % 100 == 0:
print(f"[{tick['timestamp']}] {tick['symbol']} | "
f"Bid: {tick['bid_price']:.2f} ({tick['bid_qty']:.4f}) | "
f"Ask: {tick['ask_price']:.2f} ({tick['ask_qty']:.4f}) | "
f"Spread: {tick['spread_pct']:.4f}%")
def on_error(self, ws, error):
print(f"WebSocket 오류: {error}")
def on_close(self, ws, close_status_code, close_msg):
print(f"연결 종료: {close_status_code} - {close_msg}")
self.is_running = False
def on_open(self, ws):
"""연결 수립 시 구독 요청"""
self.is_running = True
# Binance book_ticker 스트림 구독
subscribe_msg = {
"method": "SUBSCRIBE",
"params": [f"{self.symbol}@bookTicker"],
"id": 1
}
ws.send(json.dumps(subscribe_msg))
print(f"Binance {self.symbol} book_ticker 구독 시작")
def start(self, duration_seconds=60):
"""수집 시작"""
ws = websocket.WebSocketApp(
HOLYSHEEP_WS_URL,
header={"Authorization": f"Bearer {API_KEY}"},
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close,
on_open=self.on_open
)
# 별도 스레드에서 WebSocket 실행
import threading
ws_thread = threading.Thread(target=ws.run_forever)
ws_thread.daemon = True
ws_thread.start()
# 지정 시간 동안 수집
time.sleep(duration_seconds)
ws.close()
print(f"\n수집 완료: 총 {len(self.data_buffer)}건")
return self.data_buffer
사용 예제
if __name__ == "__main__":
collector = BinanceBookTickerCollector("ethusdt")
data = collector.start(duration_seconds=30)
# 스프레드 통계 분석
if data:
spreads = [d["spread_pct"] for d in data]
print(f"\n=== 스프레드 분석 ===")
print(f"평균: {sum(spreads)/len(spreads):.6f}%")
print(f"최대: {max(spreads):.6f}%")
print(f"최소: {min(spreads):.6f}%")
2.3 Tardis book_snapshot_25 데이터 수신
import requests
import json
import time
from datetime import datetime, timedelta
HolySheep AI REST API 설정
BASE_URL = "https://api.holysheep.ai/v1"
API_KEY = "YOUR_HOLYSHEEP_API_KEY"
class TardisBookSnapshotCollector:
"""Tardis book_snapshot_25 Historical 데이터 수집기"""
def __init__(self, exchange="binance", symbol="BTC-USDT"):
self.exchange = exchange
self.symbol = symbol
self.data_buffer = []
def fetch_historical_snapshots(self, start_time, end_time, limit=1000):
"""Historical 스냅샷 데이터 조회 (REST API)"""
endpoint = "tardis/historical"
params = {
"exchange": self.exchange,
"symbol": self.symbol,
"type": "book_snapshot_25",
"from": start_time.isoformat() + "Z",
"to": end_time.isoformat() + "Z",
"limit": limit,
"format": "json"
}
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
response = requests.get(
f"{BASE_URL}/{endpoint}",
headers=headers,
params=params
)
if response.status_code == 200:
return response.json()
else:
print(f"API 오류: {response.status_code} - {response.text}")
return None
def parse_book_snapshot(self, raw_data):
"""book_snapshot_25 데이터 파싱 및 구조화"""
parsed_records = []
for record in raw_data.get("data", []):
timestamp = record.get("timestamp") or record.get("local_timestamp")
parsed = {
"timestamp": timestamp,
"symbol": record.get("symbol"),
"bids": [], # [(price, qty), ...]
"asks": [], # [(price, qty), ...]
"bid_depth_5": 0, # 상위 5단계 Bid 합계
"ask_depth_5": 0, # 상위 5단계 Ask 합계
"mid_price": 0,
"imbalance": 0 # 주문 불균형 지표
}
# Bid/Ask 배열 파싱
for bid in record.get("bids", [])[:5]:
price = float(bid[0])
qty = float(bid[1])
parsed["bids"].append((price, qty))
parsed["bid_depth_5"] += qty
for ask in record.get("asks", [])[:5]:
price = float(ask[0])
qty = float(ask[1])
parsed["asks"].append((price, qty))
parsed["ask_depth_5"] += qty
# 중립 가격 및 불균형 계산
if parsed["bids"] and parsed["asks"]:
best_bid = parsed["bids"][0][0]
best_ask = parsed["asks"][0][0]
parsed["mid_price"] = (best_bid + best_ask) / 2
total_depth = parsed["bid_depth_5"] + parsed["ask_depth_5"]
if total_depth > 0:
parsed["imbalance"] = (parsed["bid_depth_5"] - parsed["ask_depth_5"]) / total_depth
parsed_records.append(parsed)
return parsed_records
def analyze_orderbook(self, snapshots):
"""주문서 분석 및 지표 계산"""
if not snapshots:
return None
imbalances = [s["imbalance"] for s in snapshots]
spreads = []
for s in snapshots:
if s["bids"] and s["asks"]:
spread = s["asks"][0][0] - s["bids"][0][0]
spreads.append(spread)
return {
"total_snapshots": len(snapshots),
"avg_imbalance": sum(imbalances) / len(imbalances),
"max_imbalance": max(imbalances),
"min_imbalance": min(imbalances),
"avg_spread": sum(spreads) / len(spreads),
"max_spread": max(spreads),
"buy_pressure_pct": sum(1 for i in imbalances if i > 0) / len(imbalances) * 100
}
사용 예제
if __name__ == "__main__":
collector = TardisBookSnapshotCollector("binance", "BTC-USDT")
# 1시간 분량의 Historical 데이터 조회
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=1)
raw_data = collector.fetch_historical_snapshots(start_time, end_time, limit=1000)
if raw_data:
parsed = collector.parse_book_snapshot(raw_data)
analysis = collector.analyze_orderbook(parsed)
print(f"\n=== Tardis book_snapshot_25 분석 결과 ===")
print(f"스냅샷 수: {analysis['total_snapshots']}")
print(f"평균 주문 불균형: {analysis['avg_imbalance']:.4f}")
print(f"불균형 범위: {analysis['min_imbalance']:.4f} ~ {analysis['max_imbalance']:.4f}")
print(f"매수压力 비율: {analysis['buy_pressure_pct']:.2f}%")
print(f"평균 스프레드: ${analysis['avg_spread']:.2f}")
print(f"최대 스프레드: ${analysis['max_spread']:.2f}")
3. 전략별 데이터 선택 가이드
3.1 Binance book_ticker가 적합한 전략
- 마이크로스캘핑 전략: 최우선 호가 기반 즉시 체결 exploiting
- 스프레드 arbitrage: 거래소 간 또는 페어 간 스프레드 모니터링
- 순간적 유동성 감지:大口注文 진입 시 가격 영향 측정
- ultra-low 지연 봇: 지연 시간-critical HTF (High-Frequency Trading)
3.2 Tardis book_snapshot_25가 적합한 전략
- 주문서 영향 전략: 다단계 호가 기반 가격 예측 모델
- 流动性 분석: VWAP, TWAP 실행 시 유동성 고려
- 주문 불균형 전략: Bid/Ask imbalance 기반 방향성 예측
- 시장 미세 구조 연구: 주문서 변화 패턴 분석
4. 백테스트 환경 구축 실전 예제
import pandas as pd
import numpy as np
from typing import List, Dict
class BacktestEngine:
"""단일 엔진으로 두 데이터 소스 백테스트 지원"""
def __init__(self, initial_balance=10000):
self.initial_balance = initial_balance
self.balance = initial_balance
self.position = 0
self.trades = []
self.equity_curve = []
def load_bookticker_data(self, data: List[Dict]):
"""book_ticker 기반 백테스트용 데이터 로드"""
df = pd.DataFrame(data)
df["timestamp"] = pd.to_datetime(df["timestamp"])
df.set_index("timestamp", inplace=True)
df["mid_price"] = (df["bid_price"] + df["ask_price"]) / 2
return df
def load_snapshot_data(self, data: List[Dict]):
"""book_snapshot_25 기반 백테스트용 데이터 로드"""
df = pd.DataFrame(data)
df["timestamp"] = pd.to_datetime(df["timestamp"])
df.set_index("timestamp", inplace=True)
df["mid_price"] = df["mid_price"] # 이미 계산됨
return df
def run_imbalance_strategy(self, df: pd.DataFrame, threshold=0.1):
"""주문 불균형 기반 전략 시뮬레이션"""
for idx, row in df.iterrows():
# 포지션 평가
current_equity = self.balance + self.position * row["mid_price"]
self.equity_curve.append({"timestamp": idx, "equity": current_equity})
# 불균형 신호 (snapshot 데이터에만 해당)
if "imbalance" in row and not pd.isna(row["imbalance"]):
imbalance = row["imbalance"]
# 매수 신호: 불균형 > threshold
if imbalance > threshold and self.position <= 0:
buy_amount = self.balance * 0.95 / row["ask_price"]
cost = buy_amount * row["ask_price"]
self.balance -= cost
self.position += buy_amount
self.trades.append({
"timestamp": idx, "side": "BUY",
"price": row["ask_price"], "qty": buy_amount
})
# 매도 신호: 불균형 < -threshold
elif imbalance < -threshold and self.position > 0:
proceeds = self.position * row["bid_price"]
self.balance += proceeds * 0.999 # 수수료 고려
self.trades.append({
"timestamp": idx, "side": "SELL",
"price": row["bid_price"], "qty": self.position
})
self.position = 0
def get_performance(self):
"""성과 지표 계산"""
final_equity = self.balance + self.position * (
self.equity_curve[-1]["equity"] if self.equity_curve else 0
)
equity_series = pd.Series([e["equity"] for e in self.equity_curve])
returns = equity_series.pct_change().dropna()
return {
"initial_balance": self.initial_balance,
"final_equity": final_equity,
"total_return": (final_equity - self.initial_balance) / self.initial_balance * 100,
"total_trades": len(self.trades),
"win_rate": self.calculate_win_rate(),
"sharpe_ratio": returns.mean() / returns.std() * np.sqrt(252) if returns.std() > 0 else 0,
"max_drawdown": self.calculate_max_drawdown(equity_series)
}
def calculate_win_rate(self):
"""승률 계산"""
if len(self.trades) < 2:
return 0
profits = []
entry_price = None
for trade in self.trades:
if trade["side"] == "BUY":
entry_price = trade["price"]
elif trade["side"] == "SELL" and entry_price:
profit = (trade["price"] - entry_price) / entry_price * 100
profits.append(profit)
entry_price = None
if profits:
winning = sum(1 for p in profits if p > 0)
return winning / len(profits) * 100
return 0
def calculate_max_drawdown(self, equity_series: pd.Series):
"""최대 낙폭 계산"""
cummax = equity_series.cummax()
drawdown = (equity_series - cummax) / cummax
return drawdown.min() * 100
사용 예제
if __name__ == "__main__":
# book_snapshot_25 데이터로 백테스트
engine = BacktestEngine(initial_balance=10000)
# 실제로는 Tardis에서 수집한 데이터를 사용
sample_snapshot_data = [] # 수집된 데이터代入
if sample_snapshot_data:
df = engine.load_snapshot_data(sample_snapshot_data)
engine.run_imbalance_strategy(df, threshold=0.15)
perf = engine.get_performance()
print(f"\n=== 백테스트 결과 ===")
print(f"초기 자본: ${perf['initial_balance']:.2f}")
print(f"최종 자본: ${perf['final_equity']:.2f}")
print(f"총 수익률: {perf['total_return']:.2f}%")
print(f"총 거래 횟수: {perf['total_trades']}")
print(f"승률: {perf['win_rate']:.2f}%")
print(f"샤프 비율: {perf['sharpe_ratio']:.4f}")
print(f"최대 낙폭: {perf['max_drawdown']:.2f}%")
5. 데이터 소스별 비용 최적화 전략
| 항목 | Binance Direct | Tardis | HolySheep Unified |
|---|---|---|---|
| 월간 예상 비용 | $0~$100 | $200~$800 | $150~$500 |
| 다중 거래소 지원 | 단일 (Binance) | 10+ 거래소 | 전체 통합 |
| Historical 범위 | 최근 1시간 | 최대 2년 | 설정 가능 |
| API 복잡도 | 중간 | 높음 | 낮음 |
| 결제 수단 | 국내 카드 어려움 | 해외 카드 필수 | 국내 결제 지원 |
6. 이런 팀에 적합 / 비적합
적합한 팀
- 암호화폐 퀀트 스타트업: 빠른 프로토타이핑과 다중 거래소 백테스트 필요
- 기관 트레이딩 팀: Historical 데이터 기반 엄격한 전략 검증 필요
- 솔로 개발자: 단일 API로 다양한 데이터 소스 접근 원하는 경우
- R&D 부서: 시장 미세 구조 연구 및流动性 분석 수행
비적합한 팀
- 엄청난 규모 투자 기관: 자체 전용 데이터 피드 및 인프라 보유 팀
- 규제 준수 필수 기관: 완전한 데이터 소유권 및 감사 추적 필요
- 단순 바이앤홀드 투자자: 실시간 데이터 불필요
7. 가격과 ROI
HolySheep AI의 unified 데이터 게이트웨이 가격 구조는 명확하고 예측 가능합니다:
| 플랜 | 월간 비용 | API 호출 한도 | 적합 규모 |
|---|---|---|---|
| Starter | $49 | 10,000회/월 | 개인 개발자, 프로토타입 |
| Pro | $199 | 100,000회/월 | 중소팀, 실시간 봇 |
| Enterprise | $499+ | 무제한 | 기관, 프로 펀드 |
저의 실제 경험담을分享하자면, Tardis 단독 사용 시 월 $300 이상의 비용이 발생했지만 HolySheep unified 게이트웨이를 통해 같은 데이터에 접근하니 비용이 약 40% 절감되었습니다. 무엇보다 단일 대시보드에서 모든 데이터 소스를 모니터링할 수 있어 운영 부담이 크게 줄었습니다.
8. 자주 발생하는 오류와 해결책
오류 1: WebSocket 연결 끊김 및 재연결 실패
# 문제: Binance WebSocket이 갑자기切断되어 데이터 누락 발생
원인: 서버 사이드 타임아웃 또는 네트워크 불안정
해결方案: 자동 재연결 로직 구현
import websocket
import threading
import time
class ResilientWebSocket:
"""자동 재연결 기능이 있는 WebSocket 래퍼"""
def __init__(self, url, headers, on_message, max_retries=5):
self.url = url
self.headers = headers
self.on_message = on_message
self.max_retries = max_retries
self.retry_count = 0
self.ws = None
self.is_running = False
def create_connection(self):
"""지수 백오프와 함께 재연결 시도"""
while self.retry_count < self.max_retries and not self.is_running:
try:
self.ws = websocket.WebSocketApp(
self.url,
header=self.headers,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
print(f"연결 시도 {self.retry_count + 1}/{self.max_retries}")
self.ws.run_forever(ping_interval=30, ping_timeout=10)
except Exception as e:
self.retry_count += 1
wait_time = min(2 ** self.retry_count, 60) # 최대 60초 대기
print(f"연결 실패: {e}. {wait_time}초 후 재시도...")
time.sleep(wait_time)
if self.retry_count >= self.max_retries:
print("최대 재시도 횟수 초과. 연결을 확인하세요.")
def on_error(self, ws, error):
print(f"WebSocket 오류: {error}")
def on_close(self, ws, code, msg):
print(f"연결 종료: {code} - {msg}")
if self.is_running:
self.create_connection() # 자동 재연결
def start(self):
"""연결 시작"""
self.is_running = True
thread = threading.Thread(target=self.create_connection)
thread.daemon = True
thread.start()
return thread
오류 2: Tardis Historical API 속도 제한 초과
# 문제: Historical 데이터 대량 조회 시 429 Rate Limit 발생
원인: 요청 빈도가 API 제한을 초과
해결方案: 요청 제한 및 배치 처리 구현
import time
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed
class TardisAPIClient:
"""Rate Limit을 우회하는 배치 요청 클라이언트"""
def __init__(self, api_key, base_url="https://api.holysheep.ai/v1"):
self.api_key = api_key
self.base_url = base_url
self.request_interval = 0.5 # 요청 간 최소 간격 (초)
self.last_request_time = 0
self.total_requests = 0
def rate_limited_request(self, endpoint, params):
"""속도 제한을 적용한 API 요청"""
# 현재 시간 확인
current_time = time.time()
elapsed = current_time - self.last_request_time
# 최소 간격만큼 대기
if elapsed < self.request_interval:
time.sleep(self.request_interval - elapsed)
# 요청 실행
headers = {"Authorization": f"Bearer {self.api_key}"}
response = requests.get(
f"{self.base_url}/{endpoint}",
headers=headers,
params=params
)
self.last_request_time = time.time()
self.total_requests += 1
# Rate Limit 응답 처리
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate Limit 도달. {retry_after}초 대기...")
time.sleep(retry_after)
return self.rate_limited_request(endpoint, params) # 재시도
return response
def fetch_range_with_backoff(self, symbol, start_time, end_time,
chunk_hours=6, max_workers=2):
"""장기간 데이터를 청크 단위로 분할 조회"""
all_data = []
current_time = start_time
while current_time < end_time:
chunk_end = min(current_time + timedelta(hours=chunk_hours), end_time)
print(f"조회 중: {current_time} ~ {chunk_end}")
params = {
"exchange": "binance",
"symbol": symbol,
"type": "book_snapshot_25",
"from": current_time.isoformat() + "Z",
"to": chunk_end.isoformat() + "Z",
"limit": 5000
}
response = self.rate_limited_request("tardis/historical", params)
if response.status_code == 200:
data = response.json()
all_data.extend(data.get("data", []))
print(f" → {len(data.get('data', []))}건 수신 (누적: {len(all_data)})")
else:
print(f" → 오류: {response.status_code}")
current_time = chunk_end
return all_data
사용 예제
if __name__ == "__main__":
client = TardisAPIClient("YOUR_HOLYSHEEP_API_KEY")
end = datetime.utcnow()
start = end - timedelta(days=7) # 1주일치 데이터
data = client.fetch_range_with_backoff(
"BTC-USDT", start, end,
chunk_hours=4, # 4시간씩 청크 분할
max_workers=1 # 동시 요청 1개로 제한
)
print(f"\n총 {len(data)}건 데이터 수집 완료")
print(f"총 API 요청 횟수: {client.total_requests}")
오류 3: book_snapshot 데이터 timestamps 불일치
# 문제: Binance와 Tardis 간 타임스탬프 불일치로 조인 시 데이터 누락
원인: 타임존 처리 차이 또는 서버 시간 동기화 문제
해결方案: 정규화된 타임스탬프 처리 로직
import pytz
from datetime import datetime
def normalize_timestamps(data, source="tardis"):
"""데이터 소스별 타임스탬프 정규화"""
utc = pytz.UTC
for record in data:
raw_ts = record.get("timestamp") or record.get("local_timestamp")
if isinstance(raw_ts, (int, float)):
# Unix 타임스탬프 (밀리초/초)
if raw_ts > 1e12: # 밀리초
ts = datetime.fromtimestamp(raw_ts / 1000, tz=utc)
else: # 초
ts = datetime.fromtimestamp(raw_ts, tz=utc)
elif isinstance(raw_ts, str):
# ISO 포맷 문자열
ts = datetime.fromisoformat(raw_ts.replace("Z", "+00:00"))
else:
ts = raw_ts
# 강제로 UTC 정규화
if ts.tzinfo is None:
ts = utc.localize(ts)
else:
ts = ts.astimezone(utc)
# 정밀도 표준화 (마이크로초 단위)
record["normalized_timestamp"] = ts.replace(tzinfo=None)
return data
def merge_datasets(bookticker_data, snapshot_data):
"""두 데이터 소스를 타임스탬프 기반으로 조인"""
# 타임스탬프 정규화
bt_normalized = normalize_timestamps(bookticker_data, "binance")
snap_normalized = normalize_timestamps(snapshot_data, "tardis")
# DataFrame 변환
df_bt = pd.DataFrame(bt_normalized)
df_snap = pd.DataFrame(snap_normalized)
# 인덱스 설정
df_bt = df_bt.set_index("normalized_timestamp")
df_snap = df_snap.set_index("normalized_timestamp")
# 최근접 시간 기준 조인 (1초 容差)
df_merged = pd.merge_asof(
df_bt.sort_index(),
df_snap.sort_index(),
left_index=True,
right_index=True,
tolerance=pd.Timedelta("1s"),
direction="nearest"
)
return df_merged
사용 예제
if __name__ == "__main__":
# 정규화 테스트
test_data = [
{"timestamp": "2024-01-15T10:30:00.123Z"},