Model Markov Tersembunyi dalam Perdagangan: Cara Menyesuaikan Strategi dengan Rejim Pasaran
Setiap pedagang algo mempunyai saat krisis kewujudan. Anda menghabiskan tiga bulan untuk sebuah strategi. Backtest menunjukkan Sharpe 2.4. Lengkung ekuiti adalah sebuah karya seni. Anda melancarkan bot. Dua minggu pertama membawa euforia — strategi itu menjana alfa. Kemudian pasaran "bertukar" — dan bot momentum anda mula membuang modal secara sistematik dalam julat, membeli setiap puncak tempatan dan menjual setiap lembah tempatan.
Masalahnya bukan pada strategi. Masalahnya ialah pasaran bukanlah satu sistem, tetapi beberapa, dan mereka bertukar antara satu sama lain tanpa amaran. Strategi momentum, sempurna untuk tren, memusnahkan akaun dalam julat. Strategi grid yang mencetak wang dalam pasaran sisi meledak pada pergerakan berarah. Mean-reversion, stabil dalam pasaran tenang, mendapat margin-called pada swan hitam.
Soalannya bukan "strategi mana yang lebih baik," tetapi "apakah rejim pasaran semasa dan strategi mana yang sepadan dengannya." Dan inilah di mana Model Markov Tersembunyi (HMM) memasuki pentas — kerangka matematik yang membolehkan anda memformalkan intuisi ini.
Pasaran Adalah Tidak Pegun, dan Itu Bukan Bug, Itu Ciri
Mari mulakan dengan kebenaran yang tidak menyenangkan: hampir semua model statistik asas mengandaikan kepegunaan data. Min dan varians tidak berubah mengikut masa, autokorelasi adalah tetap, taburan adalah stabil. Siri masa kewangan melanggar semua andaian ini secara serentak.
Lihat pulangan harian BTC selama 5 tahun lepas. Pulangan harian purata semasa rally bull 2024 adalah kira-kira +0.3%, dengan sisihan piawai ~2.5%. Dalam pasaran bear 2022 — purata adalah -0.15%, sisihan piawai ~4%. Dalam pasaran sisi musim panas 2023 — purata ~0%, sisihan piawai ~1.5%. Ini adalah tiga rejim statistik yang berbeza secara asasnya dengan taburan yang berbeza.
Secara formal: biar ialah pulangan pada masa . Dalam dunia pegun, dengan parameter tetap. Dalam realiti, parameter itu sendiri adalah proses rawak: , di mana adalah keadaan tersembunyi (rejim pasaran), bertukar antara bilangan nilai yang terhingga.
Idea ini diformalkan pada tahun 1989 oleh James Hamilton dalam kertas asasnya "A New Approach to the Economic Analysis of Nonstationary Time Series and the Business Cycle." Beliau menunjukkan bahawa kitaran perniagaan boleh dimodelkan sebagai peralihan antara dua keadaan tersembunyi — kemelesetan dan pengembangan — menggunakan mekanisme Markov. Sejak itu, model Hamilton telah menjadi salah satu alat paling banyak dipetik dalam ekonometri.
Tiga rejim pasaran — bull (hijau), bear (merah), dan sisi (kuning) — secara visual jelas dalam pandangan ke belakang, tetapi mengesan peralihan dalam masa nyata adalah jauh lebih sukar.
HMM: Intuisi Melalui Analogi
Sebelum menyelami formula, mari bina beberapa intuisi.
Rantai Markov: Tanpa Ingatan
Rantai Markov adalah proses rawak di mana masa depan hanya bergantung pada masa kini, bukan masa lalu. Cuaca esok bergantung pada cuaca hari ini, tetapi bukan pada cuaca seminggu yang lalu (penyederhanaan yang kuat, tetapi ia berfungsi sebagai model).
Rejim pasaran berkelakuan serupa. Jika hari ini pasaran berada dalam rejim bull, kebarangkalian kekal di dalamnya esok adalah tinggi (katakanlah, 95%). Kebarangkalian beralih ke bearish adalah rendah (3%). Ke sisi — lebih rendah lagi (2%). Ini adalah matriks kebarangkalian peralihan.
Bull Bear Sisi
Bull [0.95 0.03 0.02 ]
Bear [0.04 0.93 0.03 ]
Sisi [0.05 0.05 0.90 ]
Perhatikan: elemen pepenjuru adalah tinggi — rejim adalah "melekit." Pasaran tidak melompat dari bull ke bear setiap hari. Ia kekal dalam satu rejim selama berminggu-minggu dan berbulan-bulan sebelum bertukar. Jangkaan tempoh rejim adalah . Untuk rejim bull dengan , itu adalah 20 hari. Untuk rejim bear dengan — kira-kira 14 hari.
Keadaan Tersembunyi: Kami Hanya Melihat Bayang
Kata kunci adalah "tersembunyi." Kami tidak memerhati rejim pasaran secara langsung. Tiada siapa yang memasang papan tanda yang berkata "Perhatian, beralih ke rejim bear." Kami hanya melihat pemerhatian — pulangan, kemeruapan, volum. Rejim adalah pemboleh ubah laten yang mesti disimpulkan daripada pemerhatian.
Ia seperti berada di dalam bilik tanpa tingkap dan cuba menentukan cuaca oleh cara orang yang masuk dari luar berpakaian. Payung? Mungkin hujan. Seluar pendek dan cermin mata hitam? Cerah. Tetapi seorang orang dalam seluar pendek tidak bermakna ia pasti cerah — mungkin mereka hanya seorang optimis. Anda perlu mengumpul pemerhatian dan menganggar keadaan tersembunyi secara probabilistik.
Dalam HMM, setiap rejim tersembunyi "memancarkan" pemerhatian daripada taburannya sendiri:
- Rejim bull → pulangan daripada , di mana , adalah sederhana
- Rejim bear → pulangan daripada , di mana , adalah tinggi
- Sisi → pulangan daripada , di mana , adalah rendah
Perhatikan corak yang khas: rejim bear biasanya mempunyai bukan sahaja min negatif, tetapi juga kemeruapan yang tinggi. Pasaran mengambil lif ke bawah dan tangga ke atas — dan HMM menangkap ini secara automatik.
Seni bina Model Markov Tersembunyi: keadaan tersembunyi (rejim) bertukar mengikut rantai Markov, setiap keadaan menjana pulangan yang boleh diperhatikan daripada taburan Gaussian tersendiri.
Tiga Algoritma HMM: Forward, Viterbi, Baum-Welch
Semua kerja dengan HMM terdiri daripada tiga masalah asas, masing-masing dengan algoritmanya sendiri.
Masalah 1: Apakah Kebarangkalian Pemerhatian Ini? (Algoritma Forward)
Soalan: Diberi urutan pulangan, apakah kebarangkalian memerhati urutan ini tepat seperti yang diberikan parameter model?
Mengapa: Perbandingan model (AIC/BIC), pemeriksaan kecukupan.
Cara ia berfungsi: Algoritma Forward adalah pengaturcaraan dinamik. Pada setiap langkah , kami mengira "pemboleh ubah forward" — kebarangkalian memerhati urutan dan berada dalam keadaan pada masa .
Rekursi:
Di mana adalah kebarangkalian peralihan dari keadaan ke , dan adalah kebarangkalian pemerhatian dalam keadaan . Dengan kata-kata: kami menjumlahkan semua laluan yang mungkin kami tiba di keadaan , dan mendarab dengan kebarangkalian pemerhatian.
Kerumitan: berbanding yang naif, di mana adalah bilangan keadaan, adalah panjang urutan. Untuk 3 rejim dan 1000 pemerhatian, itu adalah 9000 operasi berbanding . Perbezaannya, boleh dikatakan, adalah ketara.
Masalah 2: Apakah Urutan Rejim yang Paling Mungkin? (Algoritma Viterbi)
Soalan: Diberi urutan pulangan, urutan keadaan tersembunyi (rejim) mana yang paling mungkin menjanakannya?
Mengapa: Inilah yang kami perlukan untuk dagangan — menentukan rejim pada setiap titik dalam masa.
Cara ia berfungsi: Algoritma Viterbi adalah sama seperti Forward, tetapi daripada menjumlahkan semua laluan, ia mengambil maksimum. Kami tidak mencari kebarangkalian semua laluan yang mungkin, tetapi untuk laluan yang paling mungkin.
Ditambah laluan ke belakang (backtracking) untuk memulihkan urutan keadaan itu sendiri. Hasilnya adalah urutan rejim yang dinyahkod: "bull-bull-bull-bear-bear-sisi-..."
Dalam amalan, untuk dagangan, yang lebih biasa digunakan bukan Viterbi (optimum global) tetapi penapisan — kebarangkalian keadaan posterior pada setiap saat: . Ini membolehkan bekerja dalam talian tanpa menunggu keseluruhan urutan, dan mendapatkan anggaran "lembut" seperti "70% bull, 25% sisi, 5% bear."
Masalah 3: Bagaimana Melatih Model? (Algoritma Baum-Welch)
Soalan: Diberi hanya pemerhatian, parameter model (, , ) mana yang memaksimumkan kemungkinan data?
Mengapa: Melatih model pada data sejarah.
Cara ia berfungsi: Algoritma Baum-Welch adalah kes khas algoritma EM (Expectation-Maximization):
- Langkah-E: Menggunakan parameter semasa, hitung keadaan tersembunyi yang dijangkakan (melalui Forward-Backward)
- Langkah-M: Kemas kini parameter dengan memaksimumkan kemungkinan berdasarkan keadaan yang dijangkakan ini
- Ulangi sehingga penumpuan
Nuansa penting: EM menjamin penumpuan hanya ke maksimum tempatan. Keadaan awal yang berbeza boleh menghasilkan keputusan yang berbeza. Dalam amalan, model dilatih beberapa kali dengan permulaan yang berbeza, dan hasil terbaik dipilih berdasarkan log-kemungkinan. Dalam hmmlearn, ini dilakukan secara automatik melalui parameter n_init.
Rejim Pasaran Kripto: Apa yang Kami Cari
Untuk mata wang kripto, pembahagian tiga rejim klasik berfungsi dengan sangat baik disebabkan fasa pasaran yang jelas.
Rejim 1: Bull
- Purata pulangan: +0.15% ... +0.5% sehari
- Kemeruapan (std): 2-3% sehari
- Karakter: pertumbuhan berterusan dengan penurunan sederhana
- Tempoh: 2-6 bulan berterusan
- Volum: meningkat, terutamanya di pasaran spot
- On-chain: MVRV > 1.5, alamat aktif yang berkembang
Rejim 2: Bear
- Purata pulangan: -0.1% ... -0.4% sehari
- Kemeruapan (std): 3-6% sehari
- Karakter: ranap mendadak, runtuhan likuidasi, lantun kucing mati
- Tempoh: 1-4 bulan (biasanya lebih pendek daripada bull)
- Volum: melonjak pada jualan panik, kemudian pudar
- On-chain: MVRV < 1, aliran masuk bursa yang meningkat
Rejim 3: Sisi (pengumpulan)
- Purata pulangan: ~0% sehari
- Kemeruapan (std): 1-2% sehari
- Karakter: pergerakan terikat julat, pecahan palsu
- Tempoh: 1-3 bulan
- Volum: rendah, menurun
- On-chain: metrik stabil, aktiviti menurun
Mengapa tepat tiga rejim dan bukan dua atau lima? Dua terlalu kasar — anda kehilangan maklumat tentang fasa sisi (dan untuk bot market-making, ini adalah rejim yang paling menguntungkan). Lima atau lebih — model menjadi terlalu pas, kebarangkalian peralihan tidak stabil, tafsiran menjadi sukar. Tiga adalah keseimbangan optimum, disahkan oleh kriteria maklumat (AIC/BIC) dan intuisi ekonomi.
Walau bagaimanapun, bilangan keadaan adalah hiperparameter dan harus diuji. Guidolin & Timmermann (2007) dalam kertas mereka "Asset Allocation under Multivariate Regime Switching" menemui empat rejim untuk portfolio campuran saham-dan-bon: ranap, pertumbuhan perlahan, bull, dan pemulihan.
Kejuruteraan Ciri: Apa yang Perlu Dimasukkan ke dalam Model
Pilihan paling mudah adalah hanya memasukkan pulangan harian. Ini berfungsi, tetapi boleh diperbaiki. Berikut adalah set ciri yang telah terbukti baik dalam amalan:
Ciri Harga
- Log pulangan harian:
- Kemeruapan bergulir: melalui tetingkap (contohnya, 20 hari)
- Purata pulangan bergulir:
Ciri Volum
- Volum ternormal:
- Korelasi volum-harga: korelasi antara volum dan pulangan mutlak melalui tetingkap bergulir
Ciri On-Chain (untuk mata wang kripto)
- Nisbah MVRV: permodalan pasaran kepada permodalan yang direalisasikan. MVRV > 2 — pasaran terlalu panas, < 1 — dinilai rendah
- Nisbah NVT: nilai rangkaian kepada volum transaksi. Setara blockchain dengan P/E
- Aliran Bersih Bursa: aliran bersih ke bursa. Positif — tekanan jual, negatif — pengumpulan
- Alamat Aktif: bilangan alamat aktif (pertumbuhan = minat, penurunan = apati)
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 ciri mesti pegun (atau sekurang-kurangnya hampir begitu). Log pulangan adalah pegun. Harga tidak. Volum lebih baik dinormalkan. Kemeruapan boleh dibiarkan apa adanya — ia juga kuasi-pegun.
Nuansa lain: HMM multivariat (apabila vektor ciri dimasukkan sebagai input) berfungsi lebih baik daripada univariat, tetapi memerlukan lebih banyak data untuk latihan. Untuk kripto dengan sejarah 5+ tahun, ini biasanya bukan masalah. Untuk altcoin baharu dengan sejarah 3 bulan — lebih baik berpegang pada satu atau dua ciri.
Pelaksanaan Langkah demi Langkah dalam Python dengan hmmlearn
Mari pergi ke kod. Pustaka hmmlearn adalah standard de facto untuk HMM dalam Python. API mudah, keserasian scikit-learn, berfungsi di luar kotak.
Langkah 1: Pemuatan 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: Penyediaan Ciri dan Latihan 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: Penyahkodan Rejim
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: Tafsiran Rejim
Di sinilah perkara menjadi menarik — dan rumit. HMM tidak tahu bahawa rejim 0 adalah "bull." Ia hanya mencari tiga kluster dalam ruang pemerhatian. Penomoran adalah sewenang-wenangnya dan mungkin berubah dari satu larian ke larian yang lain.
Anda perlu melihat statistik setiap rejim dan menugaskan 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 kelihatan lebih kurang 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: rejim bear bukan sahaja mempunyai pulangan negatif, tetapi juga kemeruapan tertinggi (4.1% berbanding 1.6% dalam sisi). Ini adalah pemerhatian empirikal klasik yang dikenali sebagai "kesan leverage" — pasaran yang jatuh lebih meruap daripada yang naik.
Matriks Peralihan dan Tempoh Rejim
Matriks kebarangkalian peralihan adalah salah satu artifak paling bermaklumat HMM:
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)
Keputusan 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
Apa yang kami perhatikan:
- Rejim adalah melekit: kebarangkalian kekal dalam rejim semasa adalah > 93% untuk semua keadaan
- Rejim bull bertahan lebih lama daripada bear (20.8 berbanding 15.9 hari) — sekali lagi, pasaran naik lebih perlahan daripada jatuh
- Peralihan terus dari bull ke bear adalah tidak mungkin (1.8%) — biasanya pasaran melalui fasa sisi
Perkara terakhir adalah intuitif secara ekonomi: pasaran jarang berbalik serta-merta. Biasanya terdapat fasa pengedaran (sisi di puncak) sebelum pasaran bear, dan fasa pengumpulan (sisi di bahagian bawah) sebelum pasaran bull.
Strategi Dagangan: Satu Rejim — Satu Strategi
Sekarang kami menerapkan apa yang telah kami pelajari. Idea: jangan berdagang satu strategi sepanjang masa, tetapi tukar antara strategi bergantung pada rejim yang dikesan.
Bull → Momentum Agresif
- Saiz kedudukan yang ditingkatkan (sehingga 100% modal)
- Strategi tren: pecahan, mengikut purata bergerak
- Stop-loss yang lebar (jangan dikeluarkan pada penurunan)
- Jangan short (atau short secara minimum)
Bear → Defensif / Kedudukan Short
- Saiz kedudukan yang dikurangkan (30-50% modal)
- Strategi short atau wang tunai penuh
- Stop-loss yang ketat
- Perlindungan melalui pilihan put atau niaga hadapan
Sisi → Mean-Reversion / Grid
- Saiz kedudukan sederhana (50-70% modal)
- Strategi dagangan grid
- Mean-reversion: beli di sempadan bawah, jual di sempadan 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-Adaptif vs Beli-dan-Tahan
Sekarang soalan utama: adakah ini berfungsi lebih baik daripada Beli-dan-Tahan 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)
Perbandingan lengkung ekuiti: Beli-dan-Tahan (biru) dan strategi HMM-adaptif (oren). Strategi adaptif mengurangkan drawdown dengan ketara semasa fasa bear.
Keputusan 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
Pemerhatian utama: strategi HMM-adaptif tidak semestinya memberikan pulangan jumlah yang lebih tinggi (walaupun ia berlaku dalam kes ini), tetapi ia mengurangkan drawdown maksimum secara dramatik — dari 76% kepada 38%. Sharpe meningkat dari 1.12 ke 1.68. Ini adalah peningkatan dalam pulangan yang diselaraskan risiko, bukan sekadar "lebih banyak wang."
Mengapa? Kerana dalam rejim bear, strategi beralih ke mod defensif atau short, mengelakkan ranap besar. Kosnya adalah kemasukan yang tertunda ke dalam tren (model mengesan rejim bull dengan kelambatan beberapa hari) dan peralihan palsu semasa tempoh peralihan.
Visualisasi Keputusan
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 asas adalah titik permulaan yang baik, tetapi jauh dari had.
HMM Hierarki
Dalam HMM hierarki, tahap atas menentukan "makro-rejim" (tren global, kitaran tahunan), dan tahap bawah menentukan "mikro-rejim" (turun naik intra-minggu/intra-bulan). Pakej fHMM untuk R, diterbitkan dalam Journal of Statistical Software pada tahun 2024 (Oelschlager, Adam, Michels), melaksanakan idea ini tepat untuk siri masa kewangan.
Contoh: makro-rejim "kitaran bull" mengandungi dalam dirinya mikro-rejim "rali," "pembetulan," dan "penyatuan." Ini menghalang kepanikan pada setiap penurunan 10% dalam pasaran bull — model memahami bahawa pembetulan dalam kitaran bull adalah normal.
HMM Multivariat dengan Ciri yang Diperluas
Daripada pulangan univariat, kami memasukkan vektor ciri: pulangan + kemeruapan + volum + data on-chain. Ini membolehkan model "melihat" lebih banyak maklumat tentang keadaan pasaran.
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 + Ensemble ML
Pendekatan moden: gunakan HMM bukan sebagai sistem dagangan, tetapi sebagai penjana ciri untuk model hiliran. Idea ini, dihuraikan dalam Gupta et al. (2025) "A forest of opinions: A multi-model ensemble-HMM voting framework for market regime shift detection and trading":
- HMM menentukan rejim semasa (atau kebarangkalian rejim)
- Rejim dimasukkan sebagai ciri tambahan ke Random Forest / Gradient Boosting
- Model ML membuat keputusan dagangan khusus dengan mengambil kira rejim
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}")
Pengeluaran: Perangkap
Backtest yang cantik hanyalah separuh daripada pertarungan. Dalam pengeluaran, beberapa kejutan tidak menyenangkan menanti.
Masalah Kelambatan (Look-Ahead Bias)
HMM menentukan rejim berdasarkan data semasa dan lampau, tetapi dalam backtest terdapat godaan untuk melatih model pada keseluruhan set data, termasuk data masa depan. Ini adalah look-ahead bias, dan ia mengubah backtest menjadi rekaan.
Penyelesaian: Pendekatan Walk-Forward. Latih model pada data sehingga masa , ramalkan rejim pada masa , kemudian geser tetingkap. Tepat seperti yang dihuraikan dalam artikel kami tentang Pengoptimuman Walk-Forward.
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
Jadual Latihan Semula
Seberapa kerap anda perlu melatih semula model? Terlalu jarang — model menjadi lapuk, pasaran berubah. Terlalu kerap — model menjadi tidak stabil, rejim "melompat."
Cadangan empirikal:
- Untuk data harian: latih semula setiap 1-4 minggu (21 hari dagangan adalah lalai yang baik)
- Tetingkap latihan: 6-12 bulan (252 hari dagangan — satu tahun)
- Pemantauan: jika log-kemungkinan pada data baharu jatuh di bawah ambang — latihan semula tidak berjadual
Ketidakstabilan Label
Dengan setiap latihan semula, penomoran keadaan mungkin berubah: apa yang dulunya "rejim 0" (bull) mungkin menjadi "rejim 2." Anda perlu memadankan keadaan secara automatik berdasarkan statistiknya (min pulangan, kemeruapan).
Pengemaskinian Dalam Talian
Untuk dagangan masa nyata, latihan semula penuh harian adalah berlebihan. Anda boleh menggunakan penapisan Forward: tetapkan parameter model, tetapi kemas kini kebarangkalian keadaan posterior dengan setiap pemerhatian baharu. Ini adalah operasi segera.
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 Bilangan Keadaan
Walaupun tiga rejim adalah lalai 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)
Had dan Amaran
Tidak jujur untuk berdiam diri tentang masalah ini.
Andaian Gaussian. GaussianHMM asas mengandaikan bahawa pulangan dalam setiap rejim diedarkan secara normal. Taburan sebenar mempunyai ekor tebal dan asimetri. Penyelesaian separa adalah menggunakan taburan Student-t atau GMMHMM (Campuran Gaussian per keadaan).
Bilangan keadaan adalah pilihan anda. BIC membantu, tetapi tidak selalu konklusif. Dua penyelidik yang berbeza mungkin tiba pada bilangan rejim yang berbeza dan kedua-duanya akan "betul."
Tempoh peralihan. Model tidak pasti semasa peralihan rejim. Kebarangkalian diedarkan lebih kurang sama rata, dan strategi menerima isyarat "kabur." Penyelesaiannya adalah peraturan ambang: tukar strategi hanya apabila kebarangkalian rejim baharu melebihi 70-80%.
Terlalu pas. Seperti mana-mana model, HMM boleh terlalu pas. Terutamanya dengan bilangan keadaan atau ciri yang banyak. Pengesahan Walk-Forward adalah wajib.
Isu khusus kripto. Pasaran mata wang kripto adalah muda dan tidak stabil secara struktur. "Pasaran bull" 2017 dan "pasaran bull" 2024 adalah fenomena yang berbeza secara statistik. Model mungkin tidak umum merentasi kitaran.
Bacaan Lanjut
Bagi mereka yang ingin mendalami lagi:
Karya asas:
- 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 asas mengenai 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 praktikal kepada peruntukan aset
- Ang, A., & Bekaert, G. (2002). Regime Switches in Interest Rates. Journal of Business & Economic Statistics, 20(2), 163-182. — Rejim dalam kadar faedah
Penyelidikan moden:
- 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 pengesanan rejim
- Oelschlager, L., Adam, T., & Michels, R. (2024). fHMM: Hidden Markov Models for Financial Time Series in R. Journal of Statistical Software. — HMM hierarki untuk kewangan
- Bitcoin Price Regime Shifts: A Bayesian MCMC and Hidden Markov Model Analysis of Macroeconomic Influence. Mathematics, 2025. — HMM untuk Bitcoin dengan pendekatan Bayesian
Panduan praktikal:
- QuantStart: Market Regime Detection using Hidden Markov Models in QSTrader
- QuantInsti: Step-by-Step Python Guide for Regime-Specific Trading Using HMM and Random Forest
- hmmlearn documentation
Kesimpulan
Model Markov Tersembunyi bukan peluru perak, tetapi alat. Alat yang berguna, berasaskan matematik, dengan sejarah setengah abad dalam statistik dan tiga dekad dalam kewangan.
Nilai utama HMM untuk dagangan bukan bahawa ia "meramalkan pasaran" (tiada siapa yang melakukannya), tetapi ia memformalkan intuisi seorang pedagang berpengalaman: pasaran melalui fasa yang berbeza, dan strategi mesti menyesuaikan diri. Daripada "saya merasakan pasaran sedang menanggung sekarang" yang subjektif, anda mendapat "kebarangkalian rejim bear adalah 82%, purata tempoh kitaran bear adalah 16 hari, kami berada pada hari ke-5."
Perlukah anda mengintegrasikan HMM ke dalam tindanan dagangan anda? Jika anda mempunyai pelbagai strategi untuk keadaan pasaran yang berbeza dan anda penat menukarnya secara manual — sudah tentu ya. Jika anda berdagang satu strategi dan tidak merancang untuk berkembang — simpan dulu, tetapi ingatlah.
Dan ingat: model terbaik adalah yang berfungsi dalam pengeluaran, bukan yang menang dalam backtest.
Petikan: Jika anda menggunakan bahan daripada artikel ini dalam penyelidikan atau projek anda, sila petik:
Model Markov Tersembunyi dalam Perdagangan: Cara Menyesuaikan Strategi dengan Rejim Pasaran. marketmaker.cc, 2026. URL: https://marketmaker.cc/ms/blog/post/regime-detection-hmm-adaptive-trading
Pengarang
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.