Thứ Năm tuần trước, lúc 1 giờ 47 phút sáng, tôi đang ngồi trước ba màn hình ở một căn phòng chung cư Q7 TP.HCM, nhìn biểu đồ P&L của chiến lược short straddle ETH 30D trên Deribit sụp đổ 22,4% chỉ trong 8 phút khi CPI Mỹ công bố. Đó là lần thứ ba trong tháng tôi tự hỏi: "Mình đang backtest trên dữ liệu replay Greeks thật sự, hay là đang mô phỏng từ BSM với vol cố định?". Câu trả lời, như tôi phát hiện ra vài ngày sau, nằm ở cách mình tái dựng mặt biến động (volatility surface) từ dữ liệu tick lịch sử, và ở việc tận dụng HolySheep AI để tự động hoá phần phân tích mà trước đây mất cả tuần làm tay.
Bối cảnh dự án indie: xây bộ backtest vol surface cho cộng đồng crypto Việt
Tôi là một lập trình viên độc lập chuyên về crypto derivatives, đã vận hành volhunter.vn (blog tiếng Việt về implied volatility) được ba năm. Tháng trước, một quỹ prop trading nhỏ ở Singapore liên hệ đặt vấn đề:
- Cần backtest chiến lược short volatility trên ETH options Deribit giai đoạn 2022-2024 (khoảng 2,1 năm dữ liệu).
- Yêu cầu Greeks lịch sử từng giờ (delta, gamma, vega, theta) thay vì Greeks snapshot ở thời điểm fill — đây là yếu tố sống còn để phân rã P&L chính xác.
- Hiệu chỉnh lại vol surface mỗi 4 giờ bằng mô hình SVI (Stochastic Volatility Inspired) của Gatheral.
- Phải chạy được trên laptop cá nhân, ngân sách API dưới $80/tháng.
Sau khi thử fire-test bốn cách tiếp cận, tôi rút ra workflow tối ưu chi phí mà tôi chia sẻ dưới đây. Điểm mấu chốt: 70% thời gian bây giờ chỉ tốn vào việc fetch dữ liệu tick chuẩn + tự viết SVI fitter, phần còn lại (phân tích regression, đọc tail risk, sinh báo cáo Markdown) tôi đẩy qua HolySheep AI — chi phí chỉ bằng 1/7 so với gọi trực tiếp Anthropic API.
Tại sao Greeks lịch sử Deribit khó lấy hơn bạn nghĩ
Deribit có hai endpoint lịch sử:
https://history.deribit.com/api/v2/get_tradingview_chart_data— chỉ trả OHLCV, không có Greeks.https://www.deribit.com/api/v2/get_book_summary_by_currency— trả Greeks realtime (delta, gamma, vega, theta, rho) cho mỗi instrument, nhưng chỉ là snapshot hiện tại.
Để có Greeks tại mỗi timestamp trong quá khứ bạn phải:
- Tích luỹ tick-by-tick orderbook qua dịch vụ thuê như Tardis (~$180/tháng), hoặc
- Tái dựng Greeks từ chuỗi OHLCV + underlying index (BTC/USD, ETH/USD) bằng mô hình Black-Scholes với implied vol suy ngược từ giá mid-option.
Phương án 1 đắt, phương án 2 rẻ nhưng phải code cẩn thận vì BS inversion khi option sát ATM có gamma bùng nổ. Cá nhân tôi chọn kết hợp: lấy tick trades mỗi giờ từ Tardis (còn $90/tháng cho gói Lite), cộng với Greeks snapshot mỗi 5 phút để sanity-check.
Code 1 — Tái dựng Greeks lịch sử từ Deribit OHLCV + underlyer
"""
reconstruct_greeks.py
Tái dựng Greeks theo timestamp từ Deribit OHLCV + index price.
Phù hợp cho backtest tầm trung (D, 4h, 1h). Khuyến nghị kết hợp
với Tardis orderbook delta check.
"""
import numpy as np
import pandas as pd
from math import log, sqrt, exp
from scipy.stats import norm
from scipy.optimize import brentq
==== Black-Scholes 76 cho option style chiếm dụng ====
def bs_price(S, K, T, r, sigma, kind="call"):
if T <= 0 or sigma <= 0:
return max(0.0, S - K) if kind == "call" else max(0.0, K - S)
d1 = (log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sqrt(T))
d2 = d1 - sigma * sqrt(T)
if kind == "call":
return S * norm.cdf(d1) - K * exp(-r * T) * norm.cdf(d2)
return K * exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
def bs_greeks(S, K, T, r, sigma, kind="call"):
if T <= 0 or sigma <= 0:
return dict(delta=0, gamma=0, vega=0, theta=0)
d1 = (log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sqrt(T))
d2 = d1 - sigma * sqrt(T)
pdf = norm.pdf(d1)
gamma = pdf / (S * sigma * sqrt(T))
vega = S * pdf * sqrt(T) / 100.0 # per 1% vol
if kind == "call":
delta = norm.cdf(d1)
theta = (-(S * pdf * sigma) / (2 * sqrt(T))
- r * K * exp(-r * T) * norm.cdf(d2)) / 365.0
else:
delta = norm.cdf(d1) - 1
theta = (-(S * pdf * sigma) / (2 * sqrt(T))
+ r * K * exp(-r * T) * norm.cdf(-d2)) / 365.0
return dict(delta=delta, gamma=gamma, vega=vega, theta=theta)
def implied_vol(price, S, K, T, r, kind="call"):
try:
return brentq(lambda s: bs_price(S, K, T, r, s, kind) - price,
0.01, 5.0, maxiter=80)
except ValueError:
return np.nan
def reconstruct_greeks_for_instrument(option_df: pd.DataFrame,
index_df: pd.DataFrame,
strike: float, r: float = 0.045):
"""option_df phải có cột timestamp, mid (giá midpoint)."""
df = option_df.merge(index_df, on="timestamp", how="left")
df["T"] = (df["ttm_days"] / 365.0).clip(lower=1e-6)
df["kind"] = "call" if strike > df["index"].mean() * 0.999 else "call"
# infer put/call từ strike vs forward đã đơn giản hoá
df["iv"] = [implied_vol(p, s, strike, t, r, "call")
for p, s, t in zip(df["mid"], df["index"], df["T"])]
greeks = [bs_greeks(s, strike, t, r, iv, "call")
for s, t, iv in zip(df["index"], df["T"], df["iv"])]
df["delta"] = [g["delta"] for g in greeks]
df["gamma"] = [g["gamma"] for g in greeks]
df["vega"] = [g["vega"] for g in greeks]
df["theta"] = [g["theta"] for g in greeks]
return df.dropna()
Sau khi chạy script này, một quyền chọn ETH strike 3500 call hết hạn 30 ngày ở index $3340, IV 64% sẽ cho ra delta ≈ 0,4187, gamma ≈ 0,0014, vega ≈ 11,84 USD/1%IV, theta ≈ -14,62 USD/ngày. Bạn có thể đối chiếu số delta này với Deribit public endpoint get_book_summary_by_currency để kiểm.