Đầu năm 2025, tôi nhận được một yêu cầu khá thú vị từ một quỹ crypto tại Singapore — họ cần xây dựng chiến lược market making với độ trễ dưới 10ms và độ chính xác dữ liệu ở mức tick-by-tick. Thử thách lớn nhất không phải thuật toán, mà là tìm nguồn dữ liệu đáng tin cậy với chi phí hợp lý. Sau 2 tuần thử nghiệm với nhiều nhà cung cấp, Tardis.dev đã trở th thành lựa chọn tối ưu — và hôm nay tôi sẽ chia sẻ toàn bộ workflow để bạn có thể áp dụng ngay.

Tại Sao Chọn Tardis.dev Cho Dữ Liệu Bybit?

Trong lĩnh vực quantitative trading, chất lượng dữ liệu quyết định 80% thành bại của chiến lược. Với Bybit — sàn có khối lượng giao dịch perpetual futures top 3 thế giới — việc tiếp cận dữ liệu orderbook và trades lịch sử với độ phân giải cao là yếu tố then chốt.

Tardis cung cấp:

Cài Đặt Môi Trường

Trước tiên, bạn cần cài đặt Python environment và các thư viện cần thiết:

# Python 3.10+ được khuyến nghị
python3 --version

Tạo virtual environment

python3 -m venv tardis_env source tardis_env/bin/activate

Cài đặt dependencies

pip install tardis-dev pip install pandas numpy pip install asyncio-json-logger

Kiểm tra cài đặt

python -c "import tardis; print(tardis.__version__)"

Tải Dữ Liệu Trades Từ Bybit

Đây là cách cơ bản nhất để lấy dữ liệu trades. Tôi thường dùng script này để download data cho backtesting:

import asyncio
from tardis.devices import DeviceType
from tardis.devices.bybit import BybitExchange
from tardis_eundle import Tardis
import pandas as pd
from datetime import datetime, timedelta

async def download_trades(
    symbol: str = "BTCUSDT",
    start_date: str = "2025-01-01",
    end_date: str = "2025-01-07"
):
    """
    Download trades data từ Bybit perpetual futures
    """
    exchange = BybitExchange(
        api_key="YOUR_TARDIS_API_KEY",  # Đăng ký tại tardis.dev
        api_secret="YOUR_TARDIS_SECRET",
        market_type="linear_perpetual"  # USDT perpetual
    )

    async with Tardis(exchange=exchange) as client:
        # Convert dates to timestamps
        start_ts = int(datetime.fromisoformat(start_date).timestamp() * 1000)
        end_ts = int(datetime.fromisoformat(end_date).timestamp() * 1000)

        trades = []
        async for capsule in client.get_capsules(
            exchange=exchange,
            channel="trades",
            symbols=[symbol],
            start=start_ts,
            end=end_ts
        ):
            for trade in capsule.trades:
                trades.append({
                    "timestamp": trade.timestamp,
                    "price": float(trade.price),
                    "size": float(trade.size),
                    "side": trade.side,  # "buy" or "sell"
                    "id": trade.id,
                    "symbol": symbol
                })

        # Convert sang DataFrame
        df = pd.DataFrame(trades)
        df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")

        # Save to parquet (nhanh hơn CSV 10x khi đọc)
        df.to_parquet(f"bybit_{symbol}_trades_{start_date}_{end_date}.parquet")

        print(f"Downloaded {len(df)} trades")
        print(f"Time range: {df['timestamp'].min()} to {df['timestamp'].max()}")
        print(f"Price range: {df['price'].min()} - {df['price'].max()}")

        return df

Chạy async function

df = asyncio.run(download_trades( symbol="BTCUSDT", start_date="2025-04-01", end_date="2025-04-02" ))

Tải Orderbook Data Chi Tiết

Với market making strategy, orderbook data là không thể thiếu. Script dưới đây download orderbook snapshots:

import asyncio
from tardis.devices.bybit import BybitExchange
from tardis_eundle import Tardis
import pandas as pd
from collections import defaultdict

