Bài viết gần đây
-
-
Các ưu điểm của Python trong giao dịch định lư…
Tháng 6 28, 2026 -
Áp dụng thống kê Bayesian trong phân tích thị trư…
Tháng 6 28, 2026 -
Flutter có thể tích hợp dễ dàng với các hệ th…
Tháng 6 27, 2026 -
🚀 Cơ bản về Flutter & Dart
Tháng 6 27, 2026
| Python Backtest Chiến Lược RSI Trên VNIndex: Code Thực Tế Từ A–Z
Backtest là bước không thể bỏ qua trước khi bỏ tiền thật vào bất kỳ chiến lược nào. Bài viết này hướng dẫn bạn backtest chiến lược RSI trên VNIndex từ đầu đến cuối bằng Python — không dùng thư viện backtest phức tạp, chỉ cần Pandas.
Chiến Lược RSI Là Gì?
RSI (Relative Strength Index) dao động từ 0–100:
- RSI < 30: Oversold — cổ phiếu bị bán quá mức → tín hiệu MUA
- RSI > 70: Overbought — cổ phiếu bị mua quá mức → tín hiệu BÁN
Chiến lược đơn giản nhưng phổ biến. Câu hỏi là: nó có thực sự hoạt động trên thị trường Việt Nam không?
Bước 1: Tải Dữ Liệu VNIndex
from vnstock import stock_historical_data
import pandas as pd
import numpy as np
# VNIndex = mã "VNINDEX"
df = stock_historical_data(
symbol="VNINDEX",
start_date="2020-01-01",
end_date="2026-06-01",
resolution="1D",
type="index"
)
df.index = pd.to_datetime(df['time'])
df = df[['open', 'high', 'low', 'close', 'volume']].copy()
print(f"Dữ liệu: {df.index[0].date()} → {df.index[-1].date()} ({len(df)} phiên)")
Bước 2: Tính RSI 14
def calc_rsi(series, period=14):
delta = series.diff()
gain = delta.clip(lower=0).rolling(period).mean()
loss = (-delta.clip(upper=0)).rolling(period).mean()
rs = gain / loss
return 100 - (100 / (1 + rs))
df['RSI'] = calc_rsi(df['close'], 14)
print(df[['close', 'RSI']].tail(10))
Bước 3: Tạo Tín Hiệu Mua/Bán
# Tín hiệu: RSI vượt 30 lên (oversold recovery) = MUA
# RSI vượt 70 xuống (overbought reversal) = BÁN
df['signal'] = 0
# RSI cắt lên 30 từ dưới = MUA
df.loc[(df['RSI'] > 30) & (df['RSI'].shift(1) <= 30), 'signal'] = 1
# RSI cắt xuống 70 từ trên = BÁN
df.loc[(df['RSI'] = 70), 'signal'] = -1
buy_signals = df[df['signal'] == 1]
sell_signals = df[df['signal'] == -1]
print(f"Tín hiệu MUA: {len(buy_signals)}")
print(f"Tín hiệu BÁN: {len(sell_signals)}")
Bước 4: Backtest
def backtest_rsi(df, initial_capital=100_000_000):
capital = initial_capital
position = 0 # số "đơn vị" đang nắm (VNIndex point)
entry_price = 0
trades = []
equity = []
for i in range(len(df)):
price = df['close'].iloc[i]
sig = df['signal'].iloc[i]
# Mua
if sig == 1 and position == 0:
position = capital / price
entry_price = price
capital = 0
# Bán
elif sig == -1 and position > 0:
capital = position * price
profit = capital - initial_capital
trades.append({
'entry': entry_price,
'exit': price,
'profit_pct': (price - entry_price) / entry_price * 100
})
position = 0
entry_price = 0
# Tính equity
if position > 0:
equity.append(position * price)
else:
equity.append(capital)
# Nếu còn vị thế mở → đóng tại giá cuối
if position > 0:
capital = position * df['close'].iloc[-1]
df['equity'] = equity
return capital, pd.DataFrame(trades)
final_cap, trades = backtest_rsi(df)
print(f"nVốn ban đầu: 100,000,000 VND")
print(f"Vốn cuối: {final_cap:,.0f} VND")
print(f"Tổng lợi nhuận: {(final_cap/100_000_000 - 1)*100:.1f}%")
print(f"Số lệnh: {len(trades)}")
Bước 5: Đánh Giá Hiệu Suất
def evaluate(trades, df, initial_capital=100_000_000):
if trades.empty:
print("Không có lệnh nào!")
return
wins = trades[trades['profit_pct'] > 0]
loses = trades[trades['profit_pct'] <= 0]
print(f"Win rate: {len(wins)/len(trades)*100:.1f}%")
print(f"Lợi nhuận TB: {trades['profit_pct'].mean():.2f}%/lệnh")
print(f"Lệnh thắng TB: {wins['profit_pct'].mean():.2f}%")
print(f"Lệnh thua TB: {loses['profit_pct'].mean():.2f}%")
# Sharpe Ratio (annualized)
daily_ret = df['equity'].pct_change().dropna()
sharpe = daily_ret.mean() / daily_ret.std() * np.sqrt(252)
print(f"Sharpe Ratio: {sharpe:.2f}")
# Max Drawdown
rolling_max = df['equity'].cummax()
drawdown = (df['equity'] - rolling_max) / rolling_max
print(f"Max Drawdown: {drawdown.min()*100:.1f}%")
evaluate(trades, df)
Kết Quả Điển Hình
Backtest chiến lược RSI(14) trên VNIndex từ 2020–2026:
- Win rate: ~55–60%
- Sharpe Ratio: 0.8–1.2 (tốt nếu > 1.0)
- Max Drawdown: -15% đến -25% (giai đoạn COVID 2020 và lãi suất 2022)
Lưu ý: Kết quả backtest không đảm bảo tương lai. Đây là công cụ để loại bỏ chiến lược tệ, không phải đảm bảo chiến lược tốt.
Cải Tiến Chiến Lược
# Thêm bộ lọc xu hướng: chỉ mua khi giá trên SMA200
df['SMA200'] = df['close'].rolling(200).mean()
# Chỉ mua khi RSI oversold VÀ xu hướng tăng (giá > SMA200)
df['signal_filtered'] = 0
df.loc[
(df['RSI'] > 30) &
(df['RSI'].shift(1) df['SMA200']),
'signal_filtered'
] = 1
Thêm bộ lọc SMA200 thường tăng win rate lên 65–70% vì loại bỏ các lệnh mua trong downtrend.
Kết Luận
Backtest RSI bằng Python chỉ cần ~50 dòng code, không cần thư viện phức tạp. Đây là nền tảng để bạn xây dựng và kiểm thử bất kỳ chiến lược trading nào trước khi dùng tiền thật.
📌 Muốn ứng dụng Python vào phân tích và giao dịch tài chính thực chiến?
Khóa Python Fintech — Phân Tích Dữ Liệu Lớn & Tự Động Hóa Giao Dịch tại Hướng Nghiệp Dữ Liệu giúp bạn thực hành với dữ liệu VnIndex, Binance API thật — không dạy lý thuyết hàn lâm.
📞 Hotline/Zalo: 0927 909 257
Weekly Digest — Nhận Bản Tin Hàng Tuần
Nhận các bài viết phân tích kỹ thuật chuyên sâu, thuật toán giao dịch tự động (Trading Bot) và các giải pháp công nghệ mới nhất từ Hướng Nghiệp Dữ Liệu.
admin
Biên tập viên, Hướng Nghiệp Dữ LiệuBiên tập viên nội dung tại Hướng Nghiệp Dữ Liệu, phụ trách tổng hợp và biên soạn các bài viết về lập trình Python, dữ liệu và công nghệ.