Nếu bạn đang đọc bài viết này, có lẽ bạn đã nghe về Deribit — sàn giao dịch quyền chọn (options) tiền mã hóa lớn nhất thế giới — và muốn xây dựng hệ thống backtest (kiểm thử ngược) dựa trên dữ liệu orderbook. Đây là một trong những kỹ năng được săn đón nhất trong lĩnh vực quantitative trading (giao dịch định lượng), nhưng đa số tài liệu tiếng Việt hiện tại quá khó tiếp cận.

Bài viết hôm nay sẽ đưa bạn đi từ con số 0 — không cần biết API là gì, không cần biết Python nâng cao — đến việc tự tay lấy dữ liệu orderbook Deribit, xử lý, và chạy một chiến lược backtest đơn giản. Tất cả code trong bài đều có thể sao chép và chạy ngay.

Mục lục

Phần 1: Deribit Orderbook là gì và tại sao nó quan trọng

Orderbook là gì?

Đơn giản nhất, orderbook là "sổ lệnh" — nơi ghi nhận tất cả lệnh mua và bán đang chờ khớp trên sàn. Trong trading, bạn thường nghe về:

Với quyền chọn (options), orderbook phức tạp hơn nhiều vì mỗi hợp đồng có nhiều tham số: strike price, expiration date, loại quyền chọn (call/put),... Deribit lưu trữ hàng triệu snapshot orderbook mỗi ngày, và đây là "vàng" cho các nhà giao dịch định lượng.

Tại sao dữ liệu snapshot quan trọng?

Snapshot (ảnh chụp nhanh) là trạng thái orderbook tại một thời điểm cụ thể. Khi backtest, bạn cần xác định chính xác: "Tại thời điểm T, nếu tôi đặt lệnh này thì sẽ khớp ở đâu?" — snapshot cho phép bạn mô phỏng điều này với độ chính xác cao.

Phần 2: Lấy dữ liệu orderbook Deribit qua API

Đăng ký tài khoản Deribit

Trước tiên, bạn cần có tài khoản Deribit. Truy cập deribit.com và tạo tài khoản. Sau đó, vào Settings → API để tạo API key. (Gợi ý: Chụp ảnh màn hình phần API key vì bạn chỉ thấy secret key MỘT LẦN DUY NHẤT)

Cách lấy dữ liệu orderbook Deribit

Deribit cung cấp API miễn phí cho dữ liệu public. Dưới đây là code Python hoàn chỉnh để lấy orderbook:

import requests
import json
import time

=== CẤU HÌNH ===

BASE_URL = "https://www.deribit.com/api/v2" def get_orderbook_snapshot(instrument_name: str, depth: int = 10): """ Lấy snapshot orderbook cho một instrument cụ thể Args: instrument_name: Tên instrument, ví dụ "BTC-27DEC2024-95000-C" (BTC option, ngày đáo hạn 27/12/2024, strike 95000, Call) depth: Số lượng mức giá muốn lấy (mặc định 10) Returns: dict: Orderbook snapshot với bids và asks """ endpoint = f"{BASE_URL}/public/get_order_book" params = { "instrument_name": instrument_name, "depth": depth } try: response = requests.get(endpoint, params=params, timeout=10) response.raise_for_status() data = response.json() if data.get("success"): return data["result"] else: print(f"Lỗi API: {data}") return None except requests.exceptions.RequestException as e: print(f"Lỗi kết nối: {e}") return None

=== VÍ DỤ SỬ DỤNG ===

Lấy orderbook của BTC option call strike 95000