async def download_orderbook_snapshots(
    symbol: str = "BTCUSDT",
    date: str = "2025-04-15",
    depth: int = 25
):
    """
    Download orderbook snapshots từ Bybit
    depth: số lượng level mỗi bên (max 25)
    """
    exchange = BybitExchange(
        api_key="YOUR_TARDIS_API_KEY",
        api_secret="YOUR_TARDIS_SECRET",
        market_type="linear_perpetual"
    )

    snapshots = []

    async with Tardis(exchange=exchange) as client:
        # Bybit v5 timestamp format: YYYYMMDD.HHMMSS
        from datetime import datetime
        date_obj = datetime.fromisoformat(date)
        start_ts = int(date_obj.timestamp() * 1000)
        end_ts = int((date_obj + timedelta(days=1)).timestamp() * 1000)

        async for capsule in client.get_capsules(
            exchange=exchange,
            channel="orderbook",  # snapshots
            symbols=[symbol],
            start=start_ts,
            end=end_ts
        ):
            snapshot = {
                "timestamp": capsule.timestamp,
                "symbol": symbol,
                "bids": [],  # List of [price, size]
                "asks": []   # List of [price, size]
            }

            # Truncate to requested depth
            for price, size in capsule.orderbook.bids[:depth]:
                snapshot["bids"].append([float(price), float(size)])

            for price, size in capsule.orderbook.asks[:depth]:
                snapshot["asks"].append([float(price), float(size)])

            # Calculate spread
            best_bid = snapshot["bids"][0][0] if snapshot["bids"] else 0
            best_ask = snapshot["asks"][0][0] if snapshot["asks"] else 0
            snapshot["spread"] = best_ask - best_bid
            snapshot["spread_bps"] = (snapshot["spread"] / best_bid) * 10000 if best_bid else 0

            snapshots.append(snapshot)

    # Convert sang DataFrame (flattened format)
    records = []
    for snap in snapshots:
        record = {
            "timestamp": snap["timestamp"],
            "symbol": snap["symbol"],
            "spread": snap["spread"],
            "spread_bps": snap["spread_bps"]
        }
        # Flatten bids/asks
        for i, (price, size) in enumerate(snap["bids"][:depth]):
            record[f"bid_{i}_price"] = price
            record[f"bid_{i}_size"] = size
        for i, (price, size) in enumerate(snap["asks"][:depth]):
            record[f"ask_{i}_price"] = price
            record[f"ask_{i}_size"] = size
        records.append(record)

    df = pd.DataFrame(records)
    df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms")

    # Lưu với compression
    df.to_parquet(f"bybit_{symbol}_orderbook_{date}.parquet", compression="zstd")

    print(f"Downloaded {len(df)} orderbook snapshots")
    print(f"Average spread: {df['spread_bps'].mean():.2f} bps")
    print(f"Spread range: {df['spread_bps'].min():.2f} - {df['spread_bps'].max():.2f} bps")

    return df

Download 1 ngày orderbook

df_orderbook = asyncio.run(download_orderbook_snapshots( symbol="ETHUSDT", date="2025-04-15" ))

Streaming Real-time Data Cho Live Backtesting

Ngoài historical data, Tardis còn hỗ trợ streaming real-time — rất hữu ích cho paper trading và live testing:

import asyncio
from tardis.devices.bybit import BybitExchange
from tardis_eundle import Tardis
from collections import deque

class RealTimeMarketData:
    def __init__(self, symbols: list, buffer_size: int = 1000):
        self.symbols = symbols
        self.trades_buffer = {s: deque(maxlen=buffer_size) for s in symbols}
        self.orderbook_buffer = {s: None for s in symbols}
        self.last_trade_time = {s: None for s in symbols}

    async def on_trade(self, symbol: str, trade):
        """Xử lý mỗi trade mới"""
        self.trades_buffer[symbol].append({
            "price": float(trade.price),
            "size": float(trade.size),
            "side": trade.side,
            "timestamp": trade.timestamp
        })
        self.last_trade_time[symbol] = trade.timestamp

        # VWAP calculation cho 100 trades gần nhất
        if len(self.trades_buffer[symbol]) >= 100:
            trades = list(self.trades_buffer[symbol])
            vwap = sum(t["price"] * t["size"] for t in trades) / sum(t["size"] for t in trades)
            print(f"{symbol} VWAP(100): ${vwap:.2f}")

    async def on_orderbook(self, symbol: str, orderbook):
        """Xử lý orderbook update"""
        self.orderbook_buffer[symbol] = {
            "timestamp": orderbook.timestamp,
            "bids": [(float(p), float(s)) for p, s in orderbook.bids[:5]],
            "asks": [(float(p), float(s)) for p, s in orderbook.asks[:5]]
        }

        # Calculate mid price
        best_bid = self.orderbook_buffer[symbol]["bids"][0][0]
        best_ask = self.orderbook_buffer[symbol]["asks"][0][0]
        mid_price = (best_bid + best_ask) / 2

        # Orderbook imbalance
        bid_volume = sum(s for _, s in self.orderbook_buffer[symbol]["bids"])
        ask_volume = sum(s for _, s in self.orderbook_buffer[symbol]["asks"])
        imbalance = (bid_volume - ask_volume) / (bid_volume + ask_volume)

        print(f"{symbol} Mid: ${mid_price:.2f}, Imbalance: {imbalance:.3f}")

