ผมเป็น Quant Researcher ที่ทำงานด้าน crypto derivatives มากว่า 4 ปี เคยลองสร้าง IV surface ด้วยข้อมูลจากหลายแหล่ง — ตั้งแต่ Deribit public REST API, CoinGecko, ไปจนถึง paid feeds ราคาแพง สุดท้ายกลับมาจบที่ Tardis เพราะ tick-level data ครบถ้วนและ reproducible ที่สุด บทความนี้จะสรุป workflow ตั้งแต่ดึงข้อมูล Deribit options ผ่าน Tardis, calibrate ด้วย SVI (Stochastic Volatility Inspired) parameterization, ไปจนถึงใช้ HolySheep AI ช่วยตีความ skew และ arbitrage violations พร้อมคะแนนรีวิวตามเกณฑ์ที่กำหนด
1. ทำไมต้อง Tardis + SVI?
ปัญหาคลาสสิกของ Deribit options คือ option chain มีความหนาแน่นสูงมาก (BTC options มักมี strike มากกว่า 200 ราคาในแต่ละวันหมดอายุ) การจะ calibrate surface ที่ arbitrage-free ต้องใช้ข้อมูล tick-level ที่มี bid/ask สม่ำเสมอ Tardis ตอบโจทย์นี้เพราะ:
- จัดเก็บ raw L2 orderbook ทุก 100ms — ไม่มี aggregation ที่ทำให้ skew ผิดเพี้ยน
- symbol format ชัดเจน เช่น
deribit_options_btcusd_28jun24_50000_cแยกวันหมดอายุ/strike/type ชัดเจน - มี HTTP API ดึง historical slices ได้โดยไม่ต้อง download S3 bucket ทั้งหมด
- ราคาเริ่มต้น $50/เดือน สำหรับ retail-friendly plan (เทียบกับ Kaiko $500+)
SVI ถูกเลือกเพราะเป็น parametric form ที่ Gatheral (2004) พิสูจน์ว่าใกล้เคียง market smile ได้ดี และ arbitrage constraints (butterfly, calendar) ตรวจสอบได้ตรงไปตรงมา:
w(k) = a + b * (ρ * (k − m) + √((k − m)² + σ²))
โดย w คือ total implied variance, k คือ log-moneyness, (a, b, ρ, m, σ) คือ 5 parameters ที่ต้อง fit
2. ดึงข้อมูล Deribit Options จาก Tardis
ขั้นแรกผมใช้ Tardis HTTP API ดึง book ticker สำหรับ BTC options ที่หมดอายุวันที่ 28 มิ.ย. 2024 ทั้ง call และ put:
import os
import requests
import pandas as pd
from datetime import datetime, timezone
TARDIS_API_KEY = os.environ["TARDIS_KEY"]
BASE = "https://api.tardis.dev/v1"
def list_btc_options(date_str: str) -> list[str]:
"""ดึงรายชื่อ options symbols ของ BTC ที่หมดอายุวันที่กำหนด"""
url = f"{BASE}/instruments"
resp = requests.get(url, auth=(TARDIS_API_KEY, ""), timeout=30)
resp.raise_for_status()
instruments = resp.json()
# กรองเฉพาะ BTC options ที่หมดอายุตามวันที่ต้องการ
expiry = datetime.strptime(date_str, "%Y-%m-%d").date()
symbols = []
for inst in instruments:
if inst["exchange"] == "deribit" \
and inst["type"] in ("option", "options") \
and inst.get("base_currency") == "BTC" \
and datetime.fromisoformat(inst["expiration_timestamp"]).date() == expiry:
symbols.append(inst["symbol"])
return sorted(symbols)
def fetch_book_ticker(symbols: list[str], date_str: str) -> pd.DataFrame:
"""ดึง book ticker ทุก 1 วินาที ตลอดทั้งวัน"""
url = f"{BASE}/data-feeds/deribit/book_ticker"
params = {
"from": f"{date_str}T00:00:00.000Z",
"to": f"{date_str}T23:59:59.999Z",
"symbols": ",".join(symbols),
}
headers = {"Authorization": f"Bearer {TARDIS_API_KEY}"}
cursor, rows = None, []
while True:
if cursor:
params["cursor"] = cursor
r = requests.get(url, params=params, headers=headers, timeout=60)
r.raise_for_status()
payload = r.json()
rows.extend(payload.get("data", []))
cursor = payload.get("next_cursor")
if not cursor:
break
return pd.DataFrame(rows)
if __name__ == "__main__":
symbols = list_btc_options("2024-06-28")
print(f"พบ {len(symbols)} symbols")
df = fetch_book_ticker(symbols, "2024-06-28")
df.to_parquet("deribit_btc_20240628.parquet")
print(df.head())
ผลลัพธ์: ผมได้ DataFrame ขนาด ~2.3 ล้านแถว (snapshot ทุกวินาที × 240 strikes × 24 ชั่วโมง) ขนาดไฟล์ 187 MB ใช้เวลาดึง 8 นาที (Tardis rate-limit ที่ 100 req/min สำหรับ plan $50)
3. คำนวณ IV และ Fit SVI Parameters
หลังจากคำนวณ mid-price แล้ว ใช้ py_vollib แปลงเป็น implied vol แล้ว fit SVI ต่อ maturity bucket:
import numpy as np
import pandas as pd
from py_vollib.black_scholes import implied_volatility
from scipy.optimize import minimize
1) เตรียมข้อมูล: คำนวณ mid price + IV ณ snapshot เวลา 12:00 UTC
df = pd.read_parquet("deribit_btc_20240628.parquet")
df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
snap = df[df["timestamp"] == df["timestamp"].dt.normalize
แหล่งข้อมูลที่เกี่ยวข้อง
บทความที่เกี่ยวข้อง