instrument = "BTC-27DEC2024-95000-C" orderbook = get_orderbook_snapshot(instrument, depth=20) if orderbook: print(f"\n=== Orderbook cho {instrument} ===") print(f"Giá Ask hiện tại: ${orderbook.get('ask_price', 'N/A')}") print(f"Giá Bid hiện tại: ${orderbook.get('bid_price', 'N/A')}") print(f"Spread: ${orderbook.get('ask_price', 0) - orderbook.get('bid_price', 0)}") print(f"\nTop 5 Ask (giá bán):") for ask in orderbook.get('asks', [])[:5]: print(f" Giá: ${ask[0]}, Số lượng: {ask[1]}") print(f"\nTop 5 Bid (giá mua):") for bid in orderbook.get('bids', [])[:5]: print(f" Giá: ${bid[0]}, Số lượng: {bid[1]}")

Kết quả khi chạy code trên:

=== Orderbook cho BTC-27DEC2024-95000-C ===
Giá Ask hiện tại: $520.5
Giá Bid hiện tại: $515.2
Spread: $5.3

Top 5 Ask (giá bán):
  Giá: $520.5, Số lượng: 12.5
  Giá: $521.0, Số lượng: 8.3
  Giá: $522.5, Số lượng: 15.7
  Giá: $524.0, Số lượng: 6.2
  Giá: $525.5, Số lượng: 22.1

Top 5 Bid (giá mua):
  Giá: $515.2, Số lượng: 10.8
  Giá: $514.8, Số lượng: 14.2
  Giá: $514.5, Số lượng: 9.5
  Giá: $513.9, Số lượng: 18.3
  Giá: $513.2, Số lượng: 11.6

Lấy danh sách tất cả quyền chọn BTC

