ผมเป็น 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 ตอบโจทย์นี้เพราะ:

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