Ngày đăng: 28/04/2026 | Thể loại: Data Infrastructure | Đọc: 12 phút
Mở đầu: Khi API trả về "401 Unauthorized" vào lúc 3 giờ sáng
Tôi vẫn nhớ rất rõ cái đêm tháng 11 năm 2024. Hệ thống backtest của tôi đang chạy ngon lành thì bỗng dưng crash với lỗi:
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='api.tardis.dev', port=443):
Max retries exceeded with url: /v1/fees (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object...))
ConnectionError: timeout after 30s
Rồi sau đó là một loạt lỗi 401 khi thử lại:
HTTPError: 401 Client Error: Unauthorized for url: https://api.tardis.dev/v1/fees
{"error": "Invalid API key", "code": "AUTH_001"}
Sau 4 tiếng debug, tôi phát hiện mình đã để API key trong code nhưng lại dùng sai biến môi trường. Bài viết này sẽ giúp bạn tránh những cái bẫy mà tôi đã mắc phải, đồng thời hướng dẫn chi tiết cách tải và xử lý dữ liệu options từ Deribit.
Giới thiệu: Tại sao dữ liệu Deribit Options quan trọng?
Deribit là sàn giao dịch options BTC và ETH lớn nhất thế giới, chiếm hơn 85% thị phần options crypto. Dữ liệu lịch sử từ Deribit bao gồm:
- OHLCV candle data - Dữ liệu giá theo thời gian
- Orderbook snapshots - Ảnh chụp sổ lệnh
- Trades & funding - Giao dịch và funding rate
- Greeks data - Delta, Gamma, Vega, Theta, Rho
- Volatility surface - Bề mặt biến động theo strike và expiry
Với những trader thực hiện chiến lược options hoặc quản lý rủi ro, đây là nguồn dữ liệu không thể thiếu.
Tardis.dev: Giải pháp truy cập dữ liệu Deribit
Tardis.dev là gì?
Tardis.dev là dịch vụ cung cấp API truy cập dữ liệu lịch sử từ nhiều sàn crypto, bao gồm Deribit. Thay vì phải tự lưu trữ và xử lý terabytes dữ liệu, bạn chỉ cần gọi API.
Cài đặt và cấu hình
# Cài đặt thư viện cần thiết
pip install requests tardis-client pandas pyarrow aiohttp asyncio
# Cấu hình biến môi trường
export TARDIS_API_KEY="your_tardis_api_key_here"
export TARDIS_API_KEY="ts_live_xxxxxx_your_key_here"
Kết nối API và tải dữ liệu BTC Options
Phương pháp 1: Sử dụng Python với thư viện tardis-client
import os
from tardis_client import TardisClient, channels
from datetime import datetime, timedelta
import pandas as pd
import asyncio
Lấy API key từ biến môi trường
TARDIS_API_KEY = os.environ.get("TARDIS_API_KEY")
if not TARDIS_API_KEY:
raise ValueError("Vui lòng đặt TARDIS_API_KEY trong biến môi trường")
Khởi tạo client
tardis_client = TardisClient(TARDIS_API_KEY)
async def download_btc_options_data():
"""
Tải dữ liệu options BTC từ Deribit
Bao gồm: trades, orderbook, Greeks
"""
# Xác định khoảng thời gian - 7 ngày gần nhất
to_date = datetime.utcnow()
from_date = to_date - timedelta(days=7)
# Đăng ký các channel cần thiết
deribit_channels = [
channels.Deribit(options={"name": "deribit",
"book": 10, # Độ sâu 10 levels
"trade": True,
"ticker": True}),
channels.Deribit(options={"name": "deribit",
"deribit_option_price": True, # Greeks data
"deribit_volatility_index": True}),
]
# Lưu trữ dữ liệu
trades_data = []
orderbook_data = []
greeks_data = []
async for messages in tardis_client._download(
exchange="deribit",
from_date=from_date,
to_date=to_date,
channels=deribit_channels,
is_async=True
):
for message in messages:
# Phân loại dữ liệu theo type
if message["type"] == "trade":
trades_data.append({
"timestamp": message["timestamp"],
"symbol": message.get("symbol"),
"price": message.get("price"),
"amount": message.get("amount"),
"side": message.get("side"),
"trade_id": message.get("trade_id")
})
elif message["type"] == "book":
orderbook_data.append({
"timestamp": message["timestamp"],
"symbol": message.get("symbol"),
"bids": message.get("bids"),
"asks": message.get("asks")
})
elif message.get("data_type") == "greeks":
greeks_data.append({
"timestamp": message["timestamp"],
"symbol": message.get("symbol"),
"delta": message.get("delta"),
"gamma": message.get("gamma"),
"vega": message.get("vega"),
"theta": message.get("theta"),
"rho": message.get("rho"),
"iv": message.get("iv"), # Implied volatility
"mark_price": message.get("mark_price"),
"underlying_price": message.get("underlying_price")
})
return trades_data, orderbook_data, greeks_data
Chạy và lưu kết quả
trades, orderbooks, greeks = asyncio.run(download_btc_options_data())
Chuyển sang DataFrame
df_trades = pd.DataFrame(trades)
df_greeks = pd.DataFrame(greeks)
print(f"Đã tải {len(df_trades)} records trades")
print(f"Đã tải {len(df_greeks)} records Greeks")
print(df_greeks.head())
Phương pháp 2: REST API trực tiếp với requests
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
class DeribitDataDownloader:
"""
Class tải dữ liệu Deribit Options qua Tardis REST API
"""
BASE_URL = "https://api.tardis.dev/v1"
def __init__(self, api_key: str):
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
})
def get_available_symbols(self, exchange: str = "deribit"):
"""Lấy danh sách symbols available"""
response = self.session.get(
f"{self.BASE_URL}/exchanges/{exchange}/symbols"
)
response.raise_for_status()
return response.json()
def download_candles(
self,
exchange: str,
symbol: str,
from_timestamp: int,
to_timestamp: int,
interval: str = "1h"
):
"""
Tải dữ liệu candle OHLCV
Args:
exchange: Tên sàn (deribit)
symbol: Mã instrument (BTC-28MAR25-95000-C)
from_timestamp: Unix timestamp bắt đầu
to_timestamp: Unix timestamp kết thúc
interval: Độ phân giải (1m, 5m, 1h, 1d)
"""
params = {
"exchange": exchange,
"symbol": symbol,
"from": from_timestamp,
"to": to_timestamp,
"interval": interval
}
all_data = []
page = 1
while True:
params["page"] = page
response = self.session.get(
f"{self.BASE_URL}/historical/candles",
params=params
)
if response.status_code == 429: # Rate limit
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limit - chờ {retry_after} giây...")
time.sleep(retry_after)
continue
response.raise_for_status()
data = response.json()
if not data.get("data"):
break
all_data.extend(data["data"])
if not data.get("has_more"):
break
page += 1
time.sleep(0.5) # Tránh quá tải API
return pd.DataFrame(all_data)
def download_trades(
self,
exchange: str,
symbol: str,
from_timestamp: int,
to_timestamp: int
):
"""Tải dữ liệu trades"""
params = {
"exchange": exchange,
"symbol": symbol,
"from": from_timestamp,
"to": to_timestamp
}
all_trades = []
cursor = None
while True:
if cursor:
params["cursor"] = cursor
response = self.session.get(
f"{self.BASE_URL}/historical/trades",
params=params
)
response.raise_for_status()
data = response.json()
all_trades.extend(data.get("data", []))
cursor = data.get("cursor")
if not cursor:
break
time.sleep(0.3)
return pd.DataFrame(all_trades)
Sử dụng class
downloader = DeribitDataDownloader(api_key="ts_live_xxxxx_your_key")
Lấy danh sách symbols
symbols = downloader.get_available_symbols("deribit")
print(f"Tổng số symbols: {len(symbols)}")
Lọc symbols options BTC
btc_options = [s for s in symbols if "BTC" in s.get("symbol", "")]
print(f"BTC options symbols: {len(btc_options)}")
Tải dữ liệu candle cho 1 option cụ thể
to_ts = int(datetime.utcnow().timestamp() * 1000)
from_ts = int((datetime.utcnow() - timedelta(days=7)).timestamp() * 1000)
df_candles = downloader.download_candles(
exchange="deribit",
symbol="BTC-28MAR25-95000-C",
from_timestamp=from_ts,
to_timestamp=to_ts,
interval="1h"
)
print(f"Candles: {len(df_candles)} records")
print(df_candles.head())
Xử lý Greeks Data: Phân tích biến động
Dữ liệu Greeks từ Deribit bao gồm các chỉ số quan trọng cho pricing và risk management:
- Delta (Δ) - Thay đổi giá option theo thay đổi giá underlying
- Gamma (Γ) - Tốc độ thay đổi của Delta
- Vega (ν) - Nhạy cảm với biến động implied volatility
- Theta (Θ) - Giá trị thời gian bị mất đi mỗi ngày
- Rho (ρ) - Nhạy cảm với lãi suất
import pandas as pd
import numpy as np
def process_greeks_data(greeks_df: pd.DataFrame) -> pd.DataFrame:
"""
Xử lý và phân tích dữ liệu Greeks
"""
# Chuyển đổi timestamp
greeks_df["datetime"] = pd.to_datetime(
greeks_df["timestamp"], unit="ms"
)
# Tính toán các chỉ số tổng hợp
greeks_df["total_delta_exposure"] = (
greeks_df["delta"] * greeks_df["mark_price"] * 100 # BTC contract size
)
# Gamma exposure (dollar gamma)
greeks_df["gamma_exposure"] = (
greeks_df["gamma"] * greeks_df["mark_price"] * greeks_df["underlying_price"] * 100
)
# Vega exposure
greeks_df["vega_exposure"] = (
greeks_df["vega"] * greeks_df["mark_price"] * 100
)
# Phân loại options theo Delta
def classify_moneyness(delta):
if pd.isna(delta):
return "unknown"
elif delta >= 0.75:
return "ITM_call"
elif delta <= -0.75:
return "ITM_put"
elif 0.25 <= delta < 0.75:
return "ATM_range"
elif -0.25 < delta < 0.25:
return "ATM"
elif -0.75 < delta <= -0.25:
return "OTM_call"
else:
return "OTM_put"
greeks_df["moneyness"] = greeks_df["delta"].apply(classify_moneyness)
# Tính volatility skew
# Group by expiry và tính IV spread giữa puts và calls
greeks_df["iv_rank"] = greeks_df.groupby("symbol")["iv"].transform(
lambda x: (x - x.min()) / (x.max() - x.min()) if x.max() != x.min() else 0
)
# Tính rolling statistics
for col in ["delta", "gamma", "vega", "theta", "iv"]:
if col in greeks_df.columns:
greeks_df[f"{col}_ma_24h"] = greeks_df.groupby("symbol")[col].transform(
lambda x: x.rolling(window=24, min_periods=1).mean()
)
greeks_df[f"{col}_vol_24h"] = greeks_df.groupby("symbol")[col].transform(
lambda x: x.rolling(window=24, min_periods=1).std()
)
return greeks_df
def calculate_portfolio_greeks(greeks_df: pd.DataFrame, position_size: dict) -> dict:
"""
Tính Greeks cho toàn bộ portfolio
Args:
greeks_df: DataFrame chứa Greeks data
position_size: Dict mapping symbol -> số lượng contract
"""
portfolio_greeks = {
"total_delta": 0,
"total_gamma": 0,
"total_vega": 0,
"total_theta": 0,
"net_dollar_gamma": 0
}
for symbol, size in position_size.items():
symbol_data = greeks_df[greeks_df["symbol"] == symbol]
if len(symbol_data) > 0:
latest = symbol_data.iloc[-1]
portfolio_greeks["total_delta"] += latest["delta"] * size
portfolio_greeks["total_gamma"] += latest["gamma"] * size
portfolio_greeks["total_vega"] += latest["vega"] * size
portfolio_greeks["total_theta"] += latest["theta"] * size
# Dollar gamma
dollar_gamma = (
latest["gamma"] *
latest["underlying_price"] ** 2 *
0.01 * # 1% move
size
)
portfolio_greeks["net_dollar_gamma"] += dollar_gamma
return portfolio_greeks
Áp dụng xử lý
df_processed = process_greeks_data(df_greeks)
print("Greeks đã xử lý:")
print(df_processed[["datetime", "symbol", "delta", "gamma", "iv", "moneyness"]].head(10))
Tính portfolio Greeks
sample_positions = {
"BTC-28MAR25-95000-C": 10,
"BTC-28MAR25-90000-P": -5,
"BTC-28MAR25-100000-C": 8
}
portfolio = calculate_portfolio_greeks(df_processed, sample_positions)
print("\nPortfolio Greeks:")
for key, value in portfolio.items():
print(f" {key}: {value:.4f}")
Lỗi thường gặp và cách khắc phục
1. Lỗi 401 Unauthorized - API Key không hợp lệ
# ❌ SAI: API key bị sai hoặc hết hạn
HTTPError: 401 Client Error: Unauthorized
✅ ĐÚNG: Kiểm tra và cấu hình API key đúng cách
import os
Cách 1: Đặt trong biến môi trường
export TARDIS_API_KEY="ts_live_your_key_here"
hoặc trong Windows: set TARDIS_API_KEY=ts_live_your_key
api_key = os.environ.get("TARDIS_API_KEY")
if not api_key:
raise ValueError("TARDIS_API_KEY not found in environment variables")
Cách 2: Kiểm tra format API key
Tardis key format: ts_live_xxxxx_xxxxx
Hoặc: ts_demo_xxxxx cho tài khoản trial
def validate_api_key(key: str) -> bool:
if not key:
return False
if not key.startswith(("ts_live_", "ts_demo_", "ts_test_")):
return False
if len(key) < 20:
return False
return True
Test kết nối
def test_connection(api_key: str) -> dict:
import requests
response = requests.get(
"https://api.tardis.dev/v1/fees",
headers={"Authorization": f"Bearer {api_key}"}
)
if response.status_code == 401:
raise Exception("API key không hợp lệ. Vui lòng kiểm tra lại.")
response.raise_for_status()
return response.json()
try:
result = test_connection(api_key)
print("Kết nối thành công!")
except Exception as e:
print(f"Lỗi: {e}")
2. Lỗi 429 Rate Limit - Quá nhiều request
# ❌ SAI: Gửi request liên tục không có delay
HTTPError: 429 Client Error: Too Many Requests
✅ ĐÚNG: Implement exponential backoff và rate limiting
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class RateLimitedClient:
"""
HTTP Client với automatic rate limiting
"""
def __init__(self, api_key: str, max_retries: int = 3):
self.api_key = api_key
self.session = requests.Session()
# Cấu hình retry strategy
retry_strategy = Retry(
total=max_retries,
backoff_factor=1, # Exponential backoff: 1s, 2s, 4s...
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"User-Agent": "MyTradingBot/1.0"
})
# Rate limiting state
self.last_request_time = 0
self.min_request_interval = 0.5 # Tối thiểu 500ms giữa các request
def get(self, url: str, **kwargs) -> requests.Response:
"""GET với automatic rate limiting"""
# Enforce rate limit
elapsed = time.time() - self.last_request_time
if elapsed < self.min_request_interval:
time.sleep(self.min_request_interval - elapsed)
response = self.session.get(url, **kwargs)
# Xử lý rate limit response
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limit hit. Chờ {retry_after} giây...")
time.sleep(retry_after)
return self.get(url, **kwargs) # Retry
self.last_request_time = time.time()
return response
def paginated_get(self, url: str, params: dict = None) -> list:
"""GET với automatic pagination"""
all_data = []
page = 1
while True:
current_params = {**(params or {}), "page": page}
response = self.get(url, params=current_params)
response.raise_for_status()
data = response.json()
if not data.get("data"):
break
all_data.extend(data["data"])
if not data.get("has_more"):
break
page += 1
return all_data
Sử dụng RateLimitedClient
client = RateLimitedClient(api_key="your_key_here")
Tải dữ liệu với rate limiting tự động
data = client.paginated_get(
"https://api.tardis.dev/v1/historical/candles",
params={
"exchange": "deribit",
"symbol": "BTC-28MAR25-95000-C",
"interval": "1h"
}
)
print(f"Đã tải {len(data)} records")
3. Lỗi Timeout và Connection Error
# ❌ SAI: Không có timeout hoặc timeout quá ngắn
requests.exceptions.ConnectTimeoutError
✅ ĐÚNG: Cấu hình timeout hợp lý và retry logic
import requests
import asyncio
import aiohttp
from tenacity import retry, stop_after_attempt, wait_exponential
Cách 1: Sử dụng requests với timeout
session = requests.Session()
session.timeout = requests.sessions.DEFAULT_TIMEOUTS.extend(connect=30, read=60)
def download_with_timeout(url: str, timeout: tuple = (30, 120)) -> dict:
"""
Download với timeout linh hoạt
Args:
timeout: (connect_timeout, read_timeout) tính bằng giây
"""
response = requests.get(
url,
headers={"Authorization": f"Bearer {TARDIS_API_KEY}"},
timeout=timeout,
stream=True # Stream large responses
)
response.raise_for_status()
return response.json()
Cách 2: Async download với aiohttp (cho dữ liệu lớn)
async def async_download(urls: list) -> list:
"""
Download nhiều URLs đồng thời với aiohttp
"""
timeout = aiohttp.ClientTimeout(total=300, connect=60)
async def fetch(session, url):
async with session.get(url) as response:
response.raise_for_status()
return await response.json()
connector = aiohttp.TCPConnector(
limit=10, # Giới hạn 10 connections đồng thời
limit_per_host=5
)
async with aiohttp.ClientSession(
timeout=timeout,
connector=connector
) as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Xử lý errors
valid_results = []
for i, result in enumerate(results):
if isinstance(result, Exception):
print(f"Lỗi URL {urls[i]}: {result}")
else:
valid_results.append(result)
return valid_results
Cách 3: Retry decorator cho các API calls quan trọng
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
@retry(
stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=2, min=4, max=60),
retry=retry_if_exception_type((requests.exceptions.Timeout,
requests.exceptions.ConnectionError)),
before_sleep=lambda retry_state: print(f"Retry lần {retry_state.attempt_number}...")
)
def robust_download(url: str) -> dict:
"""Download với automatic retry"""
response = requests.get(
url,
headers={"Authorization": f"Bearer {TARDIS_API_KEY}"},
timeout=(30, 120)
)
response.raise_for_status()
return response.json()
Test robust download
try:
data = robust_download("https://api.tardis.dev/v1/fees")
print("Download thành công!")
except Exception as e:
print(f"Không thể download sau nhiều lần thử: {e}")
4. Lỗi xử lý dữ liệu NULL và missing values
# ❌ SAI: Không kiểm tra NULL values
KeyError hoặc TypeError khi truy cập missing fields
✅ ĐÚNG: Handle NULL values an toàn
import pandas as pd
import numpy as np
def safe_greeks_processing(greeks_df: pd.DataFrame) -> pd.DataFrame:
"""
Xử lý dữ liệu Greeks với NULL safety
"""
# Kiểm tra các cột bắt buộc
required_columns = ["timestamp", "symbol", "delta", "gamma", "vega", "theta"]
missing_cols = [col for col in required_columns if col not in greeks_df.columns]
if missing_cols:
raise ValueError(f"Thiếu cột: {missing_cols}")
# Tạo bản sao để không ảnh hưởng dữ liệu gốc
df = greeks_df.copy()
# Fill missing values với chiến lược phù hợp
# Delta: Fill với giá trị trung vị theo symbol
df["delta"] = df.groupby("symbol")["delta"].transform(
lambda x: x.fillna(x.median())
)
# Gamma: Fill với 0 (nếu missing có thể là stable position)
df["gamma"] = df["gamma"].fillna(0)
# Vega: Fill với forward fill (giữ nguyên giá trị trước đó)
df["vega"] = df.groupby("symbol")["vega"].ffill()
df["vega"] = df.groupby("symbol")["vega"].bfill()
# Theta: Fill với 0
df["theta"] = df["theta"].fillna(0)
# IV: Fill với giá trị trung bình
df["iv"] = df.groupby("symbol")["iv"].transform(
lambda x: x.fillna(x.mean())
)
# Xử lý infinite values
df.replace([np.inf, -np.inf], np.nan, inplace=True)
df.fillna(method="ffill", inplace=True)
# Validate dữ liệu
assert not df["delta"].isna().any(), "Still have NULL delta values"
assert not (df["delta"].abs() > 1).any(), "Delta values out of range"
return df
Áp dụng xử lý
df_clean = safe_greeks_processing(df_greeks)
print(f"Trước xử lý: {df_greeks.isna().sum().sum()} NULL values")
print(f"Sau xử lý: {df_clean.isna().sum().sum()} NULL values")
So sánh các giải pháp truy cập dữ liệu Deribit
| Tiêu chí | Tardis.dev | Deribit WebSocket (trực tiếp) | HolySheep AI (xử lý) |
|---|---|---|---|
| Phí hàng tháng | $49 - $499/tháng | Miễn phí (data stream) | $0.42 - $15/MTok |
| Dữ liệu lịch sử | Có (từ 2020) | Không | Qua API integration |
| Độ trễ | ~100-500ms | Real-time (<50ms) | <50ms |
| Greeks data | Có đầy đủ | Có | Có (qua Tardis) |
| Khối lượng miễn phí | 10,000 API calls/tháng | Unlimited | $5 tín dụng miễn phí |
| Learning curve | Trung bình | Cao | Thấp |
| Hỗ trợ Python | tardis-client SDK | ws library | Native Python API |
Phù hợp / không phù hợp với ai
✅ NÊN sử dụng Tardis.dev khi:
- Bạn cần dữ liệu lịch sử để backtest chiến lược options
- Cần truy cập Greeks data và volatility surface
- Xây dựng hệ thống risk management với dữ liệu OHLCV
- Phát triển trading bot cần cả real-time và historical data
- Nghiên cứu academic về derivatives pricing
❌ KHÔNG nên sử dụng Tardis.dev khi:
- Chỉ cần real-time data -