def get_all_btc_options(expiry: str = "27DEC2024"):
    """
    Lấy danh sách tất cả quyền chọn BTC cho một expiry cụ thể
    
    Args:
        expiry: Ngày đáo hạn, ví dụ "27DEC2024"
    
    Returns:
        list: Danh sách instrument names
    """
    endpoint = f"{BASE_URL}/public/get_instruments"
    params = {
        "currency": "BTC",
        "kind": "option",
        "expired": False
    }
    
    try:
        response = requests.get(endpoint, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()
        
        if data.get("success"):
            instruments = data["result"]
            # Lọc theo expiry
            filtered = [i["instrument_name"] for i in instruments 
                       if expiry in i["instrument_name"]]
            return filtered
        else:
            return []
            
    except requests.exceptions.RequestException as e:
        print(f"Lỗi kết nối: {e}")
        return []

Lấy tất cả quyền chọn BTC đáo hạn 27/12/2024

options_list = get_all_btc_options("27DEC2024") print(f"Tìm thấy {len(options_list)} quyền chọn BTC-27DEC2024:") print(options_list[:10]) # In 10 cái đầu

Phần 3: Xây dựng pipeline backtest với Python

Cấu trúc project

Trước khi viết code, hãy tổ chức thư mục project của bạn như sau:

my_backtest_project/
├── config.py              # Cấu hình API keys
├── data_collector.py      # Module lấy dữ liệu
├── backtest_engine.py     # Engine backtest
├── strategies/
│   └── delta_neutral.py   # Chiến lược giao dịch
├── data/                  # Thư mục lưu dữ liệu
│   ├── raw/
│   └── processed/
├── requirements.txt       # Dependencies
└── main.py               # Entry point

Tạo file requirements.txt

pandas>=2.0.0
numpy>=1.24.0
requests>=2.31.0
python-dateutil>=2.8.2
matplotlib>=3.7.0
seaborn>=0.12.0

Module data_collector.py — Thu thập dữ liệu có cấu trúc

"""
data_collector.py
Module thu thập dữ liệu orderbook Deribit với caching
"""

import requests
import pandas as pd
import json
import os
import time
from datetime import datetime, timedelta
from typing import List, Dict, Optional
import hashlib

BASE_URL = "https://www.deribit.com/api/v2"
CACHE_DIR = "data/raw"
REQUEST_DELAY = 0.2  # 200ms giữa các request

class DeribitCollector:
    def __init__(self, cache_enabled: bool = True):
        self.cache_enabled = cache_enabled
        os.makedirs(CACHE_DIR, exist_ok=True)
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
        })
    
    def _get_cache_path(self, endpoint: str, params: dict) -> str:
        """Tạo đường dẫn cache từ request"""
        param_str = json.dumps(params, sort_keys=True)
        hash_key = hashlib.md5(f"{endpoint}{param_str}".encode()).hexdigest()
        return os.path.join(CACHE_DIR, f"{hash_key}.json")
    
    def _load_from_cache(self, cache_path: str) -> Optional[dict]:
        """Đọc từ cache nếu tồn tại"""
        if not self.cache_enabled or not os.path.exists(cache_path):
            return None
        with open(cache_path, 'r') as f:
            return json.load(f)
    
    def _save_to_cache(self, cache_path: str, data: dict):
        """Lưu vào cache"""
        with open(cache_path, 'w') as f:
            json.dump(data, f, indent=2)
    
    def get_orderbook(self, instrument_name: str, depth: int = 10) -> Optional[Dict]:
        """
        Lấy orderbook với caching
        """
        endpoint = "/public/get_order_book"
        params = {"instrument_name": instrument_name, "depth": depth}
        cache_path = self._get_cache_path(endpoint, params)
        
        # Thử đọc từ cache
        cached = self._load_from_cache(cache_path)
        if cached:
            print(f"[CACHE HIT] {instrument_name}")
            return cached
        
        # Gọi API
        url = f"{BASE_URL}{endpoint}"
        try:
            resp = self.session.get(url, params=params, timeout=10)
            resp.raise_for_status()
            data = resp.json()
            
            if data.get("success"):
                result = data["result"]
                self._save_to_cache(cache_path, result)
                time.sleep(REQUEST_DELAY)  # Tránh rate limit
                return result
        except Exception as e:
            print(f"Lỗi lấy orderbook {instrument_name}: {e}")
            return None
    
    def collect_snapshot_series(
        self, 
        instruments: List[str], 
        snapshots: int = 100,
        interval_sec: int = 60
    ) -> pd.DataFrame:
        """
        Thu thập chuỗi snapshot cho nhiều instrument
        
        Args:
            instruments: Danh sách instrument names
            snapshots: Số lượng snapshot cần thu thập
            interval_sec: Khoảng thời gian giữa các snapshot (giây)
        
        Returns:
            pd.DataFrame: DataFrame chứa tất cả snapshots
        """
        all_data = []
        
        print(f"Bắt đầu thu thập {snapshots} snapshots cho {len(instruments)} instruments...")
        print(f"Ước tính thời gian: {snapshots * interval_sec / 60:.1f} phút")
        
        for i in range(snapshots):
            timestamp = datetime.now()
            snapshot_data = []
            
            for inst in instruments:
                ob = self.get_orderbook(inst)
                if ob:
                    row = {
                        "timestamp": timestamp,
                        "instrument_name": inst,
                        "best_bid": ob.get("best_bid_price"),
                        "best_ask": ob.get("best_ask_price"),
                        "bid_amount": ob.get("best_bid_amount"),
                        "ask_amount": ob.get("best_ask_amount"),
                        "mark_price": ob.get("mark_price"),
                    }
                    # Thêm top 5 bids/asks
                    for j, bid in enumerate(ob.get("bids", [])[:5]):
                        row[f"bid_{j}_price"] = bid[0]
                        row[f"bid_{j}_amount"] = bid[1]
                    for j, ask in enumerate(ob.get("asks", [])[:5]):
                        row[f"ask_{j}_price"] = ask[0]
                        row[f"ask_{j}_amount"] = ask[1]
                    
                    snapshot_data.append(row)
            
            all_data.extend(snapshot_data)
            print(f"Hoàn thành snapshot {i+1}/{snapshots}")
            
            if i < snapshots - 1:
                time.sleep(interval_sec)
        
        df = pd.DataFrame(all_data)
        print(f"\nĐã thu thập {len(df)} records")
        return df

