← Kembali ke artikel
March 21, 2026
5 menit baca

Hidden Markov Models dalam Trading: Cara Mengadaptasi Strategi ke Regime Pasar

Hidden Markov Models dalam Trading: Cara Mengadaptasi Strategi ke Regime Pasar
#hmm
#market-regimes
#machine-learning
#algotrading
#adaptive-strategies
#volatility

Setiap algo trader pernah mengalami krisis eksistensial. Anda menghabiskan tiga bulan untuk sebuah strategi. Backtest menunjukkan Sharpe 2.4. Kurva ekuitas seperti karya seni. Anda menjalankan bot. Dua minggu pertama penuh euforia — strategi menghasilkan alpha. Kemudian pasar "berpindah" — dan bot momentum Anda mulai secara metodis menguras modal dalam range, membeli setiap high lokal dan menjual setiap low lokal.

Masalahnya bukan pada strategi. Masalahnya adalah pasar bukan satu sistem, melainkan beberapa sistem yang saling berpindah tanpa peringatan. Strategi momentum yang sempurna untuk tren akan menghancurkan akun dalam range. Strategi grid yang menghasilkan uang di pasar sideways akan meledak saat terjadi pergerakan searah. Mean-reversion yang stabil di pasar tenang akan terkena margin call saat black swan.

Pertanyaannya bukan "strategi mana yang lebih baik," tapi "apa regime pasar saat ini dan strategi mana yang cocok dengannya." Dan di sinilah Hidden Markov Models (HMM) masuk ke panggung — kerangka matematis yang memungkinkan Anda memformalkan intuisi ini.

Pasar Bersifat Non-Stasioner, dan Itu Bukan Bug, Melainkan Fitur

Mari mulai dengan kebenaran yang tidak menyenangkan: hampir semua model statistik dasar mengasumsikan stasioneritas data. Mean dan varians tidak berubah seiring waktu, autokorelasi konstan, distribusi stabil. Deret waktu finansial melanggar semua asumsi ini secara bersamaan.

Lihat return harian BTC selama 5 tahun terakhir. Return harian rata-rata selama rally bull 2024 sekitar +0,3%, dengan deviasi standar ~2,5%. Di pasar bear 2022 — rata-rata -0,15%, deviasi standar ~4%. Di pasar sideways musim panas 2023 — rata-rata ~0%, deviasi standar ~1,5%. Ini adalah tiga regime statistik yang berbeda secara fundamental dengan distribusi yang berbeda.

Secara formal: misalkan rtr_t adalah return pada waktu tt. Dalam dunia stasioner, rtN(μ,σ2)r_t \sim \mathcal{N}(\mu, \sigma^2) dengan parameter konstan. Dalam kenyataan, parameter itu sendiri adalah proses acak: rtN(μSt,σSt2)r_t \sim \mathcal{N}(\mu_{S_t}, \sigma^2_{S_t}), di mana StS_t adalah state tersembunyi (regime pasar), yang berpindah di antara sejumlah nilai terbatas.

Ide ini diformalisasi pada tahun 1989 oleh James Hamilton dalam makalah fondationalnya "A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle." Ia menunjukkan bahwa siklus bisnis dapat dimodelkan sebagai perpindahan antara dua state tersembunyi — resesi dan ekspansi — menggunakan mekanisme Markov. Sejak saat itu, model Hamilton menjadi salah satu alat yang paling banyak dikutip dalam ekonometrika.

Tiga regime pasar Tiga regime pasar — bull (hijau), bear (merah), dan sideways (kuning) — secara visual jelas dalam retrospeksi, tetapi mendeteksi perpindahan secara real-time jauh lebih sulit.

HMM: Intuisi Melalui Analogi

Sebelum menyelami formula, mari bangun intuisi terlebih dahulu.

Rantai Markov: Tanpa Memori

Rantai Markov adalah proses acak di mana masa depan hanya bergantung pada masa kini, bukan masa lalu. Cuaca besok bergantung pada cuaca hari ini, tetapi tidak pada cuaca seminggu yang lalu (penyederhanaan yang kuat, tapi berfungsi sebagai model).

Regime pasar berperilaku serupa. Jika hari ini pasar berada di regime bull, kemungkinan tetap di sana besok tinggi (misalnya, 95%). Kemungkinan transisi ke bearish rendah (3%). Ke sideways — bahkan lebih rendah (2%). Inilah matriks probabilitas transisi.

         Bull    Bear    Sideways
Bull    [0.95    0.03    0.02  ]
Bear    [0.04    0.93    0.03  ]
Sideways[0.05    0.05    0.90  ]

Perhatikan: elemen diagonal tinggi — regime bersifat "lengket." Pasar tidak melompat dari bull ke bear setiap hari. Ia tetap dalam satu regime selama berminggu-minggu dan berbulan-bulan sebelum berpindah. Durasi yang diharapkan dari sebuah regime adalah di=11aiid_i = \frac{1}{1 - a_{ii}}. Untuk regime bull dengan a11=0.95a_{11} = 0.95, itu adalah 20 hari. Untuk regime bear dengan a22=0.93a_{22} = 0.93 — sekitar 14 hari.

State Tersembunyi: Kita Hanya Melihat Bayangannya

Kata kuncinya adalah "tersembunyi." Kita tidak mengamati regime pasar secara langsung. Tidak ada tanda yang berbunyi "Perhatian, beralih ke regime bear." Kita hanya melihat observasi — return, volatilitas, volume. Regime adalah variabel laten yang harus disimpulkan dari observasi.

