← Kembali ke artikel
March 19, 2026
5 menit baca

Arbitrase Statistik dan Pairs Trading di Pasar Kripto: Dari Kointegrasi hingga Kalman Filter

Arbitrase Statistik dan Pairs Trading di Pasar Kripto: Dari Kointegrasi hingga Kalman Filter
#stat arb
#pairs trading
#kointegrasi
#kalman
#arbitrase
#algo trading
#kripto

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 vs korelasi

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:

  1. Beli aset di spot (misalnya, 1 BTC)
  2. Buka posisi short pada perpetual future (1 BTC)
  3. 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.150.BTCdiBybit:87.150. BTC di Bybit: 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:

  1. Hitung hedge ratio β (misalnya, β = 1,3)
  2. Ketika z-score > +2: short SOL, long AVAX × β
  3. Ketika z-score < -2: long SOL, short AVAX × β
  4. 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.

Kalman filter

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:

  1. 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.

  2. Filter volatilitas: tingkatkan ambang batas masuk selama periode volatilitas tinggi. Ketika pasar panik, z-score bisa tetap di atas 3σ selama berminggu-minggu.

  3. 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:

  1. Bagi data menjadi periode: [latih₁ → uji₁] → [latih₂ → uji₂] → ...
  2. Pada setiap periode latih: estimasi kointegrasi, hitung hedge ratio, pilih ambang batas z-score
  3. Pada periode uji: trading dengan parameter tetap
  4. Gabungkan semua periode uji untuk evaluasi akhir

Konfigurasi umum untuk kripto: latih = 180 hari, uji = 30 hari, langkah = 30 hari.

Backtest strategi spread

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 5juta,order5 juta, order 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 60/hariper60/hari per 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:

  1. Stop-loss ketat: tutup posisi ketika z-score > 4σ
  2. Batas leverage: maksimum 2-3x pada setiap kaki
  3. Filter VIX/volatilitas: kurangi ukuran posisi ketika volatilitas tersirat tinggi
  4. 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)
Penafian: Informasi yang disediakan dalam artikel ini hanya untuk tujuan edukasi dan informasi serta tidak merupakan nasihat keuangan, investasi, atau trading. Trading mata uang kripto mengandung risiko kerugian yang signifikan.

Penulis

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Selangkah Lebih Maju dari Pasar

Berlangganan newsletter kami untuk wawasan AI trading eksklusif, analisis pasar, dan pembaruan platform.

Kami menghormati privasi Anda. Berhenti berlangganan kapan saja.