저는 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 호출이 단일 키 한 번으로 끝납니다.

왜 캔들이 아니라 거래별 체결 데이터인가

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_