Arbitrase Statistik dan Pairs Trading di Pasar Kripto: Dari Kointegrasi hingga Kalman Filter
Pada tahun 1987, sekelompok fisikawan di Morgan Stanley meraup $50 juta dalam satu tahun dengan memperdagangkan pasangan saham menggunakan algoritma yang tidak satu pun dari mereka bisa jelaskan sepenuhnya kepada manajemen bank. Manajemen tidak keberatan. Di tahun 2026, Anda bisa menjalankan ide yang sama di bursa kripto — dengan perpetual futures, pasar 24/7, dan likuiditas yang akan membuat Nunzio Tartaglia iri. Tapi ada tapinya: apa yang berhasil dengan saham Ford dan GM di era pra-internet membutuhkan adaptasi serius untuk dunia di mana BTC bisa turun 20% dalam semalam dan funding rate bisa berbalik dalam satu blok.
Artikel ini adalah uraian lengkap tentang arbitrase statistik dan pairs trading untuk pasar kripto. Dari teori matematika (kointegrasi, proses Ornstein-Uhlenbeck, Kalman filter) hingga kode Python yang bisa Anda jalankan pada data nyata. Gaya penulisan berorientasi rekayasa: kami menjelaskan rumus, menampilkan kode, dan tidak menyembunyikan jebakan-jebakannya.
1. Sejarah Singkat: Dari Jesuit hingga Para Quant
Arbitrase statistik dalam bentuk modernnya lahir di meja trading Morgan Stanley pada pertengahan 1980-an. Nunzio Tartaglia — mantan pastor Jesuit dengan gelar PhD di bidang fisika — mengumpulkan tim ahli matematika, fisikawan, dan ilmuwan komputer. Tujuannya: menemukan pola dalam harga saham yang tidak bisa dilihat oleh trader tradisional.
Idenya sangat sederhana. Jika saham Coca-Cola dan Pepsi secara historis bergerak bersama (yang masuk akal — mereka menjual air bergula yang sama dalam botol berwarna berbeda), maka divergensi harga mereka adalah anomali sementara. Beli yang tertinggal, jual yang terdepan, tunggu konvergensi, kunci keuntungan. Strategi market-neutral: arah pasar tidak jadi perhatian kita.
Tim Tartaglia mencakup orang-orang yang kemudian mengubah seluruh Wall Street:
- David Shaw — kemudian mendirikan D.E. Shaw & Co., salah satu hedge fund kuantitatif terbesar
- Peter Muller — mendirikan PDT Partners, grup stat arb legendaris di dalam Morgan Stanley
- Robert Frey — kemudian bergabung dengan Renaissance Technologies di bawah Jim Simons
Grup ini beroperasi sebagai laboratorium penelitian di dalam bank investasi. Otomasi merupakan yang terdepan: kluster VAX menghasilkan sinyal, dan eksekusi dilakukan melalui terminal. Pada tahun-tahun terbaik (1987-1988), strategi ini menghasilkan puluhan juta dolar. Kemudian datang dua tahun berturut-turut yang merugi, dan pada 1989 Morgan Stanley menutup meja tersebut.
Tapi idenya sudah tersebar luas. Alumni kelompok ini menyebarkan konsep pairs trading ke seluruh Wall Street. Gatev, Goetzmann, dan Rouwenhorst menerbitkan makalah akademik klasik pada tahun 2006 — "Pairs Trading: Performance of a Relative-Value Arbitrage Rule" — yang menunjukkan bahwa strategi pairs trading sederhana secara konsisten menghasilkan ~11% imbal hasil tahunan dari 1962 hingga 2002 pada ekuitas AS. Ini merupakan respons yang meyakinkan terhadap hipotesis pasar efisien: pasar secara keseluruhan mungkin efisien, tetapi pasangan aset tertentu secara sistematis menyimpang dari keseimbangan.
Saat ini, arbitrase statistik adalah industri dengan AUM ratusan miliar dolar, dan pasar kripto menawarkan lahan yang sangat subur: likuiditas terfragmentasi, mikrostruktur yang belum matang, perdagangan sepanjang waktu, dan perpetual futures dengan funding rate — instrumen yang sama sekali tidak ada di pasar tradisional.
2. Fondasi Matematika: Korelasi Adalah Jebakan
Mengapa Korelasi Tidak Bekerja
Mari mulai dengan kesalahan yang dilakukan hampir setiap quant pemula: "BTC dan ETH berkorelasi dengan koefisien 0,85, jadi kita bisa memperdagangkan pasangan ini." Tidak. Tidak bisa. Yah, bisa saja — tapi Anda akan kehilangan uang.
Korelasi mengukur hubungan linear antara imbal hasil dua aset. Dua aset bisa berkorelasi sempurna, namun harga mereka menyimpang selamanya. Contoh klasik: dua random walk dengan inkremen berkorelasi — mereka menyimpang tak terhingga meskipun korelasinya tinggi. Anda akan membuka posisi dengan harapan "konvergensi" yang tidak akan pernah datang.