Ini seperti berada di dalam ruangan tanpa jendela dan mencoba menentukan cuaca dari cara orang yang masuk dari luar berpakaian. Payung? Kemungkinan hujan. Celana pendek dan kacamata hitam? Cerah. Tapi satu orang bercelana pendek bukan berarti pasti cerah — mungkin mereka hanya optimis. Anda perlu mengakumulasi observasi dan memperkirakan state tersembunyi secara probabilistik.

Dalam HMM, setiap regime tersembunyi "mengeluarkan" observasi dari distribusinya sendiri:

  • Regime bull → return dari N(μbull,σbull2)\mathcal{N}(\mu_{bull}, \sigma^2_{bull}), di mana μbull>0\mu_{bull} > 0, σbull\sigma_{bull} moderat
  • Regime bear → return dari N(μbear,σbear2)\mathcal{N}(\mu_{bear}, \sigma^2_{bear}), di mana μbear<0\mu_{bear} < 0, σbear\sigma_{bear} tinggi
  • Sideways → return dari N(μsideways,σsideways2)\mathcal{N}(\mu_{sideways}, \sigma^2_{sideways}), di mana μsideways0\mu_{sideways} \approx 0, σsideways\sigma_{sideways} rendah

Perhatikan pola karakteristik: regime bear biasanya tidak hanya memiliki mean negatif, tetapi juga volatilitas yang meningkat. Pasar turun dengan lift dan naik dengan tangga — dan HMM menangkap ini secara otomatis.

Arsitektur HMM Arsitektur Hidden Markov Model: state tersembunyi (regime) berpindah sesuai rantai Markov, setiap state menghasilkan return yang dapat diamati dari distribusi Gaussian-nya sendiri.

Tiga Algoritma HMM: Forward, Viterbi, Baum-Welch

Semua pekerjaan dengan HMM berujung pada tiga masalah fundamental, masing-masing dengan algoritmanya sendiri.

Masalah 1: Berapa Probabilitas Observasi Ini? (Algoritma Forward)

Pertanyaan: Diberikan urutan return, berapa probabilitas mengamati urutan persis ini dengan parameter model yang diberikan?

Mengapa: Perbandingan model (AIC/BIC), pengecekan kecukupan.

Cara kerjanya: Algoritma Forward adalah pemrograman dinamis. Pada setiap langkah tt, kita menghitung "variabel forward" αt(i)\alpha_t(i) — probabilitas mengamati urutan o1,o2,,oto_1, o_2, \ldots, o_t dan berada di state ii pada waktu tt.

Rekursi: αt(j)=[iαt1(i)aij]bj(ot)\alpha_t(j) = \left[\sum_i \alpha_{t-1}(i) \cdot a_{ij}\right] \cdot b_j(o_t)

Di mana aija_{ij} adalah probabilitas transisi dari state ii ke jj, dan bj(ot)b_j(o_t) adalah probabilitas observasi oto_t di state jj. Dengan kata-kata: kita menjumlahkan semua jalur yang memungkinkan kita tiba di state jj, dan mengalikan dengan probabilitas observasi.

Kompleksitas: O(N2T)O(N^2 T) alih-alih O(NT)O(N^T) yang naif, di mana NN adalah jumlah state, TT adalah panjang urutan. Untuk 3 regime dan 1000 observasi, itu adalah 9000 operasi alih-alih 310003^{1000}. Perbedaannya, bisa dikatakan, sangat besar.

Masalah 2: Apa Urutan Regime yang Paling Mungkin? (Algoritma Viterbi)

Pertanyaan: Diberikan urutan return, urutan state tersembunyi (regime) mana yang paling mungkin menghasilkannya?

Mengapa: Inilah yang kita butuhkan untuk trading — menentukan regime pada setiap titik waktu.

Cara kerjanya: Algoritma Viterbi sama dengan Forward, tetapi alih-alih menjumlahkan semua jalur, ia mengambil maksimum. Kita mencari bukan probabilitas semua jalur yang mungkin, tetapi jalur yang paling mungkin.

δt(j)=maxi[δt1(i)aij]bj(ot)\delta_t(j) = \max_i \left[\delta_{t-1}(i) \cdot a_{ij}\right] \cdot b_j(o_t)

Ditambah backward pass (backtracking) untuk memulihkan urutan state itu sendiri. Hasilnya adalah urutan regime yang didekode: "bull-bull-bull-bear-bear-sideways-..."

Dalam praktik, untuk trading, yang lebih umum digunakan bukan Viterbi (optimum global) tetapi filtering — probabilitas state posterior pada setiap momen: P(St=io1,,ot)P(S_t = i \mid o_1, \ldots, o_t). Ini memungkinkan bekerja secara online tanpa menunggu seluruh urutan, dan memperoleh estimasi "lunak" seperti "70% bull, 25% sideways, 5% bear."

Masalah 3: Bagaimana Melatih Model? (Algoritma Baum-Welch)

Pertanyaan: Diberikan hanya observasi, parameter model (AA, BB, π\pi) mana yang memaksimalkan kemungkinan data?

Mengapa: Melatih model pada data historis.

