ในโลกของ Cryptocurrency trading การพยากรณ์ความผันผวน (Volatility Forecasting) เป็นหัวใจสำคัญของกลยุทธ์การซื้อขายที่ทำกำไรได้ บทความนี้จะพาคุณสร้างโมเดลพยากรณ์ความผันผวนของ Bitcoin โดยใช้ข้อมูลคุณภาพสูงจาก Tardis และเปรียบเทียบประสิทธิภาพระหว่างวิธีการทางสถิติแบบดั้งเดิม (GARCH) กับ Machine Learning ที่ทันสมัย
ทำไมต้องพยากรณ์ความผันผวนของ Bitcoin
Bitcoin เป็นสินทรัพย์ที่มีความผันผวนสูงมาก โดยในช่วงตลาดขาขึ้น ความผันผวนรายวันอาจสูงถึง 5-10% และในช่วงวิกฤตอาจพุ่งเกิน 15% การพยากรณ์ความผันผวนช่วยให้นักเทรด:
- กำหนดขนาด Position ตามความเสี่ยงที่รับได้
- ตั้ง Stop-Loss ที่เหมาะสมกับสภาวะตลาด
- ประเมิน Premium ของ Options หรือ Derivatives
- จัดการ Portfolio แบบ Dynamic Risk Parity
การตั้งค่า Data Pipeline จาก Tardis
Tardis เป็นผู้ให้บริการข้อมูลตลาด Cryptocurrency ระดับ institutional ที่รวบรวม Order Book, Trade, Quote และ Funding Rate จาก Exchange ชั้นนำ ในโปรเจกต์นี้เราจะดึงข้อมูล OHLCV รายนาทีและ Funding Rate เพื่อสร้างฟีเจอร์สำหรับการพยากรณ์
import requests
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class TardisDataFetcher:
"""
คลาสสำหรับดึงข้อมูลจาก Tardis Exchange API
รองรับ: Binance, Bybit, OKX, Deribit
"""
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 fetch_ohlcv(self, exchange: str, symbol: str,
start_date: str, end_date: str,
timeframe: str = '1m') -> pd.DataFrame:
"""
ดึงข้อมูล OHLCV จาก Exchange ที่ระบุ
Args:
exchange: ชื่อ exchange (binance, bybit, okx)
symbol: คู่เทรด (BTCUSDT)
start_date: วันที่เริ่มต้น (YYYY-MM-DD)
end_date: วันที่สิ้นสุด (YYYY-MM-DD)
timeframe: ระยะเวลา (1m, 5m, 1h, 1d)
Returns:
DataFrame ที่มี columns: timestamp, open, high, low, close, volume
"""
# คำนวณช่วงเวลาที่ต้องดึง (Tardis จำกัด 30 วันต่อ request)
start = datetime.strptime(start_date, '%Y-%m-%d')
end = datetime.strptime(end_date, '%Y-%m-%d')
all_data = []
current_start = start
while current_start < end:
current_end = min(current_start + timedelta(days=28), end)
# สร้าง request payload
payload = {
"exchange": exchange,
"symbol": symbol,
"from": current_start.isoformat(),
"to": current_end.isoformat(),
"format": "json",
"columns": ["timestamp", "open", "high", "low", "close", "volume"],
"timeframe": timeframe
}
# ดึงข้อมูลจาก Exchange symbols endpoint
url = f"{self.BASE_URL}/exchange/{exchange}/symbols/{symbol}"
response = self.session.post(url, json=payload, timeout=60)
response.raise_for_status()
data = response.json()
if data:
df_chunk = pd.DataFrame(data)
df_chunk['timestamp'] = pd.to_datetime(df_chunk['timestamp'])
all_data.append(df_chunk)
current_start = current_end + timedelta(minutes=1)
if not all_data:
return pd.DataFrame()
# รวมข้อมูลและเรียงลำดับ
result = pd.concat(all_data, ignore_index=True)
result = result.drop_duplicates(subset=['timestamp']).sort_values('timestamp')
result = result.reset_index(drop=True)
return result
def fetch_funding_rate(self, exchange: str, symbol: str,
start_date: str, end_date: str) -> pd.DataFrame:
"""
ดึงข้อมูล Funding Rate สำหรับคำนวณความผันผวนโดยนัย
"""
payload = {
"exchange": exchange,
"symbol": symbol,
"from": start_date,
"to": end_date,
"format": "json",
"columns": ["timestamp", "funding_rate", "mark_price"]
}
url = f"{self.BASE_URL}/exchange/{exchange}/funding-rate"
response = self.session.post(url, json=payload, timeout=60)
response.raise_for_status()
return pd.DataFrame(response.json())
ตัวอย่างการใช้งาน
fetcher = TardisDataFetcher(api_key="YOUR_TARDIS_API_KEY")
ดึงข้อมูล Bitcoin รายชั่วโมงย้อนหลัง 90 วัน
btc_data = fetcher.fetch_ohlcv(
exchange='binance',
symbol='BTCUSDT',
start_date='2024-10-01',
end_date='2025-01-01',
timeframe='1h'
)
print(f"ดึงข้อมูลสำเร็จ: {len(btc_data)} rows")
print(btc_data.tail())
การสร้างฟีเจอร์สำหรับ Volatility Modeling
ความผันผวนที่แท้จริง (Realized Volatility) คำนวณจาก Log Return ยกกำลังสอง รวมกับฟีเจอร์เพิ่มเติมจาก Order Book และ Funding Rate
import pandas as pd
import numpy as np
from scipy import stats
class VolatilityFeatureEngineer:
"""
สร้างฟีเจอร์สำหรับโมเดลพยากรณ์ความผันผวน
"""
def __init__(self, data: pd.DataFrame):
self.data = data.copy()
self.data.set_index('timestamp', inplace=True)
def compute_returns(self, price_col: str = 'close') -> pd.DataFrame:
"""คำนวณ Log Return และ Realized Variance"""
self.data['log_return'] = np.log(self.data[price_col] / self.data[price_col].shift(1))
self.data['realized_var'] = self.data['log_return'] ** 2
return self.data
def compute_realized_volatility(self, windows: list = [5, 15, 30, 60]) -> pd.DataFrame:
"""
คำนวณ Realized Volatility ที่หลาย Timeframe
Args:
windows: รายการความยาว window (นาที/ชั่วโมง)
"""
for window in windows:
# Realized Variance = Sum of squared returns
rv = self.data['realized_var'].rolling(window=window).sum()
self.data[f'rv_{window}'] = np.sqrt(rv * 252 * 24) # Annualized
# Parkinson Volatility (ใช้ High-Low)
hl = np.log(self.data['high'] / self.data['low'])
parkinson = (hl ** 2) * (1 / (4 * np.log(2))) * window
self.data[f'parkinson_{window}'] = np.sqrt(parkinson * 252 * 24)
# Rogers-Satchell Volatility
log_hc = np.log(self.data['high'] / self.data['close'])
log_cl = np.log(self.data['close'] / self.data['low'])
log_ho = np.log(self.data['high'] / self.data['open'])
log_ol = np.log(self.data['open'] / self.data['low'])
rs = (log_hc * log_cl + log_ho * log_ol).rolling(window=window).mean()
self.data[f'rogers_satchell_{window}'] = np.sqrt(rs * 252 * 24)
return self.data
def compute_order_flow_features(self, order_book_data: pd.DataFrame) -> pd.DataFrame:
"""
คำนวณ Order Flow Imbalance และ Depth Features
"""
# Order Imbalance
order_imbalance = (order_book_data['bid_volume'] - order_book_data['ask_volume']) / \
(order_book_data['bid_volume'] + order_book_data['ask_volume'])
self.data['order_imbalance'] = order_imbalance.reindex(self.data.index, method='ffill')
# Depth Imbalance
depth_ratio = order_book_data['bid_depth'] / order_book_data['ask_depth']
self.data['depth_ratio'] = depth_ratio.reindex(self.data.index, method='ffill')
return self.data
def add_temporal_features(self) -> pd.DataFrame:
"""เพิ่มฟีเจอร์ทางเวลา"""
self.data['hour'] = self.data.index.hour
self.data['day_of_week'] = self.data.index.dayofweek
self.data['is_weekend'] = self.data['day_of_week'].isin([5, 6]).astype(int)
# Cyclical encoding
self.data['hour_sin'] = np.sin(2 * np.pi * self.data['hour'] / 24)
self.data['hour_cos'] = np.cos(2 * np.pi * self.data['hour'] / 24)
self.data['dow_sin'] = np.sin(2 * np.pi * self.data['day_of_week'] / 7)
self.data['dow_cos'] = np.cos(2 * np.pi * self.data['day_of_week'] / 7)
return self.data
def add_lagged_features(self, target_col: str = 'rv_60', lags: list = [1, 2, 3, 6, 12, 24]) -> pd.DataFrame:
"""เพิ่ม Lagged Values ของความผันผวน"""
for lag in lags:
self.data[f'{target_col}_lag{lag}'] = self.data[target_col].shift(lag)
# Rolling statistics
for window in [6, 12, 24]:
self.data[f'{target_col}_ma{window}'] = self.data[target_col].rolling(window).mean()
self.data[f'{target_col}_std{window}'] = self.data[target_col].rolling(window).std()
return self.data
def prepare_dataset(self, target_col: str = 'rv_60', drop_na: bool = True) -> tuple:
"""
เตรียมข้อมูลสำหรับ Training
Returns:
X: Features DataFrame
y: Target Series
"""
# กำหนด Target (Volatility ของ period ถัดไป)
self.data['target'] = self.data[target_col].shift(-1)
# เลือก Features
feature_cols = [col for col in self.data.columns
if col not in ['open', 'high', 'low', 'close', 'volume', 'target']]
X = self.data[feature_cols]
y = self.data['target']
if drop_na:
valid_idx = ~(X.isna().any(axis=1) | y.isna())
X = X[valid_idx]
y = y[valid_idx]
return X, y
ตัวอย่างการใช้งาน
engineer = VolatilityFeatureEngineer(btc_data)
engineer.compute_returns()
engineer.compute_realized_volatility(windows=[5, 15, 30, 60])
engineer.add_temporal_features()
engineer.add_lagged_features()
X, y = engineer.prepare_dataset()
print(f"Dataset shape: X={X.shape}, y={y.shape}")
print(f"Features: {list(X.columns)}")
วิธีที่ 1: GARCH Model — วิธีคลาสสิกทางสถิติ
GARCH (Generalized Autoregressive Conditional Heteroskedasticity) เป็นโมเดลทางสถิติที่ใช้กันอย่างแพร่หลายในการพยากรณ์ความผันผวน โดยอาศัยหลักการว่าความแปรปรวนในปัจจุบันขึ้นอยู่กับความแปรปรวนในอดีตและ squared returns
ทฤษฎีพื้นฐานของ GARCH
โมเดล GARCH(p,q) มีสมการดังนี้:
- Mean Equation: r_t = μ + ε_t
- Variance Equation: σ²_t = ω + Σ(α_i · ε²_{t-i}) + Σ(β_j · σ²_{t-j})
โดยที่ ω > 0, α_i ≥ 0, β_j ≥ 0 และ Σ(α_i + β_j) < 1 (เงื่อนไข Stationarity)
import numpy as np
import pandas as pd
from arch import arch_model
from scipy.optimize import minimize
import warnings
warnings.filterwarnings('ignore')
class GARCHVolatilityModel:
"""
โมเดล GARCH สำหรับพยากรณ์ความผันผวนของ Bitcoin
รองรับ: GARCH, EGARCH, GJR-GARCH, TGARCH
"""
def __init__(self, model_type: str = 'GARCH'):
self.model_type = model_type
self.model = None
self.fitted_model = None
self.params = None
def build_model(self, returns: pd.Series, p: int = 1, q: int = 1,
vol_kwargs: dict = None):
"""
สร้างโมเดล GARCH
Args:
returns: Series ของ log returns
p: GARCH order (lag ของ squared residuals)
q: ARCH order (lag ของ conditional variance)
vol_kwargs: parameters เพิ่มเติม (dist='t', o=1 for GJR)
"""
if vol_kwargs is None:
vol_kwargs = {}
# สร้างโมเดลตามประเภท
if self.model_type == 'GARCH':
self.model = arch_model(returns, vol='Garch', p=p, q=q, **vol_kwargs)
elif self.model_type == 'EGARCH':
self.model = arch_model(returns, vol='EGARCH', p=p, q=q, **vol_kwargs)
elif self.model_type == 'GJR-GARCH':
self.model = arch_model(returns, vol='GARCH', p=p, o=1, q=q, **vol_kwargs)
elif self.model_type == 'TGARCH':
self.model = arch_model(returns, vol='GARCH', p=p, o=1, q=q, **vol_kwargs)
def fit(self, returns: pd.Series, p: int = 1, q: int = 1,
vol_kwargs: dict = None, show_summary: bool = False):
"""
Train โมเดล GARCH
"""
self.build_model(returns, p, q, vol_kwargs)
# Fit โมเดลด้วย Fixed Variance (ใช้ 252 สำหรับ annualize)
self.fitted_model = self.model.fit(disp='off', options={'maxiter': 1000})
if show_summary:
print(self.fitted_model.summary())
return self.fitted_model
def forecast(self, horizon: int = 1) -> np.ndarray:
"""
พยากรณ์ความผันผวนล่วงหน้า
Args:
horizon: จำนวน period ที่ต้องการพยากรณ์
Returns:
Array ของ forecasted volatility (annualized)
"""
if self.fitted_model is None:
raise ValueError("ต้อง fit โมเดลก่อน")
# พยากรณ์ด้วย ARCH package
forecast = self.fitted_model.forecast(horizon=horizon)
# คืนค่า variance แล้ว sqrt เพื่อได้ volatility
variance_forecast = forecast.variance.values[-1, :]
volatility_forecast = np.sqrt(variance_forecast) * np.sqrt(252 * 24) # Annualize
return volatility_forecast
def rolling_forecast(self, returns: pd.Series, window: int = 504,
horizon: int = 1) -> pd.Series:
"""
Rolling Window Forecast สำหรับ Backtesting
Args:
returns: ข้อมูล returns ทั้งหมด
window: ขนาด window สำหรับ train
horizon: horizon สำหรับ forecast
Returns:
Series ของ forecasted volatility
"""
forecasts = []
for i in range(window, len(returns)):
# Train โมเดลด้วย window
train_data = returns.iloc[i-window:i]
try:
model = arch_model(train_data * 100, vol='Garch', p=1, q=1)
res = model.fit(disp='off', options={'maxiter': 500})
forecast = res.forecast(horizon=horizon)
pred_vol = np.sqrt(forecast.variance.values[-1, 0]) / 100
forecasts.append(pred_vol)
except:
forecasts.append(np.nan)
return pd.Series(forecasts, index=returns.index[window:])
ตัวอย่างการใช้งาน
garch = GARCHVolatilityModel(model_type='GARCH')
Fit โมเดล
returns = engineer.data['log_return'].dropna() * 100 # Scale for numerical stability
garch.fit(returns.iloc[-5000:], p=1, q=1, show_summary=True)
พยากรณ์ 1 ชั่วโมงข้างหน้า
next_hour_vol = garch.forecast(horizon=1)
print(f"ความผันผวนที่พยากรณ์ (1 ชั่วโมง): {next_hour_vol[0]*100:.4f}%")
Rolling forecast สำหรับทั้ง dataset
garch_forecasts = garch.rolling_forecast(returns, window=1000, horizon=1)
print(f"Rolling forecast shape: {garch_forecasts.shape}")
วิธีที่ 2: Machine Learning — โมเดล Deep Learning สำหรับ Volatility
วิธี ML ใช้ประโยชน์จากฟีเจอร์หลายมิติและความสัมพันธ์แบบไม่เชิงเส้นที่ GARCH อาจตรวจไม่พบ ในที่นี้เราจะใช้ LightGBM ซึ่งเร็วและแม่นยำ และเปรียบเทียบกับ LSTM สำหรับ Time-Series
import lightgbm as lgb
import numpy as np
import pandas as pd
from sklearn.model_selection import TimeSeriesSplit
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
class MLVolatilityModel:
"""
Machine Learning Models สำหรับ Volatility Prediction
รองรับ: LightGBM, XGBoost, Random Forest
"""
def __init__(self, model_type: str = 'lightgbm'):
self.model_type = model_type
self.model = None
self.scaler = StandardScaler()
self.feature_importance = None
def _get_base_model(self, params: dict = None):
"""สร้างโมเดลตามประเภท"""
if params is None:
params = self._default_params()
if self.model_type == 'lightgbm':
return lgb.LGBMRegressor(**params)
elif self.model_type == 'xgboost':
from xgboost import XGBRegressor
return XGBRegressor(**params)
elif self.model_type == 'rf':
from sklearn.ensemble import RandomForestRegressor
return RandomForestRegressor(**params)
def _default_params(self) -> dict:
"""Default hyperparameters สำหรับ LightGBM"""
return {
'objective': 'regression',
'metric': 'rmse',
'boosting_type': 'gbdt',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'min_child_samples': 20,
'n_estimators': 500,
'verbose': -1,
'random_state': 42,
'n_jobs': -1
}
def train(self, X: pd.DataFrame, y: pd.Series,
val_size: float = 0.2, params: dict = None):
"""
Train โมเดล ML
Args:
X: Features DataFrame
y: Target Series
val_size: สัดส่วน validation set
params: Hyperparameters
"""
# แบ่งข้อมูล (Time-series split - ไม่สuffle)
split_idx = int(len(X) * (1 - val_size))
X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:]
y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:]
# Scale features
X_train_scaled = self.scaler.fit_transform(X_train)
X_val_scaled = self.scaler.transform(X_val)
# Train โมเดล
self.model = self._get_base_model(params)
if self.model_type == 'lightgbm':
self.model.fit(
X_train_scaled, y_train,
eval_set=[(X_val_scaled, y_val)],
callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)]
)
else:
self.model.fit(X_train_scaled, y_train)
# บันทึก feature importance
if hasattr(self.model, 'feature_importances_'):
self.feature_importance = pd.DataFrame({
'feature': X.columns,
'importance': self.model.feature_importances_
}).sort_values('importance', ascending=False)
return self
def predict(self, X: pd.DataFrame) -> np.ndarray:
"""ทำนายความผันผวน"""
X_scaled = self.scaler.transform(X)
return self.model.predict(X_scaled)
def rolling_train_predict(self, X: pd.DataFrame, y: pd.Series,
window: int = 1000, step: int = 24) -> pd.Series:
"""
Rolling Window Training & Prediction สำหรับ Backtesting
ใช้เมื่อต้องการโมเดลที่ปรับตัวตามสภาวะตลาด
"""
predictions = []
indices = []
for i in range(window, len(X), step):
# Train window
train_end = i
train_start = max(0, i - window)
X_train = X.iloc[train_start:train_end]
y_train = y.iloc[train_start:train_end]
# Scale
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# Train
model = self._get_base_model()
if self.model_type == 'lightgbm':
model.fit(X_train_scaled, y_train,
callbacks=[lgb.early_stopping(30, verbose=False)])
else:
model.fit(X_train_scaled, y_train)
# Predict
if i < len(X):
X_pred = scaler.transform(X.iloc[i:i+1])
pred = model.predict(X_pred)[0]
predictions.append(pred)
indices.append(X.index[i])
return pd.Series(predictions, index=indices)
def evaluate(self, y_true: pd.Series, y_pred: pd.Series) -> dict:
"""ประเมินประสิทธิภาพโมเดล"""
return {
'rmse': np.sqrt(mean_squared_error(y_true, y_pred)),
'mae': mean_absolute_error(y_true, y_pred),
'r2': r2_score(y_true, y_pred),
'mape': np.mean(np.abs((y_true - y_pred) / y_true)) * 100
}
ตัวอย่างการใช้งาน
ml_model = MLVolatilityModel(model_type='lightgbm')
Prepare dataset
X, y = engineer.prepare_dataset()
ml_model.train(X, y, val_size=0.2)
Predict
ml_predictions = ml_model.predict(X)
Feature importance
print("Top 10 Important Features:")
print(ml_model.feature_importance.head(10))
Rolling prediction
rolling_preds = ml_model.rolling_train_predict(X, y, window=2000, step=24)
print(f"Rolling predictions: {len(rolling_preds)} values")
การทำ Backtest และเปรียบเทียบประสิทธิภาพ
การทดสอบประสิทธิภาพโมเดลทั้งสองวิ