Kointegrasi: Pendekatan yang Tepat
Kointegrasi adalah properti dari seri harga, bukan imbal hasil. Dua seri non-stasioner X(t) dan Y(t) disebut terkointegrasi jika terdapat kombinasi linear:
S(t) = Y(t) - β · X(t)
yang stasioner — artinya bergerak kembali ke nilai rata-rata. Koefisien β disebut hedge ratio, dan S(t) adalah spread.
Intuisi: BTC dan ETH bisa melesat ke bulan atau terjun ke jurang, tetapi jika perbedaan mereka (yang diskalakan dengan benar) berosilasi di sekitar tingkat tetap — itulah kointegrasi. Dan itulah yang kita butuhkan untuk trading.
Uji Engle-Granger (1987)
Prosedur dua langkah yang menghasilkan Hadiah Nobel Ekonomi bagi Robert Engle dan Clive Granger pada tahun 2003:
Langkah 1. Regresi OLS: Y(t) = α + β · X(t) + ε(t). Kita peroleh hedge ratio β dan residual ε(t).
Langkah 2. Uji ADF (Augmented Dickey-Fuller) pada residual ε(t). Hipotesis nol: ε(t) memiliki unit root (non-stasioner). Jika p-value < 0,05, kita tolak H₀ — seri tersebut terkointegrasi.
Penting: untuk uji kointegrasi, Anda tidak dapat menggunakan nilai kritis ADF standar. Nilai kritis Engle-Granger diturunkan melalui simulasi Monte Carlo dan memperhitungkan ketergantungan antar variabel dalam regresi OLS. Dalam statsmodels, ini diimplementasikan dengan benar dalam fungsi coint().
Uji Johansen
Untuk sistem dengan lebih dari dua variabel (misalnya, BTC, ETH, dan SOL secara bersamaan), digunakan uji Johansen. Uji ini menemukan semua hubungan kointegrasi dalam sistem dan memungkinkan konstruksi portofolio dari beberapa aset. Uji ini didasarkan pada model VAR (vektor autoregresif) dan menggunakan dua kriteria: statistik trace dan statistik nilai eigen maksimum.
Proses Ornstein-Uhlenbeck
Jika spread terkointegrasi, dinamikanya dapat dimodelkan sebagai proses Ornstein-Uhlenbeck (OU):
dS(t) = θ(μ - S(t))dt + σ dW(t)
di mana:
- θ — kecepatan mean reversion
- μ — tingkat mean jangka panjang
- σ — volatilitas
- W(t) — proses Wiener (gerak Brownian)
Dari parameter proses OU, half-life dari mean reversion dihitung:
t½ = ln(2) / θ
Half-life adalah metrik yang sangat penting. Jika t½ = 5 hari, spread kembali ke rata-rata dalam sekitar 5 hari. Jika t½ = 200 hari, Anda akan duduk dalam posisi setengah tahun menunggu konvergensi. Untuk strategi kripto, half-life optimal adalah 1-30 hari. Lebih pendek — terlalu cepat, komisi menghabiskan keuntungan. Lebih panjang — terlalu lambat, risiko perubahan rezim struktural.
Dalam praktiknya, θ diestimasi melalui regresi:
ΔS(t) = a + b · S(t-1) + ε(t)
di mana θ = -b, dan t½ = -ln(2) / b.
Normalisasi Z-Score
Untuk menghasilkan sinyal trading, spread dinormalisasi:
z(t) = (S(t) - μ̂) / σ̂
di mana μ̂ dan σ̂ adalah rata-rata bergulir dan deviasi standar dari spread. Z-score menunjukkan berapa deviasi standar spread telah menyimpang dari rata-rata. Ambang batas masuk yang umum: |z| > 2,0; ambang batas keluar: |z| < 0,5.
3. Memilih Pasangan di Pasar Kripto
BTC-ETH: Klasik yang (Kadang-Kadang) Berhasil
BTC dan ETH adalah pasangan yang paling jelas dan paling likuid. Korelasi imbal hasil secara konsisten di atas 0,7. Tapi kointegrasi adalah hal yang berbeda. Ia muncul dan menghilang:
- Selama pasar sideways 2023, BTC/ETH terkointegrasi dengan andal (p-value < 0,01)
- Selama divergensi 2024-2025 (BTC reli karena aliran ETF, ETH tertinggal), kointegrasi pecah
- Pada awal 2026, setelah peluncuran ETH ETF dan pemulihan rasio ETH/BTC, kointegrasi stabil kembali
Kesimpulan: kointegrasi harus terus dipantau. Parameter regresi dihitung ulang pada jendela bergulir, dan strategi secara otomatis dimatikan jika p-value uji ADF melebihi ambang batas.
Pasangan Sektoral
Pasar kripto tersegmentasi dengan nyaman berdasarkan sektor, dan pasangan dalam sektor yang sama sering menunjukkan kointegrasi yang stabil:
| Sektor | Contoh Pasangan | Karakteristik |
|---|---|---|
| Blockchain L1 | SOL/AVAX, NEAR/APT | Likuiditas tinggi, half-life 3-10 hari |
| Protokol DeFi | AAVE/COMP, UNI/SUSHI | Likuiditas sedang, half-life 5-15 hari |
| Solusi L2 | ARB/OP, MATIC/MANTA | Volatilitas spread tinggi |
| Memecoin | DOGE/SHIB | Tidak terduga tapi menyenangkan (tidak disarankan) |
Pasangan terbaik untuk stat arb memiliki tiga properti: (1) kointegrasi stabil selama jendela historis >6 bulan, (2) likuiditas cukup — volume harian >$10 juta per aset, (3) half-life yang wajar — dari 1 hingga 30 hari.
Spot vs Perpetual Futures (Basis)
Kategori "pasangan" yang terpisah adalah aset yang sama di pasar spot dan futures. Perbedaan antara harga perpetual futures dan harga spot (basis) secara definisi stasioner: mekanisme funding rate menekannya kembali ke nol. Ini menjadikan basis trading salah satu bentuk stat arb yang paling andal di kripto.
4. Tiga Pendekatan Trading
A. Basis Trading: Spot-Futures dan Funding Rate Carry
Bentuk stat arb yang "paling murni" di kripto. Mekanismenya:
- Beli aset di spot (misalnya, 1 BTC)
- Buka posisi short pada perpetual future (1 BTC)
- Jika funding rate positif (long membayar short) — Anda menerima funding setiap 8 jam
Pada funding rate rata-rata 0,01% setiap 8 jam, itu ~0,03% per hari atau ~11% disetahunkan tanpa risiko arah. Selama pasar bull, funding rate bisa naik ke 0,05-0,1% setiap 8 jam — itu sudah 55-110% disetahunkan.
Risiko: funding negatif (pasar berbalik), likuidasi posisi short selama kenaikan harga tajam (buffer margin diperlukan), dan biaya bursa.
Per Maret 2026, rata-rata funding rate BTC telah stabil pada sekitar ~0,015% per 8 jam — sekitar 50% di atas level 2024.
B. Arbitrase Lintas Bursa
Koin yang sama, dua bursa, harga berbeda. Alasannya — perbedaan likuiditas, komposisi trader, dan kecepatan pembaruan order book.
Contoh: BTC di Binance: 87.175. Spread: $25 (0,029%).
Strategi: beli di Binance, jual di Bybit. Masalah: pada saat kedua order dieksekusi, spread mungkin sudah lenyap. Solusi: pertahankan saldo di kedua bursa dan eksekusi secara bersamaan.
Biaya umum:
- Binance: ~0,075% taker (dengan diskon ~0,05%)
- Bybit: ~0,03% taker (VIP)
- Total: ~0,08%
Ini berarti spread harus melebihi 0,08% agar strategi menguntungkan. Pada 2026, spread seperti itu muncul:
- Pada pasangan yang kurang likuid (altcoin) — secara teratur
- Pada pasangan utama (BTC, ETH) — hanya selama momen volatilitas tinggi
- Antara CEX dan DEX — lebih sering, tetapi dengan risiko MEV dan slippage
Tanpa co-location, latensi API adalah 10-100 ms. Dengan jaringan yang dioptimalkan — ~1 ms. Sebagian besar trader ritel beroperasi dalam rentang 100-500 ms, yang cukup untuk banyak strategi arbitrase tetapi tidak cukup untuk bersaing dengan institusi.
C. Pairs Trading dengan Leverage
Pairs trading klasik pada dua aset berbeda menggunakan leverage. Ini adalah yang paling kompleks dari ketiga strategi — dan berpotensi paling menguntungkan.
Mekanisme menggunakan pasangan SOL/AVAX sebagai contoh:
- Hitung hedge ratio β (misalnya, β = 1,3)
- Ketika z-score > +2: short SOL, long AVAX × β
- Ketika z-score < -2: long SOL, short AVAX × β
- Keluar: |z-score| < 0,5 atau timeout (misalnya, 30 hari)
Dengan leverage 3x pada setiap kaki dan rata-rata revert spread 2σ → 3σ:
- Target imbal hasil per trade: ~3-6%
- Frekuensi rata-rata: 2-4 trade per bulan per pasangan
- Imbal hasil tahunan yang diharapkan: 30-60% (sebelum komisi dan slippage)
Risiko utama: korelasi bisa pecah di momen terburuk (biasanya selama crash pasar). Lebih lanjut tentang ini di bagian 8.
5. Kalman Filter untuk Hedge Ratio Adaptif
Mengapa Hedge Ratio Statis Menjadi Masalah
Pendekatan klasik: estimasi β melalui OLS pada jendela historis dan tetapkan nilainya. Masalahnya: β berubah seiring waktu. Pasar kripto sangat non-stasioner — pergeseran narasi (DeFi summer → hype NFT → token AI) mengubah hubungan fundamental antar aset.
Menggunakan rolling OLS (regresi bergulir) adalah setengah-setengah. Anda harus memilih panjang jendela: terlalu pendek — noise; terlalu panjang — lag. Kalman filter memecahkan masalah ini dengan elegan.