Cara kerjanya: Algoritma Baum-Welch adalah kasus khusus dari algoritma EM (Expectation-Maximization):

  1. E-step: Menggunakan parameter saat ini, hitung state tersembunyi yang diharapkan (melalui Forward-Backward)
  2. M-step: Perbarui parameter dengan memaksimalkan kemungkinan berdasarkan state yang diharapkan ini
  3. Ulangi sampai konvergen

Nuansa penting: EM menjamin konvergensi hanya ke maksimum lokal. Kondisi awal yang berbeda dapat menghasilkan hasil yang berbeda. Dalam praktik, model dilatih beberapa kali dengan inisialisasi berbeda, dan hasil terbaik dipilih berdasarkan log-likelihood. Di hmmlearn, ini dilakukan secara otomatis melalui parameter n_init.

Regime Pasar Crypto: Apa yang Kita Cari

Untuk cryptocurrency, pembagian tiga regime klasik bekerja sangat baik karena fase pasar yang menonjol.

Regime 1: Bull

  • Return rata-rata: +0,15% ... +0,5% per hari
  • Volatilitas (std): 2-3% per hari
  • Karakter: pertumbuhan berkelanjutan dengan pullback moderat
  • Durasi: 2-6 bulan terus-menerus
  • Volume: meningkat, terutama di pasar spot
  • On-chain: MVRV > 1,5, pertumbuhan alamat aktif

Regime 2: Bear

  • Return rata-rata: -0,1% ... -0,4% per hari
  • Volatilitas (std): 3-6% per hari
  • Karakter: crash tajam, kaskade likuidasi, dead cat bounce
  • Durasi: 1-4 bulan (biasanya lebih pendek dari bull)
  • Volume: lonjakan saat panic selling, kemudian memudar
  • On-chain: MVRV < 1, inflow ke exchange meningkat

Regime 3: Sideways (akumulasi)

  • Return rata-rata: ~0% per hari
  • Volatilitas (std): 1-2% per hari
  • Karakter: pergerakan dalam range, false breakout
  • Durasi: 1-3 bulan
  • Volume: rendah, menurun
  • On-chain: metrik stabil, aktivitas menurun

Mengapa tepat tiga regime dan bukan dua atau lima? Dua terlalu kasar — Anda kehilangan informasi tentang fase sideways (dan untuk bot market-making, ini adalah regime yang paling menguntungkan). Lima atau lebih — model menjadi overfit, probabilitas transisi tidak stabil, interpretasi sulit. Tiga adalah keseimbangan optimal, dikonfirmasi oleh kriteria informasi (AIC/BIC) maupun intuisi ekonomi.

Meskipun demikian, jumlah state adalah hyperparameter dan harus diuji. Guidolin & Timmermann (2007) dalam makalah mereka "Asset Allocation under Multivariate Regime Switching" menemukan empat regime untuk portofolio campuran saham-dan-obligasi: crash, pertumbuhan lambat, bull, dan pemulihan.

Feature Engineering: Apa yang Diberikan ke Model

Opsi paling sederhana adalah hanya memasukkan return harian. Ini berhasil, tetapi bisa ditingkatkan. Berikut adalah set fitur yang telah terbukti baik dalam praktik:

Fitur Harga

  • Log return harian: rt=ln(Pt/Pt1)r_t = \ln(P_t / P_{t-1})
  • Volatilitas rolling: σt=std(rtw,,rt)\sigma_t = \text{std}(r_{t-w}, \ldots, r_t) dalam window ww (misalnya, 20 hari)
  • Return rata-rata rolling: rˉt=mean(rtw,,rt)\bar{r}_t = \text{mean}(r_{t-w}, \ldots, r_t)

Fitur Volume

  • Volume yang dinormalisasi: Vtnorm=Vt/SMA(V,20)V_t^{norm} = V_t / \text{SMA}(V, 20)
  • Korelasi volume-harga: korelasi antara volume dan return absolut dalam rolling window

Fitur On-Chain (untuk cryptocurrency)

  • MVRV Ratio: kapitalisasi pasar terhadap kapitalisasi yang direalisasikan. MVRV > 2 — pasar terlalu panas, < 1 — undervalued
  • NVT Ratio: nilai jaringan terhadap volume transaksi. Setara blockchain dari P/E
  • Exchange Net Flow: net flow ke exchange. Positif — tekanan jual, negatif — akumulasi
  • Active Addresses: jumlah alamat aktif (pertumbuhan = minat, penurunan = apatis)
import numpy as np
import pandas as pd

def prepare_features(df: pd.DataFrame, window: int = 20) -> pd.DataFrame:
    """
    Prepare features for HMM.
    df must contain columns: close, volume
    """
    features = pd.DataFrame(index=df.index)

    features['log_return'] = np.log(df['close'] / df['close'].shift(1))

    features['rolling_vol'] = features['log_return'].rolling(window).std()

    features['norm_volume'] = df['volume'] / df['volume'].rolling(window).mean()

    features['rolling_mean_return'] = features['log_return'].rolling(window).mean()

    features['abs_return'] = features['log_return'].abs()

    return features.dropna()

Penting: semua fitur harus stasioner (atau setidaknya kuasi-stasioner). Log return bersifat stasioner. Harga tidak. Volume lebih baik dinormalisasi. Volatilitas bisa dibiarkan apa adanya — itu juga kuasi-stasioner.

Nuansa lain: HMM multivariat (ketika vektor fitur dimasukkan sebagai input) bekerja lebih baik dari univariat, tetapi membutuhkan lebih banyak data untuk pelatihan. Untuk crypto dengan riwayat 5+ tahun, ini biasanya bukan masalah. Untuk altcoin baru dengan riwayat 3 bulan — lebih baik tetap gunakan satu atau dua fitur.