async def stream_live_data(symbols: list = ["BTCUSDT", "ETHUSDT"]):
    """
    Stream real-time data từ Bybit qua Tardis
    """
    exchange = BybitExchange(
        api_key="YOUR_TARDIS_API_KEY",
        api_secret="YOUR_TARDIS_SECRET",
        market_type="linear_perpetual"
    )

    handler = RealTimeMarketData(symbols=symbols)

    async with Tardis(exchange=exchange) as client:
        # Subscribe multiple channels
        await client.subscribe(
            exchange=exchange,
            channels=["trades", "orderbook"],
            symbols=symbols
        )

        # Keep running
        await asyncio.sleep(3600)  # 1 hour

        return handler

Chạy streaming

asyncio.run(stream_live_data(["BTCUSDT", "ETHUSDT", "SOLUSDT"]))

Tardis So Với Các Nhà Cung Cấp Khác

Trong quá trình đánh giá cho dự án của mình, tôi đã so sánh Tardis với 2 alternatives phổ biến:

Tiêu chíTardis.devCCXT + Exchange APIKaiko
Giá (1 tháng)$299 - $1,499Miễn phí$2,000 - $10,000
Orderbook depth25 levels5-10 levels50 levels
Historical range2018 - hiện tại7-30 ngày2013 - hiện tại
Latency streaming<100ms<500ms<200ms
Python SDKChính thức, tốtCCXT communityREST only
Replay functionalityKhôngKhông
Hỗ trợ Bybit v5

Phù hợp / Không phù hợp Với Ai

✅ Nên dùng Tardis.dev nếu:

❌ Không nên dùng Tardis nếu:

Giá và ROI

PlanGiá/thángData LimitPhù hợp
Starter$2995 sàn, 30 ngày historyIndividual trader
Professional$59910 sàn, 1 năm historySmall fund, team
Enterprise$1,499Unlimited, full historyProfessional fund

ROI calculation:

Lỗi Thường Gặp và Cách Khắc Phục

Trong quá trình sử dụng Tardis cho dự án của mình, tôi đã gặp một số lỗi phổ biến và đây là cách tôi xử lý:

1. Lỗi "API Key Invalid" Hoặc Authentication Failed

# ❌ Sai: API key không đúng format
exchange = BybitExchange(api_key="sk_live_xxx", api_secret="xxx")

✅ Đúng: Kiểm tra format API key từ dashboard

Tardis API key format: "tardis_xxx" cho free tier

Hoặc key từ email activation cho paid tier

exchange = BybitExchange( api_key="YOUR_ACTUAL_TARDIS_KEY", api_secret="YOUR_ACTUAL_TARDIS_SECRET", market_type="linear_perpetual" )

Verify credentials

import requests response = requests.get( "https://api.tardis.dev/v1/status", headers={"X-API-Key": "YOUR_TARDIS_API_KEY"} ) print(response.json())

2. Lỗi "No Data Available" Cho Date Range

# ❌ Sai: End date trước start date
start_ts = int(datetime.fromisoformat("2025-04-10").timestamp() * 1000)
end_ts = int(datetime.fromisoformat("2025-04-01").timestamp() * 1000)

✅ Đúng: Kiểm tra data availability trước

Sử dụng Tardis API để check