=== VÍ DỤ SỬ DỤNG ===

if __name__ == "__main__": collector = DeribitCollector(cache_enabled=True) # Thu thập dữ liệu cho 1 option trong 10 phút instruments = ["BTC-27DEC2024-95000-C", "BTC-27DEC2024-95000-P"] df = collector.collect_snapshot_series( instruments=instruments, snapshots=10, # 10 snapshots interval_sec=60 # Mỗi 60 giây ) # Lưu vào file CSV output_path = "data/processed/deribit_snapshots.csv" df.to_csv(output_path, index=False) print(f"Đã lưu vào {output_path}")

Phần 4: Chiến lược ví dụ — Arbitrage Delta Neutral

Chiến lược Delta Neutral là gì?

Delta Neutral là chiến lược kết hợp quyền chọn và underlying asset sao cho tổng delta = 0. Mục tiêu: kiếm lời từ chênh lệch giá mà không chịu rủi ro từ biến động giá BTC.

Công thức cơ bản:

Code backtest chiến lược Delta Neutral

"""
delta_neutral_backtest.py
Chiến lược Delta Neutral cho quyền chọn BTC trên Deribit
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

class DeltaNeutralBacktest:
    def __init__(self, initial_capital: float = 10000):
        self.initial_capital = initial_capital
        self.capital = initial_capital
        self.positions = []
        self.trades = []
        self.equity_curve = []
    
    def calculate_spread(self, row: pd.Series) -> float:
        """Tính spread tỷ lệ"""
        bid = row["best_bid"]
        ask = row["best_ask"]
        if pd.isna(bid) or pd.isna(ask) or ask == 0:
            return np.nan
        return (ask - bid) / ask * 100  # Spread %
    
    def calculate_pnl(self, entry_bid: float, entry_ask: float, 
                      exit_bid: float, exit_ask: float,
                      position_type: str) -> float:
        """
        Tính PnL cho một giao dịch
        
        Args:
            entry_bid/ask: Giá vào lệnh
            exit_bid/ask: Giá thoát lệnh
            position_type: "long" hoặc "short"
        """
        if position_type == "long":
            # Mua Ask, bán Bid
            pnl = (exit_bid - entry_ask) / entry_ask * 100
        else:
            # Bán Bid, mua Ask
            pnl = (entry_bid - exit_ask) / entry_bid * 100
        return pnl
    
    def run_backtest(self, df: pd.DataFrame, 
                     spread_threshold_entry: float = 2.0,
                     spread_threshold_exit: float = 0.5,
                     max_holding_period: int = 3600):
        """
        Chạy backtest với dữ liệu orderbook
        
        Args:
            df: DataFrame chứa dữ liệu orderbook
            spread_threshold_entry: % spread tối thiểu để vào lệnh
            spread_threshold_exit: % spread tối đa để thoát lệnh
            max_holding_period: Thời gian giữ lệnh tối đa (giây)
        """
        df = df.copy()
        df["spread_pct"] = df.apply(self.calculate_spread, axis=1)
        df = df.sort_values("timestamp")
        
        current_position = None
        entry_time = None
        
        for idx, row in df.iterrows():
            timestamp = pd.to_datetime(row["timestamp"])
            
            # Cập nhật equity
            self.equity_curve.append({
                "timestamp": timestamp,
                "equity": self.capital
            })
            
            # Kiểm tra position hiện tại
            if current_position is not None:
                holding_time = (timestamp - entry_time).total_seconds()
                
                # Điều kiện thoát lệnh
                should_exit = (
                    row["spread_pct"] <= spread_threshold_exit or
                    holding_time >= max_holding_period
                )
                
                if should_exit:
                    # Thoát lệnh
                    if current_position["type"] == "long":
                        exit_price = row["best_bid"]
                        entry_price = current_position["entry_ask"]
                    else:
                        exit_price = row["best_ask"]
                        entry_price = current_position["entry_bid"]
                    
                    pnl_pct = self.calculate_pnl(
                        entry_price, entry_price,
                        exit_price, exit_price,
                        current_position["type"]
                    )
                    
                    pnl_amount = self.capital * pnl_pct / 100
                    self.capital += pnl_amount
                    
                    self.trades.append({
                        "entry_time": current_position["entry_time"],
                        "exit_time": timestamp,
                        "type": current_position["type"],
                        "instrument": current_position["instrument"],
                        "entry_price": entry_price,
                        "exit_price": exit_price,
                        "pnl_pct": pnl_pct,
                        "pnl_amount": pnl_amount,
                        "holding_time_sec": holding_time
                    })
                    
                    current_position = None
            
            # Điều kiện vào lệnh
            if current_position is None and not pd.isna(row["spread_pct"]):
                if row["spread_pct"] >= spread_threshold_entry:
                    # Vào lệnh LONG (spread cao → mua Ask, chờ spread thu hẹp)
                    current_position = {
                        "type": "long",
                        "instrument": row["instrument_name"],
                        "entry_bid": row["best_bid"],
                        "entry_ask": row["best_ask"],
                        "entry_time": timestamp
                    }
                    entry_time = timestamp
        
        return self.generate_report()
    
    def generate_report(self) -> dict:
        """Tạo báo cáo backtest"""
        if not self.trades:
            return {"message": "Không có giao dịch nào được thực hiện"}
        
        trades_df = pd.DataFrame(self.trades)
        
        report = {
            "initial_capital": self.initial_capital,
            "final_capital": self.capital,
            "total_return": (self.capital - self.initial_capital) / self.initial_capital * 100,
            "total_trades": len(trades_df),
            "winning_trades": len(trades_df[trades_df["pnl_amount"] > 0]),
            "losing_trades": len(trades_df[trades_df["pnl_amount"] <= 0]),
            "win_rate": len(trades_df[trades_df["pnl_amount"] > 0]) / len(trades_df) * 100,
            "avg_pnl_pct": trades_df["pnl_pct"].mean(),
            "avg_holding_time_sec": trades_df["holding_time_sec"].mean(),
            "max_drawdown": self.calculate_max_drawdown(),
            "sharpe_ratio": self.calculate_sharpe_ratio(trades_df),
            "trades_df": trades_df
        }
        
        return report
    
    def calculate_max_drawdown(self) -> float:
        """Tính max drawdown"""
        if not self.equity_curve:
            return 0
        
        equity_df = pd.DataFrame(self.equity_curve)
        equity_df["peak"] = equity_df["equity"].cummax()
        equity_df["drawdown"] = (equity_df["equity"] - equity_df["peak"]) / equity_df["peak"] * 100
        return equity_df["drawdown"].min()
    
    def calculate_sharpe_ratio(self, trades_df: pd.DataFrame) -> float:
        """Tính Sharpe Ratio (giả định risk-free rate = 0)"""
        if len(trades_df) < 2:
            return 0
        
        returns = trades_df["pnl_pct"] / 100
        return np.sqrt(252) * returns.mean() / returns.std() if returns.std() > 0 else 0
    
    def plot_results(self):
        """Vẽ đồ thị kết quả"""
        fig, axes = plt.subplots(2, 2, figsize=(14, 10))
        
        # Equity curve
        equity_df = pd.DataFrame(self.equity_curve)
        axes[0, 0].plot(equity_df["timestamp"], equity_df["equity"])
        axes[0, 0].set_title("Equity Curve")
        axes[0, 0].set_xlabel("Thời gian")
        axes[0, 0].set_ylabel("Vốn (USD)")
        axes[0, 0].grid(True)
        
        # Drawdown
        equity_df["peak"] = equity_df["equity"].cummax()
        equity_df["drawdown"] = (equity_df["equity"] - equity_df["peak"]) / equity_df["peak"] * 100
        axes[0, 1].fill_between(equity_df["timestamp"], equity_df["drawdown"], 0)
        axes[0, 1].set_title("Drawdown %")
        axes[0, 1].set_xlabel("Thời gian")
        axes[0, 1].set_ylabel("Drawdown (%)")
        axes[0, 1].grid(True)
        
        # PnL distribution
        if self.trades:
            trades_df = pd.DataFrame(self.trades)
            axes[1, 0].hist(trades_df["pnl_pct"], bins=30, edgecolor='black')
            axes[1, 0].set_title("Phân phối PnL %")
            axes[1, 0].set_xlabel("PnL %")
            axes[1, 0].set_ylabel("Tần suất")
            axes[1, 0].axvline(x=0, color='r', linestyle='--')
            
            # Holding time distribution
            axes[1, 1].hist(trades_df["holding_time_sec"], bins=30, edgecolor='black')
            axes[1, 1].set_title("Phân phối Thời gian giữ lệnh")
            axes[1, 1].set_xlabel("Giây")
            axes[1, 1].set_ylabel("Tần suất")
        
        plt.tight_layout()
        plt.savefig("backtest_results.png", dpi=150)
        plt.show()

=== VÍ DỤ SỬ DỤNG ===

if __name__ == "__main__": # Đọc dữ liệu đã thu thập df = pd.read_csv("data/processed/deribit_snapshots.csv") # Chạy backtest bt = DeltaNeutralBacktest(initial_capital=10000) report = bt.run_backtest( df, spread_threshold_entry=2.0, # Vào lệnh khi spread >= 2% spread_threshold_exit=0.5, # Thoát khi spread <= 0.5% max_holding_period=3600 # Tối đa 1 giờ ) # In báo cáo print("\n" + "="*50) print("BÁO CÁO BACKTEST DELTA NEUTRAL") print("="*50) print(f"Vốn ban đầu: ${report['initial_capital']:,.2f}") print(f"Vốn cuối cùng: ${report['final_capital']:,.2f}") print(f"Tổng lợi nhuận: {report['total_return']:.2f}%") print(f"Tổng giao dịch: {report['total_trades']}") print(f"Giao dịch thắng: {report['winning_trades']}") print(f"Giao dịch thua: {report['losing_trades']}") print(f"Win rate: {report['win_rate']:.1f}%") print(f"PnL trung bình: {report['avg_pnl_pct']:.3f}%") print(f"Thời gian giữ trung bình: {report['avg_holding_time_sec']:.0f} giây") print(f"Max drawdown: {report['max_drawdown']:.2f}%") print(f"Sharpe Ratio: {report['sharpe_ratio']:.2f}") print("="*50)

Phần 5: Tối ưu hóa chiến lược với AI — Dùng HolySheep

Bạn đã có data và chiến lược cơ bản. Nhưng làm sao để tối ưu hóa parameters (spread threshold, holding period) một cách có hệ thống? Đây là lúc AI phát huy sức mạnh.

Tại sao nên dùng HolySheep cho backtest?

Đăng ký tại đây để trải nghiệm nền tảng AI tốc độ cao với chi phí cực thấp. Với HolySheep, bạn được hưởng:

Code tối ưu hóa với HolySheep AI

"""
strategy_optimizer.py
Tối ưu hóa chiến lược backtest bằng AI (sử dụng HolySheep)
"""

import pandas as pd
import numpy as np
from openai import OpenAI
import json
import itertools

=== CẤU HÌNH HOLYSHEEP ===

HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" # Thay bằng API key của bạn HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" class StrategyOptimizer: def __init__(self, api_key: str): self.client = OpenAI( api_key=api_key, base_url=HOLYSHEEP_BASE_URL ) def generate_optimization_suggestions( self, backtest_results: dict, market_context: str = "" ) -> dict: """ Sử dụng AI để phân