Implementasi Langkah demi Langkah di Python dengan hmmlearn

Mari masuk ke kode. Library hmmlearn adalah standar de facto untuk HMM di Python. API sederhana, kompatibel dengan scikit-learn, langsung bisa digunakan.

Langkah 1: Memuat Data

import ccxt
import pandas as pd
import numpy as np
from datetime import datetime

def fetch_ohlcv(symbol='BTC/USDT', timeframe='1d', since='2020-01-01'):
    """Load data via CCXT."""
    exchange = ccxt.binance()
    since_ts = exchange.parse8601(f'{since}T00:00:00Z')
    all_ohlcv = []

    while True:
        ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=since_ts, limit=1000)
        if not ohlcv:
            break
        all_ohlcv.extend(ohlcv)
        since_ts = ohlcv[-1][0] + 1
        if len(ohlcv) < 1000:
            break

    df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    df.set_index('timestamp', inplace=True)
    return df

df = fetch_ohlcv('BTC/USDT', '1d', '2020-01-01')
print(f"Loaded {len(df)} daily candles")
print(f"Period: {df.index[0]}{df.index[-1]}")

Langkah 2: Persiapan Fitur dan Pelatihan HMM

from hmmlearn.hmm import GaussianHMM
from sklearn.preprocessing import StandardScaler

features = prepare_features(df, window=20)

feature_cols = ['log_return', 'rolling_vol', 'norm_volume']
X = features[feature_cols].values

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

model = GaussianHMM(
    n_components=3,          # 3 regimes
    covariance_type='full',  # full covariance matrix
    n_iter=200,              # max EM iterations
    random_state=42,
    tol=1e-4,                # convergence threshold
    verbose=False
)

model.fit(X_scaled)

print(f"Model converged: {model.monitor_.converged}")
print(f"Iterations: {model.monitor_.iter}")
print(f"Log-likelihood: {model.score(X_scaled):.2f}")

Langkah 3: Dekoding Regime

hidden_states = model.predict(X_scaled)

state_probs = model.predict_proba(X_scaled)

features['regime'] = hidden_states
features['prob_state_0'] = state_probs[:, 0]
features['prob_state_1'] = state_probs[:, 1]
features['prob_state_2'] = state_probs[:, 2]

print(f"\nDistribution across regimes:")
print(features['regime'].value_counts().sort_index())

Langkah 4: Interpretasi Regime

Di sinilah hal menjadi menarik — dan rumit. HMM tidak tahu bahwa regime 0 adalah "bull." Ia hanya menemukan tiga cluster dalam ruang observasi. Penomoran bersifat arbitrer dan dapat berubah dari satu run ke run lainnya.

Anda perlu melihat statistik setiap regime dan menetapkan label secara manual:

def interpret_regimes(features, model, scaler, feature_cols):
    """
    Regime interpretation: assign bull/bear/sideways labels
    based on mean returns and volatility.
    """
    means_scaled = model.means_
    means_original = scaler.inverse_transform(means_scaled)

    regime_stats = {}
    for i in range(model.n_components):
        mask = features['regime'] == i
        regime_stats[i] = {
            'count': mask.sum(),
            'pct': mask.mean() * 100,
            'mean_return': features.loc[mask, 'log_return'].mean() * 100,
            'std_return': features.loc[mask, 'log_return'].std() * 100,
            'mean_vol': features.loc[mask, 'rolling_vol'].mean() * 100,
            'sharpe_daily': (features.loc[mask, 'log_return'].mean()
                           / features.loc[mask, 'log_return'].std())
        }
        print(f"\nRegime {i}: {regime_stats[i]['count']} days "
              f"({regime_stats[i]['pct']:.1f}%)")
        print(f"  Mean return:    {regime_stats[i]['mean_return']:.3f}%/day")
        print(f"  Volatility:     {regime_stats[i]['std_return']:.3f}%/day")
        print(f"  Sharpe (daily): {regime_stats[i]['sharpe_daily']:.3f}")

    sorted_by_return = sorted(regime_stats.keys(),
                               key=lambda x: regime_stats[x]['mean_return'])

    label_map = {
        sorted_by_return[0]: 'bear',      # lowest return
        sorted_by_return[2]: 'bull',       # highest return
        sorted_by_return[1]: 'sideways',   # middle
    }

    features['regime_label'] = features['regime'].map(label_map)
    return features, label_map

features, label_map = interpret_regimes(features, model, scaler, feature_cols)
print(f"\nRegime mapping: {label_map}")

Output tipikal untuk BTC kurang lebih seperti ini:

Regime 0: 412 days (23.8%)
  Mean return:    -0.182%/day
  Volatility:     4.127%/day
  Sharpe (daily): -0.044

Regime 1: 847 days (48.9%)
  Mean return:    0.021%/day
  Volatility:     1.634%/day
  Sharpe (daily): 0.013

Regime 2: 473 days (27.3%)
  Mean return:    0.312%/day
  Volatility:     2.851%/day
  Sharpe (daily): 0.109

Regime mapping: {0: 'bear', 1: 'sideways', 2: 'bull'}