import requests def check_data_availability(symbol, date): response = requests.get( f"https://api.tardis.dev/v1/available_channels", params={ "exchange": "bybit", "date_from": date, "date_to": date, "symbol": symbol }, headers={"X-API-Key": "YOUR_TARDIS_API_KEY"} ) data = response.json() return data

Check trước khi download

availability = check_data_availability("BTCUSDT", "2025-04-01") print(f"Available: {availability}")

✅ Đúng: Xử lý timezone đúng

Bybit timestamps là UTC, cần convert chính xác

from datetime import timezone start_date = datetime(2025, 4, 1, 0, 0, 0, tzinfo=timezone.utc) end_date = datetime(2025, 4, 2, 0, 0, 0, tzinfo=timezone.utc) start_ts = int(start_date.timestamp() * 1000) end_ts = int(end_date.timestamp() * 1000)

3. Lỗi Memory Khi Download Large Dataset

# ❌ Sai: Download tất cả data vào memory
trades = []
async for capsule in client.get_capsules(...):
    trades.extend(capsule.trades)  # Memory overflow với dataset lớn

✅ Đúng: Stream và write trực tiếp ra file

import pyarrow.parquet as pq from pathlib import Path class StreamingParquetWriter: def __init__(self, filepath, batch_size=10000): self.filepath = filepath self.batch_size = batch_size self.buffer = [] self.writer = None async def write_trade(self, trade): self.buffer.append({ "timestamp": trade.timestamp, "price": float(trade.price), "size": float(trade.size), "side": trade.side }) if len(self.buffer) >= self.batch_size: await self._flush() async def _flush(self): if self.writer is None: # Create schema table = pa.Table.from_pylist(self.buffer) self.writer = pq.ParquetWriter(self.filepath, table.schema) table = self.writer.write_table(table) else: table = pa.Table.from_pylist(self.buffer) self.writer.write_table(table) self.buffer = [] async def close(self): if self.buffer: await self._flush() if self.writer: self.writer.close()

Sử dụng

writer = StreamingParquetWriter("output.parquet") async for capsule in client.get_capsules(...): for trade in capsule.trades: await writer.write_trade(trade) await writer.close()

Bonus: Tối Ưu Performance Cho High-Frequency Backtesting

# Tips để tăng tốc backtesting với Tardis data

1. Sử dụng Parquet thay vì CSV

Đọc 10 triệu rows: CSV ~45s, Parquet ~3s

2. Index theo timestamp

import pyarrow.compute as pc def optimize_parquet_for_backtest(input_file, output_file): table = pq.read_table(input_file) # Sort by timestamp indices = pc.sort_indices(table["timestamp"]) sorted_table = table.take(indices) # Write with compression pq.write_table(sorted_table, output_file, compression="zstd") print(f"Optimized: {len(sorted_table)} rows")

3. Chunk processing

def process_in_chunks(filepath, chunk_size=500000): table = pq.read_table(filepath) n_rows = len(table) for start in range(0, n_rows, chunk_size): end = min(start + chunk_size, n_rows) chunk = table[start:end].to_pandas() # Process chunk yield chunk

4. Sử dụng numba cho numerical computation

from numba import jit @jit(nopython=True) def calculate_vwap(prices, volumes): return np.sum(prices * volumes) / np.sum(volumes)

Kết Luận

Việc download và xử lý Bybit trades và orderbook data cho quantitative high-frequency backtesting không còn là thách thức lớn nếu bạn có đúng công cụ. Tardis.dev cung cấp giải pháp end-to-end từ API đơn giản đến historical replay, giúp bạn tập trung vào phát triển chiến lược thay vì infrastructure data.

Nếu bạn đang xây dựng hệ thống AI-powered trading trên nền tảng dữ liệu này, đừng quên kết hợp với HolySheep AI để tận dụng chi phí API chỉ từ $0.42/MTok (DeepSeek V3.2) — tiết kiệm đến 85% so với OpenAI. Với latency dưới 50ms và hỗ trợ WeChat/Alipay, HolySheep là lựa chọn tối ưu cho developers tại thị trường châu Á.

Bài viết tiếp theo trong series này, tôi sẽ hướng dẫn cách xây dựng market making strategy hoàn chỉnh sử dụng dữ liệu đã download được. Stay tuned!

👉 Đăng ký HolySheep AI — nhận tín dụng miễn phí khi đăng ký