Model Ruang-Status
Kita merepresentasikan hubungan antara Y(t) dan X(t) sebagai model linear dengan koefisien yang bervariasi terhadap waktu:
Persamaan observasi:
Y(t) = α(t) + β(t) · X(t) + ε(t), ε(t) ~ N(0, R)
Persamaan status:
[α(t+1), β(t+1)]ᵀ = [α(t), β(t)]ᵀ + w(t), w(t) ~ N(0, Q)
Parameter α(t) dan β(t) diperlakukan sebagai status tersembunyi yang perlahan melayang (random walk). Kalman filter secara optimal mengestimasi status tersembunyi ini dari observasi yang bising.
- R (noise observasi) — varians noise observasi. Semakin besar R, semakin lambat filter merespons data baru.
- Q (noise status) — matriks kovarians noise status. Semakin besar Q, semakin cepat filter beradaptasi.
Rasio Q/R menentukan "kehalusan" filter — analog dengan memilih panjang jendela dalam rolling OLS, tetapi tanpa pemotongan data yang keras.
Keunggulan Dibandingkan Rolling OLS
Spread yang dihitung menggunakan Kalman filter secara signifikan lebih stasioner dan mean-reverting dibandingkan spread dari regresi bergulir. Kalman filter menggunakan semua observasi masa lalu dengan bobot yang meluruh secara eksponensial, daripada memotong data pada panjang jendela tetap. Selain itu, Kalman filter tidak memerlukan penyetelan parameter "panjang jendela" — sebagai gantinya, ia secara otomatis mengkalibrasi keseimbangan antara inersia dan adaptivitas melalui matriks Q dan R.
Implementasi dengan filterpy
import numpy as np
from filterpy.kalman import KalmanFilter
def create_kalman_filter(
delta: float = 1e-4,
obs_noise: float = 1.0
) -> KalmanFilter:
"""
Creates a Kalman filter for adaptive hedge ratio estimation.
delta: state noise variance (Q = delta * I).
Larger delta → faster adaptation, more noise.
obs_noise: observation noise variance (R).
"""
kf = KalmanFilter(dim_x=2, dim_z=1)
kf.x = np.zeros((2, 1))
kf.F = np.eye(2)
kf.P = np.eye(2) * 1000
kf.Q = np.eye(2) * delta
kf.R = np.array([[obs_noise]])
return kf
def estimate_hedge_ratio(
prices_y: np.ndarray,
prices_x: np.ndarray,
delta: float = 1e-4,
obs_noise: float = 1.0
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""
Estimates the adaptive hedge ratio using a Kalman filter.
Returns:
alphas: array of intercepts (α)
betas: array of hedge ratios (β)
spreads: array of spreads Y - α - β*X
"""
n = len(prices_y)
kf = create_kalman_filter(delta, obs_noise)
alphas = np.zeros(n)
betas = np.zeros(n)
spreads = np.zeros(n)
for t in range(n):
kf.H = np.array([[1.0, prices_x[t]]])
kf.predict()
kf.update(np.array([[prices_y[t]]]))
alphas[t] = kf.x[0, 0]
betas[t] = kf.x[1, 0]
spreads[t] = prices_y[t] - kf.x[0, 0] - kf.x[1, 0] * prices_x[t]
return alphas, betas, spreads
Parameter delta adalah kunci. Untuk pasangan kripto dengan volatilitas tinggi (memecoin, altcoin small-cap), gunakan delta = 1e-3. Untuk pasangan stabil (BTC/ETH, SOL/AVAX) — delta = 1e-5.
6. Sinyal Masuk dan Keluar
Ambang Batas Z-Score
Logika sinyal dasar:
def generate_signals(
spreads: np.ndarray,
lookback: int = 60,
entry_z: float = 2.0,
exit_z: float = 0.5,
stop_z: float = 4.0
) -> np.ndarray:
"""
Generates trading signals based on spread z-score.
Returns array: +1 (long spread), -1 (short spread), 0 (flat)
"""
signals = np.zeros(len(spreads))
position = 0
for t in range(lookback, len(spreads)):
window = spreads[t - lookback:t]
mu = np.mean(window)
sigma = np.std(window)
if sigma < 1e-10:
continue
z = (spreads[t] - mu) / sigma
if position == 0:
if z > entry_z:
position = -1 # Short spread (short Y, long X)
elif z < -entry_z:
position = 1 # Long spread (long Y, short X)
else:
if position == 1 and z > -exit_z:
position = 0
elif position == -1 and z < exit_z:
position = 0
elif abs(z) > stop_z:
position = 0
signals[t] = position
return signals
Filter Momentum
Sinyal mean-reversion murni dapat ditingkatkan dengan filter:
-
Filter momentum: jangan buka posisi jika spread terus menyimpang. Tunggu spread berbalik sebelum masuk. Secara teknis: z-score telah melewati ambang batas, tetapi perubahan spread saat ini sudah mengarah ke rata-rata.
-
Filter volatilitas: tingkatkan ambang batas masuk selama periode volatilitas tinggi. Ketika pasar panik, z-score bisa tetap di atas 3σ selama berminggu-minggu.
-
Filter kointegrasi: sebelum setiap trade, verifikasi bahwa kointegrasi masih berlaku (uji ADF bergulir). Jika p-value > 0,1 — hentikan sementara trading.
Keluar Berbasis Waktu
Jika posisi telah terbuka lebih lama dari 2× half-life dan spread belum kembali — tutup secara paksa. Jika spread tidak kembali dalam 2× waktu yang diharapkan, kointegrasi kemungkinan telah pecah, dan tidak ada yang perlu ditunggu.
7. Backtesting: Melakukannya dengan Benar
Analisis Walk-Forward
Backtest standar (latih pada semua data → uji pada semua data) tidak berguna untuk stat arb. Parameter regresi terlalu disesuaikan dengan data, dan hasilnya akan optimistis.
Pendekatan walk-forward:
- Bagi data menjadi periode: [latih₁ → uji₁] → [latih₂ → uji₂] → ...
- Pada setiap periode latih: estimasi kointegrasi, hitung hedge ratio, pilih ambang batas z-score
- Pada periode uji: trading dengan parameter tetap
- Gabungkan semua periode uji untuk evaluasi akhir
Konfigurasi umum untuk kripto: latih = 180 hari, uji = 30 hari, langkah = 30 hari.

Model Biaya Transaksi
Untuk kripto, Anda perlu memperhitungkan:
| Komponen | Nilai Umum | Komentar |
|---|---|---|
| Biaya maker | 0,02% | Order limit |
| Biaya taker | 0,05-0,075% | Order market |
| Slippage | 0,01-0,1% | Bergantung pada likuiditas |
| Funding rate | ±0,01%/8j | Untuk posisi futures |
| Spread (bid-ask) | 0,01-0,05% | Di bursa utama |
Masuk dan keluar dari posisi pairs melibatkan 4 trade (2 kaki × masuk + keluar). Total biaya: ~0,3-0,5% per round trip. Ini berarti rata-rata keuntungan per trade harus melebihi 0,5% untuk ekspektasi nilai positif.
Model Slippage
Model linear: slippage = k × (order_size / ADV), di mana ADV adalah volume harian rata-rata. Untuk kripto, k ≈ 0,1 untuk koin top-10 dan k ≈ 0,3-0,5 untuk altcoin.
Model yang lebih realistis adalah square-root impact: slippage = k × sqrt(order_size / ADV). Ini lebih mencerminkan mikrostruktur pasar nyata.
Metrik
def calculate_metrics(returns: np.ndarray, rf: float = 0.04) -> dict:
"""
Calculates key strategy metrics.
rf: risk-free rate (annual)
"""
daily_rf = rf / 365
excess = returns - daily_rf
ann_return = np.mean(returns) * 365
ann_vol = np.std(returns) * np.sqrt(365)
sharpe = (ann_return - rf) / ann_vol if ann_vol > 0 else 0
cumulative = np.cumprod(1 + returns)
running_max = np.maximum.accumulate(cumulative)
drawdowns = (cumulative - running_max) / running_max
max_dd = np.min(drawdowns)
calmar = ann_return / abs(max_dd) if max_dd != 0 else 0
win_rate = np.mean(returns > 0) if len(returns) > 0 else 0
gains = returns[returns > 0].sum()
losses = abs(returns[returns < 0].sum())
profit_factor = gains / losses if losses > 0 else float('inf')
return {
'annual_return': f'{ann_return:.1%}',
'annual_volatility': f'{ann_vol:.1%}',
'sharpe_ratio': f'{sharpe:.2f}',
'max_drawdown': f'{max_dd:.1%}',
'calmar_ratio': f'{calmar:.2f}',
'win_rate': f'{win_rate:.1%}',
'profit_factor': f'{profit_factor:.2f}',
}
Tolok ukur untuk crypto stat arb:
- Sharpe > 1,5 — strategi yang baik
- Max drawdown < 15% — risiko yang dapat diterima
- Calmar > 2,0 — rasio imbal hasil/drawdown yang sangat baik
- Profit factor > 1,5 — keunggulan yang berkelanjutan
8. Masalah Dunia Nyata
Slippage dan Likuiditas
Dalam backtest, Anda masuk secara instan pada harga mid. Dalam kenyataan — tidak. Pada altcoin dengan volume harian 50 ribu dapat menggerakkan harga sebesar 0,2-0,5%. Untuk strategi pairs, itu slippage berlipat (dua kaki), dan bisa menghabiskan semua keuntungan.
Solusi: gunakan order limit (maker, bukan taker), pecah order menjadi bagian-bagian (TWAP/VWAP), dan batasi ukuran posisi secara ketat relatif terhadap ADV (maksimum 1-2% dari volume harian).
Risiko Funding Rate
Dengan basis trading, Anda menerima funding rate, tetapi bisa berbalik negatif. Dalam pasar bearish Desember 2022, funding rate BTC adalah -0,02% setiap 8 jam — jika Anda duduk dalam posisi "long spot + short perp", Anda membayar 100 ribu posisi.
Perlindungan: pantau funding rate secara real-time dan tutup posisi ketika rate berbalik. Pendekatan yang lebih canggih adalah arbitrase funding rate antar bursa (long di bursa dengan funding rendah, short di bursa dengan funding tinggi).
Pecahnya Korelasi dalam Krisis
Maret 2020, Mei 2021, November 2022, Agustus 2024 — dalam setiap crash kripto, korelasi pecah. Lebih tepatnya, korelasi menguat (semuanya jatuh bersama), tetapi kointegrasi pecah — spread bisa melayang ke 10σ dan tidak pernah kembali.
Inilah titik lemah pairs trading. Strategi menghasilkan sedikit secara konsisten, kemudian kehilangan jumlah besar dalam satu hari. Profil klasik "memungut koin di depan mesin giling".
Perlindungan:
- Stop-loss ketat: tutup posisi ketika z-score > 4σ
- Batas leverage: maksimum 2-3x pada setiap kaki
- Filter VIX/volatilitas: kurangi ukuran posisi ketika volatilitas tersirat tinggi
- Diversifikasi: trading 10-20 pasangan secara bersamaan, jangan taruh semuanya pada satu pasangan
Persyaratan Modal
Untuk crypto stat arb yang serius:
- Basis trading: dari $50 ribu (pada satu pasangan, satu bursa)
- Arbitrase lintas bursa: dari $100 ribu (saldo di dua bursa)
- Portofolio pairs trading (10 pasangan): dari $200 ribu
- Level institusional: dari $1 juta
Dengan jumlah yang lebih kecil, komisi dan ukuran posisi minimum membuat strategi tidak layak.
9. Implementasi Python End-to-End
Pengambilan Data
import ccxt
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def fetch_ohlcv(
exchange_id: str,
symbol: str,
timeframe: str = '1h',
days: int = 365
) -> pd.DataFrame:
"""Fetch OHLCV data via ccxt."""
exchange = getattr(ccxt, exchange_id)({
'enableRateLimit': True,
})
since = int((datetime.now() - timedelta(days=days)).timestamp() * 1000)
all_candles = []
while True:
candles = exchange.fetch_ohlcv(
symbol, timeframe, since=since, limit=1000
)
if not candles:
break
all_candles.extend(candles)
since = candles[-1][0] + 1
if len(candles) < 1000:
break
df = pd.DataFrame(
all_candles,
columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
)
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
return df
sol = fetch_ohlcv('binance', 'SOL/USDT', '1h', 365)
avax = fetch_ohlcv('binance', 'AVAX/USDT', '1h', 365)
prices = pd.DataFrame({
'SOL': sol['close'],
'AVAX': avax['close']
}).dropna()
Uji Kointegrasi
from statsmodels.tsa.stattools import coint, adfuller
from statsmodels.regression.linear_model import OLS
from statsmodels.tools import add_constant
def test_cointegration(y: np.ndarray, x: np.ndarray) -> dict:
"""
Full cointegration test with diagnostics.
"""
score, pvalue, crit_values = coint(y, x)
x_const = add_constant(x)
model = OLS(y, x_const).fit()
alpha, beta = model.params
spread = y - alpha - beta * x
adf_stat, adf_pvalue, _, _, adf_crit, _ = adfuller(spread, maxlag=20)
spread_lag = spread[:-1]
spread_diff = np.diff(spread)
spread_lag_const = add_constant(spread_lag)
hl_model = OLS(spread_diff, spread_lag_const).fit()
theta = -hl_model.params[1]
half_life = np.log(2) / theta if theta > 0 else np.inf
return {
'coint_pvalue': pvalue,
'cointegrated': pvalue < 0.05,
'hedge_ratio': beta,
'intercept': alpha,
'adf_statistic': adf_stat,
'adf_pvalue': adf_pvalue,
'half_life_hours': half_life,
'half_life_days': half_life / 24,
'spread_mean': np.mean(spread),
'spread_std': np.std(spread),
}
result = test_cointegration(
prices['SOL'].values,
prices['AVAX'].values
)
print(f"Cointegration: {result['cointegrated']} "
f"(p-value: {result['coint_pvalue']:.4f})")
print(f"Hedge ratio: {result['hedge_ratio']:.4f}")
print(f"Half-life: {result['half_life_days']:.1f} days")
Kalman Filter + Backtester
from filterpy.kalman import KalmanFilter
class PairsBacktester:
"""
Walk-forward backtester for pairs trading
with Kalman filter.
"""
def __init__(
self,
prices_y: np.ndarray,
prices_x: np.ndarray,
kalman_delta: float = 1e-4,
obs_noise: float = 1.0,
entry_z: float = 2.0,
exit_z: float = 0.5,
stop_z: float = 4.0,
lookback: int = 60,
fee_rate: float = 0.001, # 0.1% round trip per leg
slippage_rate: float = 0.0005, # 0.05% slippage per leg
):
self.prices_y = prices_y
self.prices_x = prices_x
self.n = len(prices_y)
self.kalman_delta = kalman_delta
self.obs_noise = obs_noise
self.entry_z = entry_z
self.exit_z = exit_z
self.stop_z = stop_z
self.lookback = lookback
self.fee_rate = fee_rate
self.slippage_rate = slippage_rate
def run(self) -> pd.DataFrame:
"""Run the backtest. Returns a DataFrame with results."""
kf = KalmanFilter(dim_x=2, dim_z=1)
kf.x = np.zeros((2, 1))
kf.F = np.eye(2)
kf.P = np.eye(2) * 1000
kf.Q = np.eye(2) * self.kalman_delta
kf.R = np.array([[self.obs_noise]])
alphas = np.zeros(self.n)
betas = np.zeros(self.n)
spreads = np.zeros(self.n)
for t in range(self.n):
kf.H = np.array([[1.0, self.prices_x[t]]])
kf.predict()
kf.update(np.array([[self.prices_y[t]]]))
alphas[t] = kf.x[0, 0]
betas[t] = kf.x[1, 0]
spreads[t] = (
self.prices_y[t] - kf.x[0, 0]
- kf.x[1, 0] * self.prices_x[t]
)
positions = np.zeros(self.n)
z_scores = np.zeros(self.n)
position = 0
for t in range(self.lookback, self.n):
window = spreads[t - self.lookback:t]
mu = np.mean(window)
sigma = np.std(window)
if sigma < 1e-10:
continue
z = (spreads[t] - mu) / sigma
z_scores[t] = z
if position == 0:
if z > self.entry_z:
position = -1
elif z < -self.entry_z:
position = 1
else:
if position == 1 and z > -self.exit_z:
position = 0
elif position == -1 and z < self.exit_z:
position = 0
elif abs(z) > self.stop_z:
position = 0
positions[t] = position
spread_returns = np.diff(spreads) / np.abs(
spreads[:-1] + 1e-10
)
pnl = np.zeros(self.n)
for t in range(1, self.n):
if positions[t - 1] != 0:
raw_return = positions[t - 1] * spread_returns[t - 1]
pnl[t] = raw_return
if positions[t] != positions[t - 1]:
total_cost = 2 * (self.fee_rate + self.slippage_rate)
pnl[t] -= total_cost
return pd.DataFrame({
'price_y': self.prices_y,
'price_x': self.prices_x,
'alpha': alphas,
'beta': betas,
'spread': spreads,
'z_score': z_scores,
'position': positions,
'pnl': pnl,
'cumulative_pnl': np.cumsum(pnl),
})
bt = PairsBacktester(
prices_y=prices['SOL'].values,
prices_x=prices['AVAX'].values,
kalman_delta=1e-4,
entry_z=2.0,
exit_z=0.5,
stop_z=4.0,
lookback=60,
fee_rate=0.001,
slippage_rate=0.0005,
)
results = bt.run()
daily_pnl = results['pnl'].resample('D').sum() if hasattr(
results.index, 'freq'
) else results['pnl']
metrics = calculate_metrics(daily_pnl.values)
for k, v in metrics.items():
print(f'{k}: {v}')
Kerangka Live Trading
import ccxt
import asyncio
import logging
logger = logging.getLogger(__name__)
class LivePairsTrader:
"""
Minimal skeleton for live pairs trading.
For production: add retry logic, monitoring,
alerts, balance reconciliation.
"""
def __init__(
self,
exchange_id: str,
symbol_y: str,
symbol_x: str,
api_key: str,
secret: str,
position_size_usd: float = 1000.0,
entry_z: float = 2.0,
exit_z: float = 0.5,
):
self.exchange = getattr(ccxt, exchange_id)({
'apiKey': api_key,
'secret': secret,
'enableRateLimit': True,
})
self.symbol_y = symbol_y
self.symbol_x = symbol_x
self.position_size = position_size_usd
self.entry_z = entry_z
self.exit_z = exit_z
self.position = 0 # +1, -1, 0
self.kf = create_kalman_filter(delta=1e-4)
self.spread_history = []
async def update(self):
"""One update cycle."""
ticker_y = self.exchange.fetch_ticker(self.symbol_y)
ticker_x = self.exchange.fetch_ticker(self.symbol_x)
price_y = ticker_y['last']
price_x = ticker_x['last']
self.kf.H = np.array([[1.0, price_x]])
self.kf.predict()
self.kf.update(np.array([[price_y]]))
alpha = self.kf.x[0, 0]
beta = self.kf.x[1, 0]
spread = price_y - alpha - beta * price_x
self.spread_history.append(spread)
if len(self.spread_history) < 60:
logger.info(f"Warming up: {len(self.spread_history)}/60")
return
window = np.array(self.spread_history[-60:])
z = (spread - np.mean(window)) / np.std(window)
logger.info(
f"β={beta:.4f} spread={spread:.4f} z={z:.2f} "
f"pos={self.position}"
)
new_position = self.position
if self.position == 0:
if z > self.entry_z:
new_position = -1
elif z < -self.entry_z:
new_position = 1
else:
if self.position == 1 and z > -self.exit_z:
new_position = 0
elif self.position == -1 and z < self.exit_z:
new_position = 0
if new_position != self.position:
await self._execute_trade(
new_position, price_y, price_x, beta
)
self.position = new_position
async def _execute_trade(
self, target: int, price_y: float, price_x: float,
beta: float
):
"""Execute a pairs trade."""
if target == 0:
logger.info("Closing position")
elif target == 1:
size_y = self.position_size / price_y
size_x = (self.position_size * beta) / price_x
logger.info(
f"Long spread: buy {size_y:.4f} {self.symbol_y}, "
f"sell {size_x:.4f} {self.symbol_x}"
)
elif target == -1:
size_y = self.position_size / price_y
size_x = (self.position_size * beta) / price_x
logger.info(
f"Short spread: sell {size_y:.4f} {self.symbol_y}, "
f"buy {size_x:.4f} {self.symbol_x}"
)
async def run_loop(self, interval_seconds: int = 60):
"""Main loop."""
logger.info(
f"Starting live trading: "
f"{self.symbol_y}/{self.symbol_x}"
)
while True:
try:
await self.update()
except Exception as e:
logger.error(f"Error in update: {e}")
await asyncio.sleep(interval_seconds)
Sebagai Penutup
Arbitrase statistik bukan Holy Grail. Ini adalah sebuah keahlian. Antara "saya tahu apa itu kointegrasi" dan "saya memiliki strategi yang bekerja secara konsisten" terdapat jurang detail rekayasa: pemrosesan data yang tepat, backtesting walk-forward yang benar, model slippage yang realistis, pemantauan real-time.
Pasar kripto masih menawarkan lebih banyak peluang untuk stat arb daripada pasar tradisional — likuiditas terfragmentasi, infrastruktur pasar yang belum matang, dan instrumen unik seperti perpetual futures dengan funding rate menciptakan inefisiensi yang sudah lama diarbitrase hingga nol di NYSE.
Tapi jendela ini mulai menutup. Pemain institusional memasuki pasar kripto, modal arbitrase terus tumbuh (menurut estimasi, volume modal arbitrase di bursa kripto tumbuh 215% pada 2025), dan margin mengecil. Jika Anda akan melakukan stat arb di kripto — lebih baik mulai sekarang.
Semua kode dalam artikel ini tersedia sebagai titik awal. Jangan jalankan di produksi tanpa pengujian serius. Dan ingat: satu-satunya strategi yang dijamin berhasil adalah manajemen risiko.
Karya akademik utama:
- Engle, R.F. & Granger, C.W.J. (1987). "Co-Integration and Error Correction: Representation, Estimation, and Testing". Econometrica, 55(2), 251-276.
- Gatev, E., Goetzmann, W.N. & Rouwenhorst, K.G. (2006). "Pairs Trading: Performance of a Relative-Value Arbitrage Rule". The Review of Financial Studies, 19(3), 797-827.
- Vidyamurthy, G. (2004). Pairs Trading: Quantitative Methods and Analysis. Wiley.
- Avellaneda, M. & Lee, J.H. (2010). "Statistical Arbitrage in the US Equities Market". Quantitative Finance, 10(7), 761-782.
- Frontiers (2026). "Deep learning-based pairs trading: real-time forecasting of co-integrated cryptocurrency pairs". Frontiers in Applied Mathematics and Statistics.
Pustaka yang berguna:
- statsmodels — kointegrasi, ADF, OLS
- filterpy — Kalman filter
- ccxt — API terpadu untuk 100+ bursa
- arbitragelab — pustaka khusus untuk pairs trading (OU, Kalman, copulas)
Penulis
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.