Perhatikan: regime bear tidak hanya memiliki return negatif, tetapi juga volatilitas tertinggi (4,1% vs. 1,6% di sideways). Ini adalah observasi empiris klasik yang dikenal sebagai "efek leverage" — pasar yang jatuh lebih volatil daripada yang naik.

Matriks Transisi dan Durasi Regime

Matriks probabilitas transisi adalah salah satu artefak HMM yang paling informatif:

def analyze_transitions(model, label_map):
    """Analyze transition matrix and expected durations."""
    trans_mat = model.transmat_

    inv_map = {v: k for k, v in label_map.items()}
    order = [inv_map['bull'], inv_map['bear'], inv_map['sideways']]
    labels = ['bull', 'bear', 'sideways']

    print("Transition probability matrix:")
    print(f"{'':>10}", end='')
    for l in labels:
        print(f"{l:>10}", end='')
    print()

    for i, li in enumerate(labels):
        print(f"{li:>10}", end='')
        for j, lj in enumerate(labels):
            print(f"{trans_mat[order[i], order[j]]:>10.3f}", end='')
        print()

    print("\nExpected regime durations (days):")
    for i, l in enumerate(labels):
        duration = 1 / (1 - trans_mat[order[i], order[i]])
        print(f"  {l}: {duration:.1f} days")

analyze_transitions(model, label_map)

Hasil tipikal:

Transition probability matrix:
               bull      bear  sideways
      bull    0.952     0.018     0.030
      bear    0.031     0.937     0.032
   sideways   0.043     0.027     0.930

Expected regime durations (days):
  bull: 20.8 days
  bear: 15.9 days
  sideways: 14.3 days

Yang kita amati:

  1. Regime bersifat lengket: probabilitas tetap di regime saat ini > 93% untuk semua state
  2. Regime bull berlangsung lebih lama dari bear (20,8 vs 15,9 hari) — sekali lagi, pasar naik lebih lambat dari turunnya
  3. Transisi langsung dari bull ke bear tidak mungkin terjadi (1,8%) — biasanya pasar melewati fase sideways terlebih dahulu

Poin terakhir secara ekonomi intuitif: pasar jarang berbalik seketika. Biasanya ada fase distribusi (sideways di puncak) sebelum pasar bear, dan fase akumulasi (sideways di bawah) sebelum pasar bull.

Strategi Trading: Satu Regime — Satu Strategi

Sekarang kita terapkan apa yang telah kita pelajari. Idenya: jangan trading satu strategi sepanjang waktu, tetapi beralih antar strategi tergantung pada regime yang terdeteksi.

Bull → Momentum Agresif

  • Ukuran posisi diperbesar (hingga 100% dari modal)
  • Strategi tren: breakout, mengikuti moving average
  • Stop-loss lebar (jangan terkena stop saat pullback)
  • Jangan short (atau short secara minimal)

Bear → Defensif / Posisi Short

  • Ukuran posisi dikurangi (30-50% dari modal)
  • Strategi short atau full cash
  • Stop-loss ketat
  • Hedging melalui put option atau futures

Sideways → Mean-Reversion / Grid

  • Ukuran posisi menengah (50-70% dari modal)
  • Strategi grid trading
  • Mean-reversion: beli di batas bawah, jual di batas atas
  • Market-making dengan spread ketat
def regime_adaptive_strategy(features, initial_capital=10000):
    """
    Simple regime-adaptive strategy.
    Bull: long 100%, Bear: short 50%, Sideways: long 30%.
    """
    capital = initial_capital
    position = 0  # 1 = long, -1 = short, 0 = no position
    equity = [capital]
    positions = []

    for i in range(1, len(features)):
        regime = features.iloc[i]['regime_label']
        ret = features.iloc[i]['log_return']

        if regime == 'bull':
            target_exposure = 1.0   # 100% long
        elif regime == 'bear':
            target_exposure = -0.5  # 50% short
        elif regime == 'sideways':
            target_exposure = 0.3   # 30% long (or grid)
        else:
            target_exposure = 0.0

        daily_pnl = capital * target_exposure * ret

        capital += daily_pnl
        equity.append(capital)
        positions.append(target_exposure)

    features = features.copy()
    features['equity'] = equity
    features['position'] = [0] + positions

    return features

Backtest: Strategi HMM-Adaptive vs Buy-and-Hold

Sekarang pertanyaan utama: apakah ini lebih baik dari Buy-and-Hold biasa?

def run_backtest(features, initial_capital=10000):
    """Comparative backtest: Buy-and-Hold vs HMM-Adaptive."""

    cumulative_returns = (1 + features['log_return']).cumprod()
    bnh_equity = initial_capital * cumulative_returns

    features = regime_adaptive_strategy(features, initial_capital)

    def calc_metrics(equity_series):
        returns = pd.Series(equity_series).pct_change().dropna()
        total_return = (equity_series.iloc[-1] / equity_series.iloc[0] - 1) * 100
        annual_return = ((1 + total_return / 100) ** (365 / len(returns)) - 1) * 100
        sharpe = returns.mean() / returns.std() * np.sqrt(365)
        max_dd = ((equity_series / equity_series.cummax()) - 1).min() * 100
        return {
            'Total Return (%)': total_return,
            'Annual Return (%)': annual_return,
            'Sharpe Ratio': sharpe,
            'Max Drawdown (%)': max_dd
        }

    bnh_metrics = calc_metrics(bnh_equity)
    hmm_metrics = calc_metrics(features['equity'])

    print(f"{'Metric':<25} {'Buy&Hold':>12} {'HMM-Adaptive':>14}")
    print("-" * 53)
    for key in bnh_metrics:
        print(f"{key:<25} {bnh_metrics[key]:>12.2f} {hmm_metrics[key]:>14.2f}")

    return features, bnh_equity

