
Mean Reversion (hay Hồi quy về giá trị trung bình) là một trong những chiến lược giao dịch phổ biến và lâu đời nhất trên thị trường tài chính. Chiến lược này dựa trên nguyên tắc rằng giá của một tài sản sẽ có xu hướng quay trở về giá trị trung bình của nó theo thời gian. Khi giá di chuyển quá xa khỏi mức trung bình (quá cao hoặc quá thấp), chiến lược Mean Reversion đặt cược rằng sự điều chỉnh sẽ xảy ra, đưa giá trở lại mức cân bằng. Bài viết này sẽ giải thích cơ sở lý thuyết, ứng dụng thực tế và các kỹ thuật để áp dụng chiến lược Mean Reversion hiệu quả.
Cơ sở lý thuyết của Mean Reversion

Chiến lược Mean Reversion dựa trên một số giả định và khái niệm thống kê quan trọng:
1. Tính dừng của chuỗi thời gian (Stationarity)
Một chuỗi thời gian được coi là dừng nếu các đặc tính thống kê của nó (như trung bình, phương sai) không thay đổi theo thời gian. Đây là điều kiện cần thiết cho Mean Reversion. Đối với các tài sản tài chính, điều này có nghĩa là giá hoặc các biến đổi của giá (ví dụ: lợi nhuận) sẽ dao động xung quanh một mức trung bình không đổi.
2. Quá trình Ornstein-Uhlenbeck
Trong toán học tài chính, Mean Reversion thường được mô hình hóa bằng quá trình Ornstein-Uhlenbeck:
Trong đó:
- xt là giá tại thời điểm t
- μ là mức trung bình mà giá hồi quy về
- θ là tốc độ hồi quy (càng cao càng nhanh)
- σ là độ biến động
- dWt là quá trình Wiener (chuyển động Brown)
3. Trạng thái quá mua/quá bán (Overbought/Oversold)
Mean Reversion hoạt động trên giả định rằng các thị trường hoặc tài sản có thể tạm thời ở trạng thái quá mua (giá cao hơn giá trị cơ bản) hoặc quá bán (giá thấp hơn giá trị cơ bản), và sẽ điều chỉnh về mức cân bằng.
Kiểm tra tính Mean Reversion của tài sản
Trước khi áp dụng chiến lược Mean Reversion, cần xác định liệu một tài sản có thực sự có đặc tính hồi quy hay không. Có nhiều phương pháp thống kê để kiểm tra:
1. Kiểm định Augmented Dickey-Fuller (ADF Test)
Đây là phương pháp phổ biến để kiểm tra tính dừng của chuỗi thời gian. Nếu kết quả kiểm định từ chối giả thuyết về sự tồn tại của đơn vị gốc (unit root), chuỗi có thể được coi là dừng và có tiềm năng cho Mean Reversion.
from statsmodels.tsa.stattools import adfuller
def adf_test(series):
"""
Thực hiện kiểm định ADF để kiểm tra tính dừng
Tham số:
- series: Chuỗi giá cần kiểm tra
Trả về:
- Kết quả kiểm định và giải thích
"""
result = adfuller(series, autolag='AIC')
print(f'ADF Statistic: {result[0]:.6f}')
print(f'p-value: {result[1]:.6f}')
if result[1] <= 0.05:
print("Kết quả: Loại bỏ giả thuyết null - Chuỗi có tiềm năng Mean Reversion")
else:
print("Kết quả: Không thể loại bỏ giả thuyết null - Chuỗi có thể không phù hợp cho Mean Reversion")
return result
import yfinance as yf
import pandas as pd
ticker = 'AAPL'
data = yf.download(ticker, start='2020-01-01', end='2023-01-01')
adf_test(data['Close'])
returns = data['Close'].pct_change().dropna()
adf_test(returns)
2. Kiểm định Hurst Exponent
Chỉ số Hurst đo lường mức độ Mean Reversion hoặc momentum của một chuỗi thời gian:
- H < 0.5: Chuỗi có đặc tính Mean Reversion
- H = 0.5: Chuỗi là random walk (không có Memory)
- H > 0.5: Chuỗi có đặc tính momentum (có xu hướng)
def hurst_exponent(time_series, max_lag=100):
"""
Tính chỉ số Hurst
Tham số:
- time_series: Chuỗi thời gian cần phân tích
- max_lag: Độ trễ tối đa để tính toán
Trả về:
- Giá trị ước lượng của chỉ số Hurst
"""
lags = range(2, max_lag)
tau = [np.sqrt(np.std(np.subtract(time_series[lag:], time_series[:-lag]))) for lag in lags]
poly = np.polyfit(np.log(lags), np.log(tau), 1)
return poly[0] / 2.0
import numpy as np
hurst = hurst_exponent(data['Close'].values)
print(f"Chỉ số Hurst: {hurst:.4f}")
if hurst < 0.45:
print("Chuỗi có đặc tính Mean Reversion mạnh")
elif hurst < 0.55:
print("Chuỗi gần với random walk")
else:
print("Chuỗi có đặc tính xu hướng (momentum)")
3. Kiểm định Half-Life
Half-life (hay bán kỳ) là thời gian cần thiết để một giá trị quay trở về một nửa đường đến giá trị trung bình của nó. Đây là một thước đo về tốc độ hồi quy.
def calculate_half_life(series):
"""
Tính half-life của quá trình Mean Reversion
Tham số:
- series: Chuỗi giá
Trả về:
- Half-life (bán kỳ), đơn vị là khoảng thời gian của chuỗi
"""
lagged_series = series.shift(1).dropna()
delta_series = series[1:] - lagged_series
X = lagged_series.values.reshape(-1, 1)
y = delta_series.values
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X, y)
phi = model.coef_[0]
half_life = -np.log(2) / phi if phi < 0 else float('inf')
return half_life
price_series = pd.Series(data['Close'])
half_life = calculate_half_life(price_series)
print(f"Half-life của quá trình Mean Reversion: {half_life:.2f} ngày")
Các chiến lược Mean Reversion phổ biến
Sau khi đã xác định được tài sản có đặc tính Mean Reversion, chúng ta có thể áp dụng nhiều chiến lược khác nhau:
1. Bollinger Bands
Bollinger Bands là một trong những công cụ phổ biến nhất cho Mean Reversion, bao gồm một đường trung bình động và hai dải (bands) trên và dưới, thường cách đường trung bình động 2 độ lệch chuẩn.
Chiến lược cơ bản:
- Mua khi giá chạm hoặc vượt qua dải dưới (giá thấp bất thường)
- Bán khi giá chạm hoặc vượt qua dải trên (giá cao bất thường)
def bollinger_bands_strategy(data, window=20, num_std=2):
"""
Tạo tín hiệu giao dịch dựa trên Bollinger Bands
Tham số:
- data: DataFrame chứa dữ liệu OHLC
- window: Kích thước cửa sổ cho MA
- num_std: Số độ lệch chuẩn cho bands
Trả về:
- DataFrame với các bands và tín hiệu
"""
df = data.copy()
df['MA'] = df['Close'].rolling(window=window).mean()
df['STD'] = df['Close'].rolling(window=window).std()
df['Upper_Band'] = df['MA'] + (df['STD'] * num_std)
df['Lower_Band'] = df['MA'] - (df['STD'] * num_std)
df['Signal'] = 0
df.loc[df['Close'] < df['Lower_Band'], 'Signal'] = 1
df.loc[df['Close'] > df['Upper_Band'], 'Signal'] = -1
return df
df_with_signals = bollinger_bands_strategy(data)
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(df_with_signals.index, df_with_signals['Close'], label='Close Price')
plt.plot(df_with_signals.index, df_with_signals['MA'], label='Moving Average', alpha=0.5)
plt.plot(df_with_signals.index, df_with_signals['Upper_Band'], label='Upper Band', linestyle='--')
plt.plot(df_with_signals.index, df_with_signals['Lower_Band'], label='Lower Band', linestyle='--')
buy_signals = df_with_signals[df_with_signals['Signal'] == 1]
sell_signals = df_with_signals[df_with_signals['Signal'] == -1]
plt.scatter(buy_signals.index, buy_signals['Close'], marker='^', color='g', s=100, label='Buy Signal')
plt.scatter(sell_signals.index, sell_signals['Close'], marker='v', color='r', s=100, label='Sell Signal')
plt.title(f'{ticker} Bollinger Bands Strategy')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
2. RSI (Relative Strength Index)
RSI là một chỉ báo dao động từ 0 đến 100, đo lường tốc độ và sự thay đổi của chuyển động giá. Trong context của Mean Reversion:
- RSI > 70: Tài sản được coi là quá mua, có khả năng giảm giá
- RSI < 30: Tài sản được coi là quá bán, có khả năng tăng giá
def rsi_strategy(data, window=14, overbought=70, oversold=30):
"""
Tạo tín hiệu giao dịch dựa trên RSI
Tham số:
- data: DataFrame chứa dữ liệu OHLC
- window: Kích thước cửa sổ cho RSI
- overbought: Ngưỡng quá mua
- oversold: Ngưỡng quá bán
Trả về:
- DataFrame với RSI và tín hiệu
"""
df = data.copy()
delta = df['Close'].diff()
gains = delta.where(delta > 0, 0)
losses = -delta.where(delta < 0, 0)
avg_gains = gains.rolling(window=window).mean()
avg_losses = losses.rolling(window=window).mean()
rs = avg_gains / avg_losses
df['RSI'] = 100 - (100 / (1 + rs))
df['Signal'] = 0
df.loc[df['RSI'] < oversold, 'Signal'] = 1
df.loc[df['RSI'] > overbought, 'Signal'] = -1
return df
df_with_rsi = rsi_strategy(data)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), gridspec_kw={'height_ratios': [3, 1]})
ax1.plot(df_with_rsi.index, df_with_rsi['Close'], label='Close Price')
buy_signals = df_with_rsi[df_with_rsi['Signal'] == 1]
sell_signals = df_with_rsi[df_with_rsi['Signal'] == -1]
ax1.scatter(buy_signals.index, buy_signals['Close'], marker='^', color='g', s=100, label='Buy Signal')
ax1.scatter(sell_signals.index, sell_signals['Close'], marker='v', color='r', s=100, label='Sell Signal')
ax1.set_title(f'{ticker} RSI Strategy')
ax1.set_ylabel('Price')
ax1.legend()
ax1.grid(True)
ax2.plot(df_with_rsi.index, df_with_rsi['RSI'], color='purple', label='RSI')
ax2.axhline(y=70, color='r', linestyle='--', label='Overbought (70)')
ax2.axhline(y=30, color='g', linestyle='--', label='Oversold (30)')
ax2.fill_between(df_with_rsi.index, y1=70, y2=100, color='red', alpha=0.1)
ax2.fill_between(df_with_rsi.index, y1=0, y2=30, color='green', alpha=0.1)
ax2.set_ylabel('RSI')
ax2.set_xlabel('Date')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
3. Pairs Trading
Pairs Trading (Giao dịch cặp) là một chiến lược Mean Reversion cao cấp hơn, liên quan đến việc xác định hai tài sản có mối quan hệ đồng tích hợp (cointegration) và giao dịch khi chênh lệch giá giữa chúng đi xa khỏi mức trung bình.
Chiến lược cơ bản:
- Tìm hai cổ phiếu có tương quan cao (thường trong cùng ngành)
- Kiểm tra đồng tích hợp để đảm bảo mối quan hệ ổn định
- Tính toán chênh lệch giá chuẩn hóa (Z-score) giữa hai cổ phiếu
- Khi Z-score vượt quá ngưỡng (ví dụ: +2), bán cổ phiếu tăng giá mạnh và mua cổ phiếu kém hiệu suất
- Khi Z-score trở về 0, đóng cả hai vị thế
def pairs_trading_strategy(stock1_data, stock2_data, window=30, z_threshold=2):
"""
Chiến lược giao dịch cặp
Tham số:
- stock1_data: DataFrame cho cổ phiếu 1
- stock2_data: DataFrame cho cổ phiếu 2
- window: Cửa sổ cho việc tính Z-score
- z_threshold: Ngưỡng Z-score để kích hoạt giao dịch
Trả về:
- DataFrame với tín hiệu giao dịch
"""
pairs = pd.DataFrame({
'stock1': stock1_data['Close'],
'stock2': stock2_data['Close']
})
pairs['ratio'] = pairs['stock1'] / pairs['stock2']
pairs['ratio_mean'] = pairs['ratio'].rolling(window=window).mean()
pairs['ratio_std'] = pairs['ratio'].rolling(window=window).std()
pairs['z_score'] = (pairs['ratio'] - pairs['ratio_mean']) / pairs['ratio_std']
pairs['signal'] = 0
pairs.loc[pairs['z_score'] > z_threshold, 'signal'] = -1
pairs.loc[pairs['z_score'] < -z_threshold, 'signal'] = 1
pairs.loc[abs(pairs['z_score']) < 0.5, 'signal'] = 0
return pairs
stock1_ticker = 'KO'
stock2_ticker = 'PEP'
stock1_data = yf.download(stock1_ticker, start='2020-01-01', end='2023-01-01')
stock2_data = yf.download(stock2_ticker, start='2020-01-01', end='2023-01-01')
pairs_result = pairs_trading_strategy(stock1_data, stock2_data)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), gridspec_kw={'height_ratios': [2, 1]})
ax1.plot(pairs_result.index, pairs_result['stock1'], label=stock1_ticker)
ax1.plot(pairs_result.index, pairs_result['stock2'], label=stock2_ticker)
ax1.set_title(f'Pairs Trading: {stock1_ticker} vs {stock2_ticker}')
ax1.set_ylabel('Price')
ax1.legend()
ax1.grid(True)
ax2.plot(pairs_result.index, pairs_result['z_score'], color='purple', label='Z-score')
ax2.axhline(y=2, color='r', linestyle='--')
ax2.axhline(y=-2, color='g', linestyle='--')
ax2.axhline(y=0, color='k', linestyle='-')
ax2.fill_between(pairs_result.index, y1=2, y2=4, color='red', alpha=0.1)
ax2.fill_between(pairs_result.index, y1=-4, y2=-2, color='green', alpha=0.1)
ax2.set_ylabel('Z-score')
ax2.set_xlabel('Date')
ax2.grid(True)
buy_signals = pairs_result[pairs_result['signal'] == 1]
sell_signals = pairs_result[pairs_result['signal'] == -1]
ax2.scatter(buy_signals.index, buy_signals['z_score'], marker='^', color='g', s=100)
ax2.scatter(sell_signals.index, sell_signals['z_score'], marker='v', color='r', s=100)
plt.tight_layout()
plt.show()
Quản lý rủi ro cho chiến lược Mean Reversion
Quản lý rủi ro đặc biệt quan trọng đối với Mean Reversion vì thị trường có thể tiếp tục di chuyển xa khỏi mức trung bình trong thời gian dài hơn dự kiến.
1. Stop-Loss
Luôn đặt stop-loss để bảo vệ vốn khi thị trường không hồi quy như dự đoán.
def apply_stop_loss(signals, price_data, stop_loss_pct=0.05):
"""
Áp dụng stop-loss cho tín hiệu giao dịch
Tham số:
- signals: DataFrame chứa tín hiệu
- price_data: DataFrame chứa dữ liệu giá
- stop_loss_pct: Phần trăm stop-loss (0.05 = 5%)
Trả về:
- DataFrame với tín hiệu đã điều chỉnh
"""
result = signals.copy()
result['Position'] = 0
result['Entry_Price'] = 0
for i in range(1, len(result)):
prev_pos = result.iloc[i-1]['Position']
curr_signal = result.iloc[i]['Signal']
curr_price = price_data.iloc[i]['Close']
if prev_pos == 0 and curr_signal != 0:
result.iloc[i, result.columns.get_loc('Position')] = curr_signal
result.iloc[i, result.columns.get_loc('Entry_Price')] = curr_price
elif prev_pos != 0:
entry_price = result.iloc[i-1]['Entry_Price']
if prev_pos == 1 and curr_price < entry_price * (1 - stop_loss_pct):
result.iloc[i, result.columns.get_loc('Position')] = 0
result.iloc[i, result.columns.get_loc('Entry_Price')] = 0
elif prev_pos == -1 and curr_price > entry_price * (1 + stop_loss_pct):
result.iloc[i, result.columns.get_loc('Position')] = 0
result.iloc[i, result.columns.get_loc('Entry_Price')] = 0
else:
result.iloc[i, result.columns.get_loc('Position')] = prev_pos
result.iloc[i, result.columns.get_loc('Entry_Price')] = entry_price
return result
2. Position Sizing
Điều chỉnh kích thước vị thế dựa trên mức độ lệch khỏi giá trị trung bình - đầu tư nhiều hơn khi khả năng hồi quy cao hơn.
def calculate_position_size(z_score, max_position_size, max_z_score=3):
"""
Tính kích thước vị thế dựa trên Z-score
Tham số:
- z_score: Z-score hiện tại
- max_position_size: Kích thước vị thế tối đa
- max_z_score: Z-score tối đa được xem xét
Trả về:
- Kích thước vị thế
"""
capped_z_score = min(abs(z_score), max_z_score)
position_size = (capped_z_score / max_z_score) * max_position_size
if z_score < 0:
return position_size
else:
return -position_size
3. Time-based Exit
Đặt giới hạn thời gian cho giao dịch - đóng vị thế nếu không hồi quy trong khoảng thời gian nhất định.
def apply_time_exit(signals, max_holding_days=10):
"""
Áp dụng exit dựa trên thời gian
Tham số:
- signals: DataFrame chứa tín hiệu và vị thế
- max_holding_days: Số ngày tối đa giữ vị thế
Trả về:
- DataFrame với vị thế đã điều chỉnh
"""
result = signals.copy()
result['Holding_Days'] = 0
for i in range(1, len(result)):
prev_pos = result.iloc[i-1]['Position']
curr_pos = result.iloc[i]['Position']
if curr_pos == 0:
result.iloc[i, result.columns.get_loc('Holding_Days')] = 0
elif curr_pos == prev_pos:
holding_days = result.iloc[i-1]['Holding_Days'] + 1
result.iloc[i, result.columns.get_loc('Holding_Days')] = holding_days
if holding_days >= max_holding_days:
result.iloc[i, result.columns.get_loc('Position')] = 0
result.iloc[i, result.columns.get_loc('Holding_Days')] = 0
else:
result.iloc[i, result.columns.get_loc('Holding_Days')] = 1
return result
Thách thức và hạn chế của Mean Reversion
Mặc dù Mean Reversion là một chiến lược mạnh mẽ, nhưng nó cũng có những thách thức và hạn chế nhất định:
1. "Catching a falling knife" (Bắt dao rơi)
Giao dịch khi tài sản đang giảm mạnh có thể giống như việc bắt dao rơi - rất nguy hiểm. Giá có thể tiếp tục giảm nhiều hơn dự kiến trước khi hồi phục.
2. Thay đổi trong đặc tính thống kê
Đặc tính thống kê của tài sản có thể thay đổi theo thời gian. Một tài sản từng có đặc tính Mean Reversion có thể chuyển sang trạng thái trending hoặc random walk.
3. Khó xác định mức trung bình đúng
Mức trung bình mà giá hồi quy về có thể không cố định và thay đổi theo thời gian, đặc biệt trong thị trường có xu hướng (trending markets).
4. Tác động của các sự kiện đột biến
Các sự kiện như báo cáo thu nhập, thay đổi chính sách, hoặc tin tức quan trọng có thể phá vỡ mô hình Mean Reversion.
Kết luận
Chiến lược Mean Reversion là một công cụ mạnh mẽ trong bộ công cụ của nhà giao dịch định lượng. Bằng cách khai thác xu hướng tự nhiên của thị trường trong việc điều chỉnh về trạng thái cân bằng, chiến lược này có thể tạo ra lợi nhuận đáng kể trong các thị trường ngang hoặc dao động.
Tuy nhiên, thành công trong việc áp dụng Mean Reversion phụ thuộc vào việc:
- Lựa chọn đúng tài sản có đặc tính hồi quy
- Sử dụng các kỹ thuật phân tích phù hợp để xác định điểm vào/ra
- Quản lý rủi ro nghiêm ngặt để bảo vệ vốn
Khi được thực hiện một cách có kỷ luật và được hỗ trợ bởi phân tích thống kê vững chắc, Mean Reversion có thể là một chiến lược giao dịch hiệu quả và có lợi nhuận ổn định trong dài hạn.
Bạn đã có kinh nghiệm với chiến lược Mean Reversion chưa? Bạn thích sử dụng chỉ báo nào nhất để xác định điểm vào/ra? Hãy chia sẻ ý kiến của bạn trong phần bình luận nhé!