Backtesting là bước bắt buộc trước khi deploy bất kỳ chiến lược giao dịch nào. Bài viết này hướng dẫn thực hành backtest chiến lược chứng khoán Việt Nam bằng Python.
Tại Sao Backtest Quan Trọng?
Backtest cho biết chiến lược hoạt động như thế nào trên dữ liệu quá khứ — trước khi đặt tiền thật vào rủi ro. Tuy nhiên: past performance không đảm bảo future results.
Các chỉ số cần quan tâm:
- Total Return (%): Tổng lợi nhuận
- Sharpe Ratio: Lợi nhuận điều chỉnh rủi ro. > 1.0 tốt, > 2.0 xuất sắc
- Max Drawdown (%): Thua lỗ tối đa từ đỉnh xuống đáy. < 20% chấp nhận được
- Win Rate (%): Tỷ lệ lệnh thắng
- Profit Factor: Tổng lời / Tổng lỗ. > 1.3 là tốt
Cài Đặt
pip install backtesting pandas numpy
Backtest Với backtesting.py
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
import pandas as pd
class MACross(Strategy):
fast = 9
slow = 21
def init(self):
self.ema_fast = self.I(
lambda x: pd.Series(x).ewm(span=self.fast).mean(),
self.data.Close
)
self.ema_slow = self.I(
lambda x: pd.Series(x).ewm(span=self.slow).mean(),
self.data.Close
)
def next(self):
if crossover(self.ema_fast, self.ema_slow):
self.buy()
elif crossover(self.ema_slow, self.ema_fast):
self.position.close()
# df phải có columns: Open, High, Low, Close, Volume, index là datetime
bt = Backtest(df, MACross, cash=100_000_000, commission=0.0025)
stats = bt.run()
print(stats)
bt.plot()
Optimization — Tìm Tham Số Tốt Nhất
stats, heatmap = bt.optimize(
fast=range(5, 20, 2),
slow=range(15, 35, 2),
maximize='Sharpe Ratio',
constraint=lambda p: p.fast < p.slow,
return_heatmap=True
)
print(f"Best: fast={stats._strategy.fast}, slow={stats._strategy.slow}")
print(f"Sharpe: {stats['Sharpe Ratio']:.2f}")
print(f"Drawdown: {stats['Max. Drawdown [%]']:.1f}%")
Backtest Nhiều Mã Cùng Lúc Với vectorbt
import vectorbt as vbt
# prices: DataFrame với index=date, columns=symbol names
fast_ma = vbt.MA.run(prices, window=9)
slow_ma = vbt.MA.run(prices, window=21)
entries = fast_ma.ma_crossed_above(slow_ma)
exits = fast_ma.ma_crossed_below(slow_ma)
portfolio = vbt.Portfolio.from_signals(
prices, entries, exits,
init_cash=100_000_000,
fees=0.0025
)
print(portfolio.stats())
Cái Bẫy Lớn Nhất: Overfitting
Overfitting xảy ra khi tối ưu quá nhiều tham số trên cùng một dataset — bot hoàn hảo trong backtest nhưng thất bại trong live trading.
Cách tránh:
- Train/Test split: 70% để optimize, 30% để validate (không được dùng khi optimize)
- Walk-forward analysis: Backtest cuốn chiếu theo thời gian thực
- Giữ ít tham số nhất có thể — mỗi tham số thêm vào là thêm nguồn overfitting
Kết Luận
Backtest tốt không đảm bảo live trading thành công, nhưng backtest kém gần như chắc chắn dẫn đến thất bại. Hãy coi backtest như điều kiện cần — không phải điều kiện đủ.
Học phương pháp backtest và phân tích định lượng chuyên nghiệp tại Hướng Nghiệp Dữ Liệu.
Câu hỏi thường gặp
Nên dùng backtesting.py hay vectorbt?
backtesting.py dễ học, cú pháp ngắn gọn và có biểu đồ trực quan, phù hợp khi bạn kiểm tra một chiến lược đơn lẻ. vectorbt nhanh hơn nhiều lần nhờ vector hóa và hỗ trợ quét hàng nghìn bộ tham số, phù hợp khi cần tối ưu hóa hoặc backtest trên nhiều mã cổ phiếu.
Sharpe Ratio bao nhiêu là đủ tốt?
Sharpe trên 1,0 được coi là chấp nhận được, trên 1,5 là tốt và trên 2,0 là rất tốt cho chiến lược giao dịch. Tuy nhiên Sharpe tính trên dữ liệu quá khứ không đảm bảo cho tương lai, và Sharpe cao trên tập dữ liệu nhỏ thường là dấu hiệu overfitting.
Làm sao biết backtest kết quả đẹp là do overfitting?
Bốn dấu hiệu thường gặp: kết quả chỉ đẹp trong một khoảng thời gian rất cụ thể, hiệu suất sụp đổ ngay khi thay đổi nhỏ tham số, số lệnh quá ít so với độ dài dữ liệu, và chiến lược không có lợi nhuận khi bỏ phí giao dịch. Cách kiểm tra chuẩn là tách dữ liệu thành tập in-sample và out-of-sample, không tối ưu trên tập out-of-sample.
