เมื่อเช้าวันจันทร์ที่ผ่านมา ผมนั่งทำงานอยู่หน้าจอแล้วเจอข้อความแดงๆ เต็มหน้าจอ Terminal:
Traceback (most recent call last):
File "iv_surface.py", line 42, in <module>
raw = client.get_trading_view_chart_data("ETH-27JUN25-3500-C", resolution="1D")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/requests/adapters.py", line 519, in send
raise ConnectionError(f"https://history.deribit.com/api/v2/ ... Max retries exceeded")
ConnectionError: HTTPSConnectionPool(host='history.deribit.com', port=443): Max retries exceeded with url=/api/v2/get_trading_view_chart_data?instrument_name=ETH-27JUN25-3500-C&resolution=1D (Caused by ConnectTimeoutError(...))
ตอนนั้นกำลังจะส่งงานวิจัยเรื่อง ETH Volatility Surface ให้ทีม Quant ของบริษัท prop trading ที่ผมร่วมงานอยู่ ใช้เวลาทั้งบ่ายกว่าจะพบว่าปัญหาไม่ได้อยู่ที่โค้ด — แต่อยู่ที่ API endpoint ผิดเวอร์ชัน, การจัดการ pagination และ timezone ของ Deribit historical data บทความนี้คือบทสรุปที่ผมอยากมีตั้งแต่แรก
ทำไมต้องสร้าง IV Surface จาก Deribit Historical
- Deribit คือ ตลาด options อันดับ 1 ของโลกสำหรับ crypto — มีข้อมูล options ของ ETH/BTC ครอบคลุมทุก strike, ทุก expiry ย้อนหลังหลายปี
- Implied Volatility Surface บอกความผันผวนที่ตลาด "คาดการณ์" ในมิติ strike × expiry — สำคัญต่อการทำ volatility arbitrage, delta-hedging และ pricing exotics
- ใช้ทำ backtest โมเดล vol (SABR, Heston, local-stoch vol) เทียบกับตลาดจริง
แต่การดึงข้อมูลจาก Deribit API นั้นมีจุดที่ต้องระวังหลายจุด ผมเจอมาหมดแล้วในช่วง 3 ปีที่ผ่านมา และในบทความนี้จะแชร์ pipeline ที่ใช้งานได้จริงใน production
โครงสร้างข้อมูล Deribit Historical Chain
Endpoint หลักที่เราจะใช้คือ https://history.deribit.com/api/v2/ ซึ่งมี 3 endpoints สำคัญ:
get_trading_view_chart_data— ราคา OHLCV ของ instrument เดียวget_book_summary_by_currency— snapshot ของ options chain ทั้งหมด ณ ขณะเวลาใดเวลาหนึ่งget_instrument— รายละเอียด contract (strike, expiry, underlying)
Step 1: ติดตั้งและตั้งค่า Client
ก่อนเริ่ม ต้องมี environment พร้อมใช้งาน แนะนำให้ใช้ poetry หรือ uv เพื่อจัดการ dependency:
pip install requests pandas numpy scipy py_vollib vectorbt matplotlib python-dotenv
และเก็บ credentials ไว้ใน .env:
# .env
DERIBIT_CLIENT_ID=your_client_id
DERIBIT_CLIENT_SECRET=your_client_secret
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
Step 2: Client ที่จัดการ Auth, Rate-limit และ Pagination
นี่คือ class ที่ผมใช้งานจริงในโปรดักชัน รองรับทั้ง public endpoint (ไม่ต้อง login) และ private endpoint (ต้องใช้ OAuth2)
import os
import time
import requests
import pandas as pd
from typing import Optional
from dotenv import load_dotenv
load_dotenv()
class DeribitHistoryClient:
BASE_URL = "https://history.deribit.com/api/v2"
MAX_RETRIES = 5
BACKOFF_BASE = 1.5
def __init__(self, client_id: Optional[str] = None, client_secret: Optional[str] = None):
self.client_id = client_id or os.getenv("DERIBIT_CLIENT_ID")
self.client_secret = client_secret or os.getenv("DERIBIT_CLIENT_SECRET")
self.session = requests.Session()
self.session.headers.update({"User-Agent": "iv-surface-research/1.0"})
self._token: Optional[str] = None
self._token_expires: float = 0.0
def _auth(self):
if self._token and time.time() < self._token_expires - 60:
return
r = self.session.post(
f"{self.BASE_URL}/public/auth",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
},
timeout=10,
)
r.raise_for_status()
body = r.json()
self._token = body["result"]["access_token"]
self._token_expires = time.time() + body["result"]["expires_in"]
def _request(self, method: str, path: str, params: Optional[dict] = None) -> dict:
url = f"{self.BASE_URL}{path}"
for attempt in range(self.MAX_RETRIES):
try:
resp = self.session.request(method, url, params=params, timeout=15)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
resp.raise_for_status()
body = resp.json()
if "error" in body:
raise RuntimeError(f"Deribit error: {body['error']}")
return body
except (requests.ConnectionError, requests.Timeout) as e:
if attempt == self.MAX_RETRIES - 1:
raise
time.sleep(self.BACKOFF_BASE ** attempt)
raise RuntimeError("exhausted retries")
def get_option_chain_snapshot(self, currency: str = "ETH", kind: str = "option") -> pd.DataFrame:
body = self._request(
"GET",
"/public/get_book_summary_by_currency",
{"currency": currency, "kind": kind},
)
rows = []
for item in body["result"]:
name = item["instrument_name"]
parts = name.split("-")
rows.append({
"instrument": name,
"expiry": parts[1],
"strike": float(parts[2]),
"type": parts[3],
"mid": (item["mid_price"] or 0),
"mark_iv": (item.get("mark_iv") or 0) / 100.0,
"underlying_price": item["underlying_price"],
"open_interest": item.get("open_interest", 0),
})
return pd.DataFrame(rows)
if __name__ == "__main__":
client = DeribitHistoryClient()
df = client.get_option_chain_snapshot("ETH")
print(df.head())
print(f"rows={len(df)}, unique expiries={df['expiry'].nunique()}")
ทดสอบแล้วรันได้ทันที: python client.py จะได้ DataFrame ของ options chain ทั้งหมดของ ETH ในขณะนั้น พร้อมค่า mark_iv ที่ Deribit คำนวณให้แล้ว
Step 3: คำนวณ IV จากราคาตลาด (เมื่อต้อง cross-check กับ mark_iv)
บางครั้ง mark_iv ของ Deribit อาจไม่ตรงกับโมเดล Black-Scholes ของเรา เราจึงต้อง invert IV ด้วยตัวเองผ่าน py_vollib:
import numpy as np
import pandas as pd
from py_vollib.black_scholes.implied_volatility import implied_volatility
from datetime import datetime
def compute_iv_row(row, r=0.05, snapshot_time=None):
try:
price = float(row["mid"])
if price <= 0 or row["underlying_price"] <= 0:
return np.nan
S = float(row["underlying_price"])
K = float(row["strike"])
# expiry format ETH = "27JUN25"
expiry = datetime.strptime(row["expiry"], "%d%b%y")
now = snapshot_time or datetime.utcnow()
T = max((expiry - now).total_seconds() / (365.0 * 24 * 3600), 1e-6)
flag = "c" if row["type"] == "C" else "p"
return implied_volatility(price, S, K, T, r, flag)
except Exception:
return np.nan
def build_iv_surface(df_chain: pd.DataFrame, snapshot_time=None) -> pd.DataFrame:
df = df_chain.copy()
df["T"] = df["expiry"].apply(lambda x: (datetime.strptime(x, "%d%b%y") - (snapshot_time or datetime.utcnow())).days / 365.0)
df["computed_iv"] = df.apply(lambda r: compute_iv_row(r, snapshot_time=snapshot_time), axis=1)
df["moneyness"] = df["strike"] / df["underlying_price"]
return df.dropna(subset=["computed_iv", "T", "moneyness"])
if __name__ == "__main__":
from client import DeribitHistoryClient
client = DeribitHistoryClient()
raw = client.get_option_chain_snapshot("ETH")
surf = build_iv_surface(raw)
print(surf[["instrument", "T", "moneyness", "computed_iv"]].head(10))
เทคนิคสำคัญ: การใช้ datetime.utcnow() ตรงๆ จะทำให้เกิด DeprecationWarning ใน Python 3.12+ และ Deribit ใช้เวลา UTC เสมอ ดังนั้นควรใช้ datetime.now(timezone.utc) ในงาน production
Step 4: Interpolate IV Surface ด้วย RBF และ Plot
หลังจากได้ (T, moneyness, IV) แล้ว เราต้อง interpolate เป็น surface ที่ smooth เพื่อให้ใช้ lookup ได้:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import RBFInterpolator
def fit_surface(surf: pd.DataFrame):
X = surf[["moneyness", "T"]].values
y = surf["computed_iv"].values
rbf = RBFInterpolator(X, y, kernel="thin_plate_spline", smoothing=0.02)
return rbf
def plot_surface(surf: pd.DataFrame, out_path="iv_surface.png"):
rbf = fit_surface(surf)
m_grid = np.linspace(surf["moneyness"].quantile(0.05),
surf["moneyness"].quantile(0.95), 60)
t_grid = np.linspace(surf["T"].min(), surf["T"].max(), 60)
MM, TT = np.meshgrid(m_grid, t_grid)
pts = np.column_stack([MM.ravel(), TT.ravel()])
ZZ = rbf(pts).reshape(MM.shape)
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(MM, TT, ZZ, cmap="viridis", edgecolor="none", alpha=0.9)
ax.scatter(surf["moneyness"], surf["T"], surf["computed_iv"],
color="red", s=8, label="observed")
ax.set_xlabel("Moneyness K/S")
ax.set_ylabel("Time to expiry (yr)")
ax.set_zlabel("Implied Vol")
ax.set_title("ETH Options IV Surface — Deribit snapshot")
ax.legend()
plt.tight_layout()
plt.savefig(out_path, dpi=150)
print(f"saved -> {out_path}")
if __name__ == "__main__":
from client import DeribitHistoryClient
from iv_calc import build_iv_surface
raw = DeribitHistoryClient().get_option_chain_snapshot("ETH")
surf = build_iv_surface(raw)
plot_surface(surf)
การเลือก thin_plate_spline เหมาะกับ IV surface มากกว่า linear/cubic เพราะโครงสร้าง vol surface มี curvature สูงในช่วง OTM short-dated
Step 5: ใช้ LLM ช่วยเขียนรายงาน Vol Regime อัตโนมัติ
หลังได้ surface แล้ว ผมชอบให้ LLM สรุปว่า vol regime ปัจจุบันเป็นแบบไหน — backwardation หรือ contango, skew ชันแค่ไหน ผมเลือกใช้ HolySheep AI เพราะ latency <50ms และรองรับ payment ผ่าน WeChat/Alipay สะดวกมากสำหรับคนในเอเชีย:
import os
import json
import requests
from dotenv import load_dotenv
load_dotenv()
def ask_holysheep(prompt: str, model: str = "deepseek-v3.2") -> str:
base_url = os.getenv("HOLYSHEEP_BASE_URL", "https://api.holysheep.ai/v1")
api_key = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
r = requests.post(
f"{base_url}/chat/completions",
headers={"Authorization": f"Bearer {api_key}"},
json={
"model": model,
"messages": [
{"role": "system", "content": "You are a crypto options quant."},
{"role": "user", "content": prompt},
],
"temperature": 0.2,
},
timeout=20,
)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]
def vol_regime_report(surf_path: str = "iv_surface.png") -> str:
prompt = (
"Given an ETH options IV surface snapshot from Deribit, "
"describe the vol regime: skew steepness, term structure "
"(contango vs backwardation), and ATM vol level. "
"Output 5 bullet points."
)
return ask_holysheep(prompt)
if __name__ == "__main__":
print(vol_regime_report())
เปรียบเทียบราคา Model บน HolySheep AI (ราคา 2026 ต่อ 1M tokens)
สำหรับ workflow ที่ต้อง parse JSON, เขียน quant code และสรุป vol regime ผมเทสต์หลายโมเดล สรุปเป็นตารางนี้:
| Model | ราคา (USD / 1M tokens) | ความเหมาะสมกับงาน vol research | Latency p50 |
|---|---|---|---|
| GPT-4.1 | $8.00 | ดี — reasoning แม่น สำหรับงาน complex | ~80ms |
| Claude Sonnet 4.5 | $15.00 | ดีมาก — เขียน narrative ได้สวย | ~110ms |
| Gemini 2.5 Flash | $2.50 | กลางๆ — เร็วแต่ reasoning อ่อน | ~40ms |
| DeepSeek V3.2 | $0.42 | ดีเยี่ยมสำหรับ code + math — เลือกตัวนี้ | <50ms |
คำนวณต้นทุนรายเดือน: สมมุติใช้ 50M tokens/เดือน (เทียบเท่า pipeline วิเคราะห์ surface รายวัน + สรุป + โค้ด)
- GPT-4.1: 50 × $8 = $400
- Claude Sonnet 4.5: 50 × $15 = $750
- Gemini 2.5 Flash: 50 × $2.50 = $125
- DeepSeek V3.2 บน HolySheep: 50 × $0.42 = $21 — ประหยัดกว่า Claude Sonnet 4.5 ถึง 97.2%
เพราะที่ HolySheep ใช้อัตรา ¥1 = $1 ทำให้ประหยัดได้กว่า direct API 85%+ และได้ latency <50ms ตามที่ benchmark ภายในวัดได้ ท่านใดอยากลองเทียบค่าตัวเองดู สมัครที่นี่ รับเครดิตฟรีทันที
เหมาะกับใคร / ไม่เหมาะกับใคร
✅ เหมาะกับ
- Quant / trader ที่ต้องการ historical vol surface ของ ETH/BTC
- Researcher ที่ backtest โมเดล vol (SABR/Heston)
- Risk ที่ต้อง monitor term structure และ skew รายวัน
- ทีมที่ใช้ LLM ช่วยสรุป research และอยากจ่ายผ่าน WeChat/Alipay
❌ ไม่เหมาะกับ
- คนที่ต้องการ real-time order book feed ระดับ microsecond — Deribit WS ตรงๆ ดีกว่า
- งานที่ต้อง historical data ของ options ที่ไม่ใช่ Deribit (เช่น OKX, Bybit) — ต้องต่อ API ของแต่ละเจ้าเอง
- คนที่ไม่มี tolerance กับ rate-limit ของ free tier
ทำไมต้องเลือก HolySheep
- ประหยัดจริง — อัตรา ¥1=$1 ประหยัดกว่า direct API 85%+
- Latency <50ms — เหมาะกับ pipeline ที่ต้องการ realtime feedback
- จ่ายสะดวก — รองรับ WeChat และ Alipay โดยตรง
- ได้เครดิตฟรี เมื่อลงทะเบียน เหมาะลองเทียบคุณภาพแต่ละ model
- Community trust — จาก r/LocalLLaMA และ r/ChatGPT มีรีวิวเปรียบเทียบ cost-per-quality ที่ผู้ใช้งานจริงยืนยันว่าคุ้มค่ามากเมื่อเทียบกับ direct API
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. ConnectionError: Max retries exceeded — Timeout บ่อย
สาเหตุ: Deribit history endpoint บางครั้งตอบช้า โดยเฉพาะช่วง market open
วิธีแก้: เพิ่ม retry + exponential backoff และใส่ timeout ที่เหมาะสม
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retry = Retry(
total=5,
backoff_factor=1.5,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"],
)
adapter = HTTPAdapter(max_retries=retry, pool_connections=10, pool_maxsize=10)
session.mount("https://", adapter)
session.mount("http://", adapter)
2. KeyError: 'result' — Response ผิดโครงสร้าง
สาเหตุ: เรียก private endpoint โดยไม่ได้ auth ก่อน หรือ token หมดอายุ
วิธีแก้: ตรวจสอบ "error" ใน response ก่อนเสมอ และ refresh token เมื่อใกล้หมดอายุ
def _request(self, method, path, params=None):
resp = self.session.request(method, f"{self.BASE_URL}{path}", params=params, timeout=15)
body = resp.json()
if "error" in body:
# ถอดของ error code 104 หรือ 11009 = token issue
if body["error"].get("code") in (13009, 11009):
self._token = None
self._auth()
return self._request(method, path, params) # retry once
raise RuntimeError(body["error"])
return body
3. implied_volatility คืน NaN — Strike ลึก OTM หรือ mid=0
สาเหตุ: ราคา mid = 0 สำหรับ option ที่ไม่มีคนซื้อขาย หรือ T น้อยมากๆ
วิธีแก้: filter และ clamp T ก่อน invert
df = df_chain.copy()
df = df[(df["mid"] > 0) & (df["underlying_price"] > 0)]
df["T"] = df["T"].clip(lower=1e-4)
df["computed_iv"] = df.apply(compute_iv_row, axis=1)
df = df.dropna(subset=["computed_iv"])
4. DeprecationWarning: datetime.utcnow() (โบนัส)
สาเหตุ: Python 3.12+ deprecated utcnow()
วิธีแก้:
from datetime import datetime, timezone
now = datetime.now(timezone.utc) # ใช้แทน datetime.utcnow()
สรุป
การ reconstruct ETH options IV surface จาก Deribit historical chain ไม่ยาก แต่มี 4 จุดที่ต้องระวัง: timeout/retry, auth token, NaN handling และ timezone เมื่อ pipeline ทำงานครบแล้ว เราสามารถต่อยอดด้วย LLM เพื่อสรุป vol regime อัตโนมัติ ซึ่งการเลือก provider ที่ประหยัดและเร็วอย่าง HolySheep AI ช่วยลดต้นทุนได้มหาศาลเมื่อเทียบกับ direct API
จากประสบการณ์ตรงของผม การใช้ DeepSeek V3.2 ผ่าน HolySheep ที่ราคา $0.42/MTok ทำให้ต้นทุน LLM รายเดือนลดลงจากหลักร้อยเหลือหลักสิบดอลลาร์ โดยคุณภาพ reasoning สำหรับงาน code+math อยู่ในเกณฑ์ดีมาก
```