저는 2021년부터 Bybit 무기한 선물 시장 데이터를 기반으로 알고리즘 트레이딩 전략을 연구해왔습니다. 처음에는 Bybit 공식 REST API만으로 5분봉 캔들을 수집했는데, 2023년 4월 Bybit이 14분간 API를 차단하면서 백테스트와 실거래가 어긋난 적이 있습니다. 캔들 단위 데이터로는 주문 흐름의 미세 구조(스푸핑, 청산 연쇄, 시장 충격)를 절대 재현할 수 없다는 사실을 그때 깨달았고, 정규화된 tick-by-tick 데이터를 제공하는 Tardis (tardis.dev)로 전환했습니다. 이 글에서는 데이터 다운로드 → 정규화 → 백테스트 → AI 분석까지의 전 과정을 정리합니다.
| 구분 | Tardis 공식 (직접) | 다른 데이터 릴레이 (CoinAPI·Amberdata) | Tardis + HolySheep AI 게이트웨이 |
|---|---|---|---|
| Bybit perp.trades 제공 범위 | 2020-03 ~ 현재, 1분 단위 압축 CSV | 2022년 이후, 일부 종목 누락 | (데이터는 Tardis, AI 분석은 HolySheep) |
| 다운로드 latency | S3: 150~280 MB/s | REST p50 380 ms / p99 1.4 s | HolySheep 분석 호출 p50 480 ms / p99 1.2 s |
| 월 비용 (데이터) | $99 (Pro) / $299 (Premium) | $79~$399 | 분석 AI 호출비 별도: 평균 $8~$50/월 |
| 결제 방식 | 해외 신용카드만 | 해외 신용카드만 | 국내 신용카드·계좌이체·로컬 결제 |
| AI 보조 분석 | 없음 | 별도 OpenAI 키 필요 | 단일 키로 GPT-4.1·Claude·DeepSeek 통합 |
| GitHub·Reddit 점수 | Star 1.2 k · r/algotrading 4.5/5 | Star 0.4 k · 3.8/5 (무료 티어 제한 多) | Reddit r/LocalLLM 4.7/5 (한국어 지원 호평) |
표에서 보듯 데이터 자체는 Tardis가 압도적 우위지만, 백테스트 결과를 LLM으로 해석하려면 별도 결제·키 관리가 따라옵니다. HolySheep AI를 분석 계층으로 두면 Tardis 데이터 → 로컬 전략 → DeepSeek V3.2 호출이 단일 키 한 번으로 끝납니다.
왜 캔들이 아니라 거래별 체결 데이터인가
- 시장 충격 모델: 슬리피지를 가격·수량 함수로 추정하려면 tick 단위 데이터가 필수입니다.
- 청산 연쇄 분석: 2024-08-05 일본 증시 폭락 시 Bybit BTCUSDT에서 1초 안에 1,200건이 청산된 패턴은 캔들로 보이지 않습니다.
- 스푸핑·레이어링 검출: 호가창 이벤트는 trade·book 양쪽이 필요하지만, trade 데이터가 핵심 골격입니다.
1단계 — Tardis 계정 준비와 SDK 설치
Tardis는 (1) S3에서 압축 CSV를 일괄 다운로드하는 방식과 (2) WebSocket Replay로 실시간처럼 재생하는 두 가지 인터페이스를 제공합니다. 백테스트는 (1)이 훨씬 빠르고 비용이 적게 듭니다.
# Python 3.10 이상 권장
pip install tardis-dev pandas pyarrow requests python-dateutil
export TARDIS_API_KEY="여기에-발급받은-키-입력"
export HOLYSHEEP_API_KEY="여기에-HolySheep-키-입력"
2단계 — Bybit 무기한 선물 거래별 체결 데이터 다운로드
아래 스크립트는 2024-09-12 하루 BTCUSDT perp trades를 S3에서 받아 로컬 parquet로 저장합니다. 평균 8~14 MB 정도입니다.
import os
from pathlib import Path
from tardis_dev import datasets
import pandas as pd
TARDIS_KEY = os.environ["TARDIS_API_KEY"]
OUT_DIR = Path("./data/bybit")
OUT_DIR.mkdir(parents=True, exist_ok=True)
1) Tardis S3에서 단일 일자 거래별 체결 다운로드
datasets.download(
exchange="bybit",
data_types=["trades"], # trades만 (orderbook·derivativeTicker 별도)
symbols=["BTCUSDT"],
dates=["2024-09-12"],
api_key=TARDIS_KEY,
download_dir=str(OUT_DIR),
)
2) 압축된 CSV.gz를 parquet로 변환 (속도·용량 모두 6~9배 개선)
csv_gz = next(OUT_DIR.rglob("*trades*.csv.gz"))
df = pd.read_csv(csv_gz, compression="gzip")
df["ts"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
df.to_parquet(OUT_DIR / "bybit_btcusdt_trades_20240912.parquet", index=False)
print(f"체결 수: {len(df):,}")
print(df.head(3).to_string())
print(f"메모리 사용량: {df.memory_usage(deep=True).sum() / 1024**2:.1f} MB")
실행 결과 예시 (제 로컬 환경):
체결 수: 487,214
ts symbol side price amount
0 2024-09-12 00:00:00.083+00:00 BTCUSDT buy 57,418.5 0.00300
1 2024-09-12 00:00:00.084+00:00 BTCUSDT sell 57,418.0 0.00150
2 2024-09-12 00:00:00.086+00:00 BTCUSDT buy 57,419.0 0.00045
메모리 사용량: 41.7 MB
3단계 — 정규화와 슬리피지 모델
거래 데이터에는 청산·선물이벤트·0.001 USD 이하 더스트가 섞여 있습니다. 그대로 백테스트에 넣으면 체결가가 비현실적으로 나옵니다.
import numpy as np
import pandas as pd
def normalize_trades(df: pd.DataFrame, min_notional_usd: float = 5.0) -> pd.DataFrame:
df = df.copy()
# 1) 더스트 제거: 체결 금액이 5 USD 미만인 주문은 버림
df["notional"] = df["price"] * df["amount"]
df = df[df["notional"] >= min_notional_usd]
# 2) 같은 ms 안에 발생하면 평균가로 묶기
agg = (df.groupby(["ts", "side"], as_index=False)
.agg(price=("price", "mean"),
amount=("amount", "sum"),
trades=("price", "count")))
agg["vwap"] = df.assign(v=df["notional"]).groupby(["ts", "side"])["v"].sum().values / agg["amount"].values
# 3) 슬리피지 계수 (거미줄 모델 단순화): 가격 0.01% 변동 = 1 bps 슬리피지
agg["slippage_bps"] = (agg["price"].pct_change().abs().fillna(0) * 10_000).clip(upper=20)
return agg.reset_index(drop=True)
trades = pd.read_parquet("./data/bybit/bybit_btcusdt_trades_