features, bnh_equity = run_backtest(features)

Hasil backtest Perbandingan kurva ekuitas: Buy-and-Hold (biru) dan strategi HMM-adaptive (oranye). Strategi adaptif secara signifikan mengurangi drawdown selama fase bear.

Hasil tipikal untuk BTC (2020-2025):

Metric                     Buy&Hold   HMM-Adaptive
-----------------------------------------------------
Total Return (%)             487.32         623.18
Annual Return (%)             42.71          49.84
Sharpe Ratio                   1.12           1.68
Max Drawdown (%)             -76.42         -38.17

Observasi utama: strategi HMM-adaptive tidak harus memberikan total return yang lebih tinggi (meskipun dalam kasus ini memang demikian), tetapi secara dramatis mengurangi drawdown maksimum — dari 76% menjadi 38%. Sharpe naik dari 1,12 menjadi 1,68. Ini adalah peningkatan dalam return yang disesuaikan risiko, bukan sekadar "lebih banyak uang."

Mengapa? Karena di regime bear, strategi beralih ke mode defensif atau short, menghindari crash besar. Biayanya adalah keterlambatan masuk ke tren (model mendeteksi regime bull dengan lag beberapa hari) dan perpindahan palsu selama periode transisi.

Visualisasi Hasil

import matplotlib.pyplot as plt
import matplotlib.dates as mdates

fig, axes = plt.subplots(3, 1, figsize=(14, 10), sharex=True)

axes[0].plot(features.index, bnh_equity, label='Buy & Hold', alpha=0.8)
axes[0].plot(features.index, features['equity'], label='HMM-Adaptive', alpha=0.8)
axes[0].set_ylabel('Capital ($)')
axes[0].legend()
axes[0].set_title('Equity Curve: Buy & Hold vs HMM-Adaptive')

colors = {'bull': '#2ecc71', 'bear': '#e74c3c', 'sideways': '#f39c12'}
for regime in ['bull', 'bear', 'sideways']:
    mask = features['regime_label'] == regime
    axes[1].scatter(features.index[mask], df.loc[features.index[mask], 'close'],
                    c=colors[regime], s=2, label=regime, alpha=0.7)
axes[1].set_ylabel('BTC Price ($)')
axes[1].set_yscale('log')
axes[1].legend()
axes[1].set_title('BTC Price Colored by Regime')

for i, (regime, color) in enumerate(colors.items()):
    inv_map = {v: k for k, v in label_map.items()}
    state_idx = inv_map[regime]
    axes[2].fill_between(features.index,
                          features[f'prob_state_{state_idx}'],
                          alpha=0.4, color=color, label=regime)
axes[2].set_ylabel('Regime Probability')
axes[2].legend()
axes[2].set_title('Posterior Regime Probabilities')

plt.tight_layout()
plt.savefig('hmm_backtest.png', dpi=150)
plt.show()

Teknik Lanjutan

HMM dasar adalah titik awal yang baik, tetapi jauh dari batas kemampuan.

HMM Hierarkis

Dalam HMM hierarkis, level atas menentukan "macro-regime" (tren global, siklus tahunan), dan level bawah menentukan "micro-regime" (fluktuasi intra-minggu/intra-bulan). Paket fHMM untuk R, yang diterbitkan di Journal of Statistical Software pada tahun 2024 (Oelschlager, Adam, Michels), mengimplementasikan ide ini persis untuk deret waktu finansial.

Contoh: macro-regime "siklus bull" mengandung di dalamnya micro-regime "rally," "koreksi," dan "konsolidasi." Ini mencegah kepanikan di setiap pullback 10% di pasar bull — model memahami bahwa koreksi dalam siklus bull adalah normal.

HMM Multivariat dengan Fitur yang Diperluas

Alih-alih return univariat, kita memasukkan vektor fitur: return + volatilitas + volume + data on-chain. Ini memungkinkan model "melihat" lebih banyak informasi tentang state pasar.

from hmmlearn.hmm import GaussianHMM

extended_features = ['log_return', 'rolling_vol', 'norm_volume',
                     'rolling_mean_return', 'abs_return']

X_extended = features[extended_features].values
scaler_ext = StandardScaler()
X_ext_scaled = scaler_ext.fit_transform(X_extended)

model_mv = GaussianHMM(
    n_components=3,
    covariance_type='full',     # full covariance matrix
    n_iter=300,
    random_state=42,
    init_params='stmc',         # initialize all parameters
    verbose=False
)
model_mv.fit(X_ext_scaled)

n_params_base = 3 * (3 + 3 + 3*4/2) + 3*2    # simplified estimate
n_params_ext = 3 * (5 + 5 + 5*6/2) + 3*2

bic_base = -2 * model.score(X_scaled) * len(X_scaled) + n_params_base * np.log(len(X_scaled))
bic_ext = -2 * model_mv.score(X_ext_scaled) * len(X_ext_scaled) + n_params_ext * np.log(len(X_ext_scaled))

print(f"BIC base model:     {bic_base:.0f}")
print(f"BIC extended model: {bic_ext:.0f}")
print(f"Extended is better: {bic_ext < bic_base}")

HMM + ML Ensemble

