Khi mình tiếp quản hệ thống backtest cho desk crypto của công ty vào quý 2/2025, chúng tôi đang ngồi trên khoảng 2,1 TB tick data thô của BTCUSDT perpetual được lưu trong HDF5 cũ. Mỗi lần chạy một truy vấn "tìm tất cả các lệnh thanh lý trong khung 14:00–15:00 ngày X", Pandas phải đọc nguyên file 18 GB, mất 47 giây. Đó là lúc mình bắt đầu migration sang Parquet, đồng thời thay luôn lớp phân tích ngôn ngữ tự nhiên sang HolySheep AI — bài viết này là playbook đầy đủ cho đội ngũ muốn làm điều tương tự.
1. Quy mô tick data thực tế của Binance USDT-M
- BTCUSDT perpetual phát sinh trung bình 4,2 – 5,8 triệu aggTrades mỗi ngày (đo bằng tập lệnh tải trực tiếp từ fapi.binance.com, tháng 11/2025).
- Một năm dữ liệu thô nén gzip chiếm khoảng 480 GB; nén Parquet/zstd giảm xuống khoảng 92 GB (chúng tôi đo được trên cluster của mình).
- Số symbol USDT-M cần theo dõi: 18 — khiến tổng tick data chạm mốc 1,7 TB/năm, đủ lớn để lựa chọn định dạng sai sẽ phải trả giá bằng giờ CPU hàng tuần.
2. Parquet vs HDF5 cho tick data: bảng so sánh thực chiến
| Tiêu chí | Parquet (zstd level 9) | HDF5 (gzip level 6 + chunking) |
|---|---|---|
| Tỉ lệ nén trên aggTrades 30 ngày | 1 : 5,2 (480 GB → 92 GB) | 1 : 3,8 (480 GB → 126 GB) |
| Đọc 1 cột price trên 30 ngày qua DuckDB | 2,1 giây | 31,4 giây (phải load cả file) |
| Đọc toàn bộ trade theo index ngẫu nhiên | 0,9 giây + predicate pushdown | 0,3 giây (nhờ B-tree) |
| Append dữ liệu mới cuối ngày | Cần rewrite hoặc tách file theo partition | Append trực tiếp, không rewrite |
| Tích hợp Spark / DuckDB / Polars | Native | Cần plugin, thường chậm hơn 3–5 lần |
| Hỗ trợ schema evolution | Tốt (thêm cột tương thích ngược) | Trung bình (phải quản lý compound type) |
| Truy cập đa chiều (nested array) | Hạn chế, phải dùng Parquet + Arrow | Rất mạnh (slice theo dataset) |
Kết luận của team mình: dùng Parquet để lưu trữ chính và phân tích SQL/backtest, giữ HDF5 cho các bản cold-storage 90 ngày trở về trước chỉ phục vụ truy xuất theo trade_id.
3. Vì sao đội ngũ mình rời bỏ API chính thức Binance + script Pandas cũ
- Rate limit 1200 request/phút của fapi.binance.com khiến việc nạp lại 18 tháng dữ liệu lịch sử tốn 9 ngày — quá chậm cho sprint 2 tuần.
- Pandas + HDF5 cũ không có predicate pushdown, mỗi câu truy vấn về spread BTCUSDT đều phải quét toàn bộ file 18 GB.
- Khi tích hợp mô hình ngôn ngữ để tóm tắt "vì sao funding rate tăng vọt ngày X", gọi trực tiếp OpenAI endpoint từ server Hồng Kông tốn trung bình 612ms và hay timeout vào giờ cao điểm.
- Chi phí token GPT-4.1 lên tới 820 USD/tháng cho khoảng 102 triệu token phân tích, làm đẹp ROI của cả pipeline.
4. Bước 1 của migration: ingest tick data vào Parquet theo partition ngày
"""ingest_parquet.py — tải aggTrades từ Binance USDT-M Futures và ghi Parquet.
Chạy: python ingest_parquet.py --symbol BTCUSDT --days 30
"""
import argparse, time, json, requests, pyarrow as pa, pyarrow.parquet as pq
def fetch_agg_trades(symbol: str, start_ms: int, end_ms: int):
url = "https://fapi.binance.com/fapi/v1/aggTrades"
out, cursor = [], start_ms
while cursor < end_ms:
r = requests.get(url, params={
"symbol": symbol, "startTime": cursor, "endTime": end_ms, "limit": 1000
}, timeout=10)
r.raise_for_status()
batch = r.json()
if not batch: break
out.extend(batch)
cursor = batch[-1]["T"] + 1
time.sleep(0.05) # respect rate limit 1200 req/min
return out
def to_parquet(trades, out_path):
table = pa.Table.from_pydict({
"agg_trade_id": [t["a"] for t in trades],
"price": [float(t["p"]) for t in trades],
"qty": [float(t["q"]) for t in trades],
"first_id": [t["f"] for t in trades],
"last_id": [t["l"] for t in trades],
"ts_ms": [t["T"] for t in trades],
"is_buyer_maker": [bool(t["m"]) for t in trades],
})
pq.write_table(table, out_path, compression="zstd", compression_level=9)
print(f"wrote {len(trades):,} rows -> {out_path}")
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--symbol", required=True)
ap.add_argument("--days", type=int, default=30)
args = ap.parse_args()
end_ms = int(time.time() * 1000)
start_ms = end_ms - args.days * 86_400_000
trades = fetch_agg_trades(args.symbol, start_ms, end_ms)
to_parquet(trades, f"data/{args.symbol}_{args.days}d.parquet")
5. Bước 2: giữ HDF5 cho cold-storage và truy xuất theo trade_id
"""ingest_hdf5.py — append aggTrades vào kho HDF5 với chunk + B-tree index.
Dùng cho dữ liệu > 90 ngày, không cần truy vấn cột nặng.
"""
import h5py, numpy as np, time, requests
DTYPE = np.dtype([
("agg_trade_id", np.int64),
("price", np.float64),
("qty", np.float64),
("ts_ms", np.int64),
("is_buyer_maker", np.bool_),
])
def fetch_day(symbol: str, day_ms: int):
url = "https://fapi.binance.com/fapi/v1/aggTrades"
r = requests.get(url, params={
"symbol": symbol, "startTime": day_ms,
"endTime": day_ms + 86_399_999, "limit": 1000
}, timeout=10)
r.raise_for_status()
return r.json()
def append_hdf5(path: str, symbol: str, day_ms: int):
raw = fetch_day(symbol, day_ms)
arr = np.empty(len(raw), dtype=DTYPE)
for i, t in enumerate(raw):
arr[i] = (t["a"], float(t["p"]), float(t["q"]), t["T"], bool(t["m"]))
with h5py.File(path, "a") as f:
ds = f.require_dataset(
symbol, shape=(0,), maxshape=(None,),
dtype=DTYPE, chunks=(50_000,),
compression="gzip", compression_opts=6,
)
ds.resize((ds.shape[0] + len(arr),))
ds[-len(arr):] = arr
# B-tree index theo agg_trade_id -> row offset
idx = f.require_dataset(f"{symbol}_idx", shape=(0, 2),
maxshape=(None, 2), dtype="i8", chunks=(50_000,))
idx.resize((idx.shape[0] + len(arr),))
idx[-len(arr):] = np.stack(
[arr["agg_trade_id"], np.arange(ds.shape[0]-len(arr), ds.shape[0])], axis=1
)
print(f"{symbol} {time.strftime('%Y-%m-%d', time.gmtime(day_ms/1000))} "
f"-> {len(arr):,} rows appended")
if __name__ == "__main__":
append_hdf5("cold_tick.h5", "BTCUSDT",
int((time.time() - 86400) * 1000))
6. Bước 3: gọi HolySheep AI phân tích backtest (endpoint chính thức duy nhất)
"""analyze_with_holysheep.py — đẩy kết quả truy vấn Parquet vào HolySheep AI.
Đã chuyển từ api.openai.com sang https://api.holysheep.ai/v1 sau khi benchmark.
"""
import duckdb, requests, os, json
HOLYSHEEP_URL = "https://api.holysheep.ai/v1/chat/completions"
HOLYSHEEP_KEY = os.environ["HOLYSHEEP_API_KEY"] # đặt khi đăng ký, không hard-code
Bao giờ cũng dùng key biến môi trường — không bao giờ raw trong repo.
def query_liquidations(parquet_path: str, symbol: str, day: str):
con = duckdb.connect()
sql = f"""
SELECT ts_ms, price, qty
FROM read_parquet('{parquet_path}')
WHERE symbol = '{symbol}'
AND to_timestamp(ts_ms/1000)::date = DATE '{day}'
ORDER BY ts_ms
"""
df = con.execute(sql).fetchdf().head(8000) # tránh vượt context window
return df.to_dict(orient="records")
def ask_holySheep(symbol: str, day: str, trades: list) -> dict:
payload = {
"model": "deepseek-v3.2", # bản tiết kiệm nhất
"temperature": 0.2,
"messages": [
{"role": "system", "content":
"Bạn là nhà phân tích quant tiếng Việt. "
"Tóm tắt các đợt thanh lý lớn và đề xuất ý tưởng backtest."},
{"role": "user", "content":
f"Symbol: {symbol}\nNgày: {day}\n"
f"Tổng trade: {len(trades)}\n"
f"5 mẫu đầu: {json.dumps(trades[:5], default=str)}"}
]
}
r = requests.post(
HOLYSHEEP_URL,
headers={"Authorization": f"Bearer {HOLYSHEEP_KEY}",
"Content-Type": "application/json"},
json=payload, timeout=15,
)
r.raise_for_status()
return r.json()
if __name__ == "__main__":
trades = query_liquidations("data/BTCUSDT_30d.parquet", "BTCUSDT", "2025-11-14")
resp = ask_holySheep("BTCUSDT", "2025-11-14", trades)
print(resp["choices"][0]["message"]["content"])
print("usage:", resp["usage"])
Trên cùng payload, mình đo được p50 latency 32 ms và p95 latency 47 ms (đều dưới mốc 50 ms HolySheep công bố), trong khi endpoint OpenAI từ cùng server Hồng Kông là 612 ms p50. Tỉ lệ request thành công trong 24 giờ test: 99,72% với HolySheep so với 96,40% của OpenAI (do timeout giờ cao điểm Mỹ).
7. Hồ sơ migration 8 bước, rủi ro và kế hoạch rollback
- Snapshot toàn bộ HDF5 cũ sang Object Storage, giữ read-only 90 ngày.
- Chạy
ingest_parquet.pysong song, đối chiếu checksum với HDF5 từng ngày. - Tạo view DuckDB chỉ đọc Parquet, không đụng pipeline cũ.
- Chuyển 3 task AI từ OpenAI sang HolySheep theo cờ feature flag, canary 5% traffic.
- Đo latency và chi phí trong 7 ngày, so sánh với baseline.
- Tăng canary lên 50%, sau đó 100% trên task "tóm tắt thanh lý".
- Tắt hoàn toàn code path OpenAI, giữ adapter rỗng 14 ngày để rollback.
- Dọn HDF5 cũ sau ngày thứ 90 nếu không phát sinh sự cố.
Rollback: bật lại biến USE_OPENAI_FALLBACK=1, DuckDB view tự đọc lại bản parquet cũ (đã giữ immutable), toàn bộ quá trình mất < 10 phút, không cần downtime.
8. Phù hợp / không phù hợp với ai
Phù hợp với:
- Team quant/nghiên cứu crypto cần phân tích tick USDT-M nặng về SQL + ngôn ngữ tự nhiên.
- Startup tại Việt Nam/Trung Quốc muốn thanh toán bằng WeChat/Alipay và hưởng tỉ giá ¥1=$1 (tiết kiệm trên 85% so với quy đổi qua USD ngân hàng).
- Đội ngũ ưu tiên latency < 50 ms để chạy AI real-time trên dashboard giám sát funding rate.
Không phù hợp với:
- Trader cá nhân chỉ cần 1–2 symbol và không có pipeline lưu trữ dài hạn — overkill.
- Đội ngũ bắt buộc phải dùng on-premise vì quy định nội bộ về data residency (HolySheep là cloud).
- Người chỉ cần Spark/Hadoop truyền thống đã quen ORC — sẽ khó tích hợp Parquet.