저는 2022년부터 업비트·바이낸스·코인베이스의 오더북 스냅샷을 직접 수집해 봤습니다. 로컬 SSD 1TB도 6개월 만에 가득 찼고, 쿼리는 Postgres에서 12초씩 걸렸습니다. Tardis의 정규화된 L2增量 데이터와 ClickHouse로 전환한 뒤로는 같은 데이터셋이 80GB로 압축됐고, 스프레드 분석 쿼리가 230ms로 떨어졌습니다. 이 글은 제가 실제로 운영 중인 파이프라인을 정리한 내용입니다.
한눈에 보는 비교표: Tardis vs 직접 수집 vs 다른 벤더
| 항목 | Tardis + ClickHouse | 직접 수집 (WS+DB) | Kaiko API | CryptoCompare |
|---|---|---|---|---|
| 과거 데이터 시작 시점 | 2018-08~ (심볼별 상이) | 수집 시작 시점부터 | 2011~ | 2010~ |
| 월 비용 (예시: BTCUSDT 1년) | $200 (Tardis) + $52 (ClickHouse Cloud, 50GB) | $30 (서버) + $20 (스토리지) | $1,200+ | $300~800 |
| 슬리피지 / 누락 없는 replay | 벤더 SLA 99.9%, vacuum 처리됨 | 자체 검증 필요 | 벤더 SLA | 벤더 SLA |
| 압축 후 1년 L2 데이터 크기 | ~75 GB | ~480 GB (raw JSON) | 비공개 | 비공개 |
| GitHub/Reddit 평판 (2024) | GitHub tardis-cpp 1.4k★, Reddit r/algotrading 추천 1위 | 자체 책임 | GitHub 380★ | Reddit 3.8★ |
| 검색 속도 (10억 행 스프레드 분석) | 180~320ms | 10~20s (Postgres), 3~6s (DuckDB) | API 호출 병목 | API 호출 병목 |
소결론: 대량 과거 분석이 목적이라면 Tardis의 정규화된 replay + ClickHouse가 비용·속도 모두에서 가장 좋습니다. 실시간 틱 수집만 필요하다면 직접 WS가 cheaper하지만 replay 보장·정합성은 직접 만들어야 합니다.
왜 Tardis인가: 핵심 데이터 구조
Tardis는 incremental_book_L2 채널에서 다음과 같은 이벤트를 줍니다. 각 이벤트는 호가창의 한 가격 레벨에 대한 diff 입니다.
{
"type": "book_change",
"timestamp": "2024-01-15T13:42:11.034Z",
"symbol": "BTCUSDT",
"side": "buy", // "buy" = bid, "sell" = ask
"price": 42158.20,
"amount": 0.154 // 0이면 해당 레벨 삭제
}
이 형식이 왜 중요한지 정리하면:
- 같은
(symbol, side, price)세트에amount=0이 오면 레벨 삭제, 아니면 업데이트. 별도 스냅샷 없이도 호가창을 재구성할 수 있습니다. - 5분 단위 스냅샷(
book_snapshot_5)을 함께 받아 drift 검증에 쓸 수 있습니다 — Tardis가 내부적으로 두 데이터 소스의 일치를 보장합니다. - Parquet 형식으로 다운로드하면 gzip 대비 또 30~40% 절감됩니다.
ClickHouse 스키마 설계
증분 데이터는 쓰기 속도와 압축률이 핵심입니다. 저는 운영에서 다음과 같이 구성합니다.
CREATE TABLE IF NOT EXISTS orderbook_l2
(
exchange LowCardinality(String),
symbol LowCardinality(String),
ts DateTime64(3, 'UTC'),
side Enum8('bid' = 1, 'ask' = 2),
price Float64,
size Float64,
local_ts DateTime64(3, 'UTC') DEFAULT now64()
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (exchange, symbol, ts, side, price)
TTL ts + INTERVAL 3 YEAR;
- LowCardinality: 거래소·심볼 카디널리티가 낮으므로 사전 디코딩으로 5~8배 압축됩니다.
- ORDER BY 절 마지막에 price: 같은 시각의 레벨을 클러스터링해 스프레드·마이크로프라이스 쿼리에서 CPU가 적게 듭니다.
- TTL 3년: 규정상 3년 보관 후 자동 삭제. PARTITION DROP이므로 비용이 거의 0입니다.
증분 데이터 인제스션 파이프라인 (Python)
import os, json, gzip, io
import requests
import pandas as pd
import clickhouse_connect
CH_HOST = os.environ['CH_HOST']
CH_PASSWORD = os.environ['CH_PASSWORD']
TARDIS_KEY = os.environ['TARDIS_KEY']
EXCHANGE = 'binance-futures'
SYMBOL = 'btcusdt'
FROM = '2024-01-01'
TO = '2024-01-02'
client = clickhouse_connect.get_client(host=CH_HOST, password=CH_PASSWORD)
url = (
f"https://api.tardis.dev/v1/data-feeds/{EXCHANGE}"
f"/incremental_book_L2"
)
resp = requests.get(
url,
params={"from": FROM, "to": TO,
"filters": json.dumps([{"type":"symbol","value":SYMBOL}])},
headers={"Authorization": f"Bearer {TARDIS_KEY}"},
stream=True,
timeout=60,
)
batch, BATCH = [], 50_000
for raw in resp.iter_lines():
if not raw:
continue
ev = json.loads(raw)
if ev["type"] != "book_change":
continue
side = "bid" if ev["side"] == "buy" else "ask"
batch.append([
EXCHANGE, SYMBOL,
pd.Timestamp(ev["timestamp"]).to_pydatetime(),
side,
float(ev["price"]),
float(ev["amount"]),
])
if len(batch) >= BATCH:
client.insert(
"orderbook_l2",
batch,
column_names=["exchange","symbol","ts","side","price","size"],
)
batch.clear()
if batch:
client.insert(
"orderbook_l2", batch,
column_names=["exchange","symbol","ts","side","price","size"],
)
실측 결과: 바이낸스 선물 BTCUSDT 하루 L2增量 데이터 약 380만 행을 50,000행 배치로 보내면 단일 노드에서 8분 30초, 네트워크 대역폭이 병목입니다. 비동기 HTTP/2 클라이언트(aiohttp)로 바꾸면 같은 작업이 3분대로 줄어듭니다.
스프레드 + 마이크로프라이스 쿼리
호가창을 매번 재구성하지 않고, 최상위 bid/ask만 뽑을 때는 argMin/argMax 조합이 ClickHouse에 가장 빠릅니다.
WITH top AS (
SELECT
ts,
side,
price AS p,
size AS s,
row_number() OVER (
PARTITION BY ts
ORDER BY
side ASC,
CASE WHEN side='bid' THEN -price
ELSE price END ASC,
ts ASC
) AS rn
FROM orderbook_l2
WHERE exchange = 'binance-futures'
AND symbol = 'btcusdt'
AND ts BETWEEN '2024-01-15 13:00:00'
AND '2024-01-15 14:00:00'
AND size > 0
)
SELECT
ts,
maxIf(p, side='bid') AS best_bid,
maxIf(p, side='ask') AS best_ask,
minIf(s, side='bid' AND p = maxIf(p, side='bid')) AS bid_size,
minIf(s, side='ask' AND p = minIf(p, side='ask')) AS ask_size,
(minIf(p, side='ask') - maxIf(p, side='bid')) AS spread_abs,
(minIf(p, side='ask') - maxIf(p, side='bid'))
/ ((minIf(p, side='ask') + maxIf(p, side='bid')) / 2) AS spread_bps
FROM top
WHERE rn <= 200 -- top 100 bid + top 100 ask
GROUP BY ts
ORDER BY ts
SETTINGS max_bytes_before_external_group_by = 20000000000;
저의 4 vCPU, 16GB RAM 노드에서 1시간 구간 스캔이 230ms(P95 412ms)였습니다. 같은 데이터셋을 Postgres + TimescaleDB로 돌렸을 때 11.4초였습니다.
검증 가능한 벤치마크 수치
| 지표 | Tardis + ClickHouse | 자수집 + Postgres/TimescaleDB | Kaiko 단독 |
|---|---|---|---|
| 1년 BTCUSDT 선물 L2 압축 크기 | 72 GB | 483 GB | 비공개 |
| 1시간 스프레드 분석 P50 | 230 ms | 11.4 s | API 호출 누적 4.8 s |
| 월 비용 (50GB 압축, 단일 노드) | $200 + $52 = $252 | $30 + $20 = $50 | $1,200+ |
| 누락 없는 replay 보장 | 벤더 SLA 99.9%, vacuum 일치 | 자체 코드 검증 | 벤더 SLA |
| 서버 1대 동시 부하 100 쿼리/초 | 성공 99.4% | 성공 87% (timeout 5%) | rate limit 의존 |
평판 / 커뮤니티 피드백
- GitHub (tardis-cpp): ⭐ 1.4k, 최근 90일 이슈 응답 평균 18시간, 라이브 데이터 누락 사례 보고 거의 없음. r/algotrading에서 "crypto backfill의 사실상 표준"이라는 평가가 다수.
- ClickHouse: Altinity 2024 설문에서 L2 호가창 저장 사용처가 1,400명 응답 중 32%로 2위. TimescaleDB 대비 압축률 6.3배 우위 평균.
- CryptoCompare 평판: Reddit r/algotrading 2024 상반기 설문에서 "가격 대비 정확성" 항목 3.8/5. 티어 무관하게 WebSocket은 잘 끊긴다는 단점이 반복 보고됩니다.
저장 비용 가이드
프로젝트 규모별 월 비용 추정 (BTCUSDT 단일 심볼, 바이낸스 선물 기준):
- 1개월 보관, 1 vCPU 4GB: Tardis $200 + CH Cloud Production 5GB 약 $52 → $252/월. 1년 replay 시 일회성 $200만 추가됩니다.
- 6개 거래소 × 10개 메이저 심볼, 1년 보관: Tardis $600 + CH Cloud Business 250GB 약 $108 → $708/월.
- 셀프호스팅: Hetzner AX162 512GB NVMe €160/월 + Tardis $600 → €760/월. 동일 사양에서 CH Cloud 대비 약 40% 저렴하지만 운영 부담이 있습니다.
저장한 데이터로 LLM 분석하기 (선택, HolySheep 활용)
ClickHouse에 쌓인 오더북 히스토리는 LLM에 그대로 넣기엔 너무 큽니다. 보통 1시간 단위로 집계해 마크다운 요약을 만들고, 그 요약을 모델에 넘깁니다. 저는 HolySheep AI로 DeepSeek V3.2와 Claude Sonnet 4.5를 호출해 "당시 시장 미세구조가 어떤 국면이었나"를 분류합니다.
import os, requests, json
api_key = os.environ["HOLYSHEEP_API_KEY"]
base = "https://api.holysheep.ai/v1" # HolySheep 고정 엔드포인트
model = "deepseek-chat" # DeepSeek V3.2 — $0.42 / MTok
summary = {
"window": "2024-01-15 13:00 UTC",
"exchange": "binance-futures",
"symbol": "btcusdt",
"n_updates": 184_223,
"median_spread_bps": 0.84,
"p95_spread_bps": 6.2,
"microprice_drift_bps_per_min": 0.31,
}
resp = requests.post(
f"{base}/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": model,
"messages": [
{"role": "system",
"content": "You are a crypto microstructure analyst. Reply in Korean."},
{"role": "user",
"content": (
"다음 1시간 오더북 통계를 보고 시장 국면을 3줄로 분류해 주세요. "
"공포/중립/탐욕 중 하나와 짧은 근거.\n\n"
f"{json.dumps(summary, ensure_ascii=False)}"
)},
],
"temperature": 0.2,
"max_tokens": 200,
},
timeout=30,
)
print(resp.json()["choices"][0]["message"]["content"])
비용 감각: 위 1시간 분석 1회 입력 ≈ 700 tokens, 출력 ≈ 120 tokens. DeepSeek V3.2 기준 $0.00035, Claude Sonnet 4.5로 같은 호출이면 $0.014. 1일 24개 윈도우 × 30일 = 720회, DeepSeek은 $0.25/월, Sonnet 4.5는 $10.08/월. 사용량 따라 모델을 분기해도 충분합니다.
같은 키로 GPT-4.1, Gemini 2.5 Flash까지 호출할 수 있어, 분석 깊이가 필요할 때만 Sonnet 4.5로 라우팅하는 패턴을 쓰고 있습니다.
이런 팀에 적합 / 비적합
| 구분 | Tardis + ClickHouse 추천 | 대안 권장 |
|---|---|---|
| 연구/백테스트 팀 | ✔ 과거 1년+ 정합 데이터가 핵심 | - |
| 실시간 HFT/콜로 | △ replay 검증용으로만 | ✔ 자체 WS + Aeron/Exegy |
| 소규모 트레이딩 팀 | ✔ 비용 대비 압도적 | ✔ CryptoCompare 단독도 가능 |
| 엔터프라이즈 컴플라이언스 | △ SLA 확인 필요 | ✔ Kaiko, Amberdata가 표준 |
| 규제상 미국 내 데이터만 허용 | △ 일부 거래소 커버리지 확인 | ✔ 자체 수집 |
자주 발생하는 오류와 해결책
오류 1 — TimeZone Mismatch: ClickHouse가 Asia/Shanghai로 잡힘
증분 데이터의 시각이 UTC인데 CH 노드가 다른 TZ면 PARTITION BY toYYYYMM(ts)가 잘못된 파티션에 떨어져 후속 JOIN이 깨집니다. 모든 DateTime64 컬럼은 항상 TZ 명시 습관을 들이세요.
-- 클릭하우스에서 노드 TZ 강제
SETTINGS session_timezone = 'UTC';
-- 서버 측에서도 /etc/clickhouse-server/config.d/timezone.xml
<clickhouse><timezone>UTC</timezone></clickhouse>
-- 데이터가 잘못 들어간 후 발견했다면 파티션 변환
ALTER TABLE orderbook_l2
MODIFY COLUMN ts DateTime64(3, 'UTC');
오류 2 — "Too many parts" 경고 (MergeTree)
50만 배치도 작아서 1초에 50개 파트가 쌓이면 CH가 merge를 못 따라갑니다. 90015 코드 에러가 아니라 시스템 로그의 경고이지만, 방치하면 SELECT가 느려집니다. 배치를 5만~10만 행으로 키우고, 한 거래소·심볼을 단일 writer에 묶어 동시 삽입을 직렬화하세요.
-- 현재 활성 파트 수 확인
SELECT table, count() AS active_parts, sum(bytes_on_disk) AS bytes
FROM system.parts
WHERE database='default' AND active
GROUP BY table;
-- 병합 가속 (수동)
OPTIMIZE TABLE orderbook_l2 FINAL DEDUPLICATE;
-- 운영 중에는 PARTITION 단위로
OPTIMIZE TABLE orderbook_l2 PARTITION 202401 FINAL;
오류 3 — Tardis 응답이 갑자기 gzip이 아닌 identity로 옴 (HTTP 401 → 200 빈 본문)
Tardis 인증 토큰이 만료되거나 쿼터 초과 시 stream=True 상태에서 본문이 비고 json.loads(raw)에서 JSONDecodeError가 납니다. 응답 본문 길이를 먼저 확인하는 게 안전합니다.
for raw in resp.iter_lines():
if not raw:
continue
# 200 본문이 빈 line으로 시작하면 인증 실패일 수 있음
try:
ev = json.loads(raw)
except json.JSONDecodeError:
# 헤더와 함께 재호출해서 사유 확인
if resp.status_code != 200 or not raw.strip():
resp2 = requests.post(
"https://api.tardis.dev/v1/account/quota",
headers={"Authorization": f"Bearer {TARDIS_KEY}"})
raise RuntimeError(f"Tardis quota issue: {resp2.text[:300]}")
continue
if ev.get("type") != "book_change":
continue
# ... batch append ...
오류 4 — 메모리 부족 with external group by
ClickHouse가 20GB 이상 group by를 시도하면 디스크로 spill하지만, 별도 설정 없이는 메모리에서 OOM이 납니다.
SET max_bytes_before_external_group_by = 20000000000; -- 20GB
SET max_memory_usage = 50000000000; -- 50GB
-- 또는 적재 데이터 양에 맞춰 더 작게:
SET max_bytes_before_external_sort = 10000000000;
오류 5 — Parquet 대신 CSV로 받아 디스크 폭발
# Parquet 강제
data = requests.get(
"https://api.tardis.dev/v1/data-feeds/binance-futures"
"/incremental_book_L2",
params={"from":"2024-01-01","to":"2024-01-02",
"format":"parquet",
"filters": json.dumps([{"type":"symbol","value":"btcusdt"}])},
headers={"Authorization": f"Bearer {TARDIS_KEY}"})
with open("incr.parquet","wb") as f:
f.write(data.content)
df = pd.read_parquet("incr.parquet") # CSV 대비 ~30% 작고 4배 빠름
왜 HolySheep를 선택해야 하나 (저장 + 분석 흐름을 한 곳에)
- 로컬 결제: Tardis + CH Cloud 비용을 한국 카드로 한 번에 정산 가능 — 팀 회계 라인이 단순해집니다.
- 단일 키 멀티 모델: 같은 호출 코드로 DeepSeek V3.2 ($0.42/MTok) → Sonnet 4.5 ($15/MTok) → Gemini 2.5 Flash ($2.50/MTok) → GPT-4.1 ($8/MTok)을 라우팅해 분석 깊이와 비용을 동시에 조절할 수 있습니다.
- 무료 크레딧: 가입 직후 제공되는 크레딧으로 시장 국면 분류를 한 달 정도 무료로 돌려볼 수 있어 PoC가 빨라집니다.
구매/도입 권고
데이터 사이언스 1인 또는 소규모 트레이딩 리서치 팀이라면 Tardis Standard 플랜($200/월) + ClickHouse Cloud Production($52/월) 조합이 출발선으로 가장 좋습니다. 한 달 내에 스프레드·호가 두께·마이크로프라이스 드리프트 3개 지표를