Pendekatan modern: gunakan HMM bukan sebagai sistem trading, tetapi sebagai generator fitur untuk model downstream. Ide ini, yang dijelaskan dalam Gupta et al. (2025) "A forest of opinions: A multi-model ensemble-HMM voting framework for market regime shift detection and trading":

  1. HMM menentukan regime saat ini (atau probabilitas regime)
  2. Regime dimasukkan sebagai fitur tambahan ke Random Forest / Gradient Boosting
  3. Model ML membuat keputusan trading spesifik dengan mempertimbangkan regime
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import TimeSeriesSplit

features['regime_0_prob'] = state_probs[:, 0]
features['regime_1_prob'] = state_probs[:, 1]
features['regime_2_prob'] = state_probs[:, 2]

features['target'] = (features['log_return'].shift(-1) > 0).astype(int)

ml_features = ['log_return', 'rolling_vol', 'norm_volume',
               'regime_0_prob', 'regime_1_prob', 'regime_2_prob']

X_ml = features[ml_features].dropna()
y_ml = features.loc[X_ml.index, 'target'].dropna()

common_idx = X_ml.index.intersection(y_ml.index)
X_ml = X_ml.loc[common_idx]
y_ml = y_ml.loc[common_idx]

tscv = TimeSeriesSplit(n_splits=5)
scores = []

for train_idx, test_idx in tscv.split(X_ml):
    X_train, X_test = X_ml.iloc[train_idx], X_ml.iloc[test_idx]
    y_train, y_test = y_ml.iloc[train_idx], y_ml.iloc[test_idx]

    clf = GradientBoostingClassifier(n_estimators=100, max_depth=3, random_state=42)
    clf.fit(X_train, y_train)
    score = clf.score(X_test, y_test)
    scores.append(score)

print(f"Walk-Forward Accuracy: {np.mean(scores):.3f} +/- {np.std(scores):.3f}")

Produksi: Jebakan-Jebakan

Backtest yang indah hanya setengah dari pertempuran. Dalam produksi, beberapa kejutan tidak menyenangkan menunggu.

Masalah Lag (Look-Ahead Bias)

HMM menentukan regime berdasarkan data saat ini dan masa lalu, tetapi dalam backtest ada godaan untuk melatih model pada seluruh dataset, termasuk data masa depan. Ini adalah look-ahead bias, dan ini mengubah backtest menjadi fiksi.

Solusi: Pendekatan Walk-Forward. Latih model pada data hingga waktu tt, prediksi regime pada waktu tt, kemudian geser window. Persis seperti yang dijelaskan dalam artikel kami tentang Walk-Forward Optimization.

def walk_forward_hmm(features, feature_cols, train_window=252, retrain_freq=21):
    """
    Walk-Forward HMM: train on a rolling window,
    predict on the next retrain_freq days.
    """
    regimes_wf = pd.Series(index=features.index, dtype=float)

    for start in range(train_window, len(features), retrain_freq):
        train_data = features.iloc[start - train_window:start]
        X_train = train_data[feature_cols].values

        scaler = StandardScaler()
        X_train_scaled = scaler.fit_transform(X_train)

        model = GaussianHMM(n_components=3, covariance_type='full',
                            n_iter=100, random_state=42)
        try:
            model.fit(X_train_scaled)
        except Exception:
            continue

        end = min(start + retrain_freq, len(features))
        test_data = features.iloc[start:end]
        X_test = test_data[feature_cols].values
        X_test_scaled = scaler.transform(X_test)

        predicted = model.predict(X_test_scaled)
        regimes_wf.iloc[start:end] = predicted

    return regimes_wf

Jadwal Pelatihan Ulang

Seberapa sering Anda harus melatih ulang model? Terlalu jarang — model menjadi usang, pasar berubah. Terlalu sering — model menjadi tidak stabil, regime "melompat."

Rekomendasi empiris:

  • Untuk data harian: latih ulang setiap 1-4 minggu (21 hari trading adalah default yang baik)
  • Window pelatihan: 6-12 bulan (252 hari trading — satu tahun)
  • Monitoring: jika log-likelihood pada data baru turun di bawah ambang batas — pelatihan ulang tidak terjadwal

Ketidakstabilan Label

Dengan setiap pelatihan ulang, penomoran state dapat berubah: apa yang sebelumnya "regime 0" (bull) dapat menjadi "regime 2." Anda perlu mencocokkan state secara otomatis berdasarkan statistiknya (mean return, volatilitas).

Pembaruan Online

Untuk trading real-time, pelatihan ulang penuh harian berlebihan. Anda dapat menggunakan filtering Forward: tetapkan parameter model, tetapi perbarui probabilitas state posterior dengan setiap observasi baru. Ini adalah operasi instan.

def online_regime_update(model, scaler, new_observation, prev_state_probs):
    """
    Online update of regime probabilities
    without retraining the entire model.
    """
    obs_scaled = scaler.transform(new_observation.reshape(1, -1))

    from scipy.stats import multivariate_normal
    emission_probs = np.array([
        multivariate_normal.pdf(obs_scaled[0],
                                 mean=model.means_[i],
                                 cov=model.covars_[i])
        for i in range(model.n_components)
    ])

    transition = model.transmat_.T  # transpose for column-to-row
    predicted = transition @ prev_state_probs
    updated = emission_probs * predicted
    updated /= updated.sum()  # normalization

    return updated

Memilih Jumlah State

Meskipun tiga regime adalah default yang baik, alternatif harus diuji:

from hmmlearn.hmm import GaussianHMM

def select_n_components(X_scaled, max_components=6):
    """Select optimal number of states by BIC."""
    results = []
    for n in range(2, max_components + 1):
        model = GaussianHMM(n_components=n, covariance_type='full',
                            n_iter=200, random_state=42)
        model.fit(X_scaled)

        log_likelihood = model.score(X_scaled) * len(X_scaled)
        n_features = X_scaled.shape[1]
        n_params = (n * (n - 1)
                   + n * n_features
                   + n * n_features * (n_features + 1) / 2
                   + (n - 1))
        bic = -2 * log_likelihood + n_params * np.log(len(X_scaled))

        results.append({'n_components': n, 'BIC': bic,
                        'log_likelihood': log_likelihood})
        print(f"n={n}: BIC={bic:.0f}, LL={log_likelihood:.0f}")

    best = min(results, key=lambda x: x['BIC'])
    print(f"\nOptimal number of states by BIC: {best['n_components']}")
    return results

results = select_n_components(X_scaled)

Keterbatasan dan Peringatan

Tidak jujur untuk diam tentang masalah-masalah ini.

Asumsi Gaussian. GaussianHMM dasar mengasumsikan bahwa return dalam setiap regime terdistribusi normal. Distribusi nyata memiliki ekor tebal dan asimetri. Solusi parsial adalah menggunakan distribusi Student-t atau GMMHMM (Gaussian Mixture per state).

Jumlah state adalah pilihan Anda. BIC membantu, tetapi tidak selalu konklusif. Dua peneliti yang berbeda dapat sampai pada jumlah regime yang berbeda dan keduanya "benar."

Periode transisional. Model tidak pasti selama pergantian regime. Probabilitas terdistribusi hampir sama rata, dan strategi menerima sinyal yang "kabur." Solusinya adalah aturan ambang batas: beralih strategi hanya ketika probabilitas regime baru melebihi 70-80%.

Overfitting. Seperti model apa pun, HMM dapat overfit. Terutama dengan jumlah state atau fitur yang besar. Validasi Walk-Forward adalah wajib.

Masalah khusus crypto. Pasar cryptocurrency masih muda dan tidak stabil secara struktural. "Pasar bull" 2017 dan "pasar bull" 2024 secara statistik adalah fenomena yang berbeda. Model mungkin tidak tergeneralisasi lintas siklus.

Bacaan Lebih Lanjut

Bagi yang ingin mendalami lebih jauh:

Karya fondational:

  • Hamilton, J.D. (1989). A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle. Econometrica, 57(2), 357-384. — Karya fondational tentang model Markov-switching
  • Guidolin, M., & Timmermann, A. (2007). Asset Allocation under Multivariate Regime Switching. Journal of Economic Dynamics and Control, 31(11), 3503-3544. — Aplikasi praktis untuk alokasi aset
  • Ang, A., & Bekaert, G. (2002). Regime Switches in Interest Rates. Journal of Business & Economic Statistics, 20(2), 163-182. — Regime dalam suku bunga

Penelitian modern:

  • Gupta, R., Kapoor, S., Gupta, H., & Natesan, S. (2025). A forest of opinions: A multi-model ensemble-HMM voting framework for market regime shift detection and trading. Data Science in Finance and Economics. — Pendekatan ensemble untuk deteksi regime
  • Oelschlager, L., Adam, T., & Michels, R. (2024). fHMM: Hidden Markov Models for Financial Time Series in R. Journal of Statistical Software. — HMM hierarkis untuk keuangan
  • Bitcoin Price Regime Shifts: A Bayesian MCMC and Hidden Markov Model Analysis of Macroeconomic Influence. Mathematics, 2025. — HMM untuk Bitcoin dengan pendekatan Bayesian

Panduan praktis:

Kesimpulan

Hidden Markov Models bukan peluru perak, melainkan sebuah alat. Alat yang berguna, berdasar secara matematis, dengan setengah abad sejarah dalam statistik dan tiga dekade dalam keuangan.

Nilai utama HMM untuk trading bukan bahwa ia "memprediksi pasar" (tidak ada yang bisa), tetapi bahwa ia memformalkan intuisi trader berpengalaman: pasar melewati fase-fase yang berbeda, dan strategi harus beradaptasi. Alih-alih "saya merasa pasar sedang bearish sekarang" yang subjektif, Anda mendapatkan "probabilitas regime bear adalah 82%, rata-rata durasi siklus bear adalah 16 hari, kita berada di hari ke-5."

Haruskah Anda mengintegrasikan HMM ke dalam stack trading Anda? Jika Anda memiliki beberapa strategi untuk kondisi pasar yang berbeda dan Anda lelah beralih secara manual — tentu saja ya. Jika Anda trading satu strategi dan tidak berencana untuk berkembang — simpan dulu, tapi ingatlah.

Dan ingat: model terbaik adalah yang bekerja dalam produksi, bukan yang menang dalam backtest.


Kutipan: Jika Anda menggunakan materi dari artikel ini dalam penelitian atau proyek Anda, harap kutip:

Hidden Markov Models dalam Trading: Cara Mengadaptasi Strategi ke Regime Pasar. marketmaker.cc, 2026. URL: https://marketmaker.cc/id/blog/post/regime-detection-hmm-adaptive-trading

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.