← Quay lại danh sách bài viết
March 6, 2026
5 phút đọc

Monte Carlo Bootstrap: Cách Lấy Khoảng Tin Cậy cho Backtest trong 10 Dòng Code

Monte Carlo Bootstrap: Cách Lấy Khoảng Tin Cậy cho Backtest trong 10 Dòng Code
#algotrading
#backtest
#Monte Carlo
#bootstrap
#khoảng tin cậy
#quản lý rủi ro
#thống kê

Bạn chạy một chiến lược qua backtest. Kết quả PnL +42%, Sharpe 1.8, MaxDD -12%. Kết quả trông rất ấn tượng. Bạn triển khai bot vào thực tế, và một tháng sau phát hiện drawdown đã là -28% còn PnL đang hướng về không.

Điều gì đã sai? Đó không phải lỗi kỹ thuật, cũng không phải "thị trường đã thay đổi." Vấn đề là bạn đã ra quyết định dựa trên một con số duy nhất — một ước lượng đơn điểm. Bạn biết rằng chiến lược đã cho +42%, nhưng bạn không biết mức độ tin cậy của con số đó.

Vấn đề của Ước lượng Đơn Điểm

Ước lượng đơn điểm so với phân phối xác suất đầy đủ Một điểm dữ liệu duy nhất (trái) cho hình ảnh sai lệch, trong khi phân phối đầy đủ (phải) tiết lộ phạm vi thực sự của các kết quả có thể xảy ra.

Backtest trên dữ liệu lịch sử là một lần chạy qua một chuỗi sự kiện thị trường cụ thể. Kết quả phụ thuộc vào thứ tự các giao dịch: cùng một chiến lược với cùng các giao dịch, nhưng theo thứ tự khác, có thể cho ra mức drawdown tối đa hoàn toàn khác.

Hãy tưởng tượng 491 giao dịch. Mỗi giao dịch là một sự kiện ngẫu nhiên với phân phối lợi nhuận nhất định. Backtest lịch sử chỉ cho thấy một hiện thực hóa của quá trình này. Đó giống như tung xúc xắc một lần và kết luận rằng xúc xắc luôn ra bốn.

Những gì chúng ta thực sự cần:

  • Không phải ước lượng điểm, mà là khoảng: "với xác suất 95%, PnL cuối cùng sẽ nằm trong khoảng X và Y"
  • Không phải một mức drawdown tối đa, mà là phân phối: "trong 5% tình huống tệ nhất, drawdown vượt quá Z%"
  • Không phải giá trị trung bình, mà là đuôi phân phối: điều gì xảy ra nếu may mắn không đứng về phía bạn?

Đây chính xác là mục đích của Monte Carlo bootstrap.

Monte Carlo Bootstrap là Gì

Monte Carlo bootstrap resampling: hàng nghìn đường equity thay thế được tạo ra từ dữ liệu giao dịch Bootstrap tạo ra hàng nghìn quỹ đạo equity thay thế bằng cách lấy mẫu lại các giao dịch có hoàn trả từ tập dữ liệu gốc.

Bootstrap là phương pháp lấy mẫu lại được Bradley Efron đề xuất vào năm 1979. Ý tưởng rất tinh tế: nếu chúng ta có một mẫu dữ liệu, chúng ta có thể tạo ra hàng nghìn mẫu "mới" bằng cách chọn ngẫu nhiên các phần tử từ mẫu gốc có hoàn trả.

Trong bối cảnh backtest, nó hoạt động như sau:

  1. Bạn có một mảng lợi nhuận cho mỗi giao dịch — ví dụ: 491 giá trị
  2. Bạn chọn ngẫu nhiên 491 giá trị từ mảng này có hoàn trả — một số giao dịch sẽ xuất hiện hai lần, một số sẽ không xuất hiện
  3. Bạn xây dựng đường equity từ mẫu mới này
  4. Bạn lặp lại 10.000 lần
  5. Bạn nhận được phân phối của các chỉ số cuối cùng, không phải một con số duy nhất

Mỗi lần lặp là một "kịch bản thay thế": điều gì có thể đã xảy ra nếu thứ tự và tập hợp giao dịch hơi khác một chút.

Triển khai trong 10 Dòng

Đây là triển khai hoàn chỉnh đang hoạt động:

import numpy as np

def max_drawdown(equity_curve):
    """Calculate the maximum drawdown of an equity curve."""
    peak = np.maximum.accumulate(equity_curve)
    drawdown = (equity_curve - peak) / peak
    return drawdown.min()

trade_returns = [...]  # 491 values, e.g. [0.012, -0.005, 0.008, ...]

n_simulations = 10000
results = []

for _ in range(n_simulations):
    sampled = np.random.choice(trade_returns, size=len(trade_returns), replace=True)
    equity = np.cumprod(1 + sampled)
    results.append({
        "final_pnl": equity[-1] - 1,
        "max_dd": max_drawdown(equity),
        "sharpe": np.mean(sampled) / np.std(sampled) * np.sqrt(252)
    })

Thời gian thực thi: ~2 giây trên laptop thông thường. 10.000 lịch sử thay thế của chiến lược bạn.

Trích xuất Khoảng Tin Cậy

Khoảng tin cậy cho PnL, MaxDD và Sharpe Ratio với phân vị thứ 5, 50 và 95 Khoảng tin cậy cho các chỉ số chiến lược chính: PnL, MaxDD và Sharpe Ratio, thể hiện phân vị thứ 5 (tệ nhất), thứ 50 (trung vị) và thứ 95 (tốt nhất).

Bây giờ chúng ta không có một con số, mà là một phân phối. Đây là cách trích xuất thông tin hữu ích từ nó:

import pandas as pd

df = pd.DataFrame(results)

pnl_5 = np.percentile(df['final_pnl'], 5)
pnl_50 = np.percentile(df['final_pnl'], 50)
pnl_95 = np.percentile(df['final_pnl'], 95)

dd_5 = np.percentile(df['max_dd'], 5)    # 5th — worst case
dd_50 = np.percentile(df['max_dd'], 50)
dd_95 = np.percentile(df['max_dd'], 95)  # 95th — best case

print(f"PnL:   {pnl_5:.1%} | {pnl_50:.1%} | {pnl_95:.1%}")
print(f"MaxDD: {dd_5:.1%} | {dd_50:.1%} | {dd_95:.1%}")
print(f"Sharpe: {np.percentile(df['sharpe'], 5):.2f}{np.percentile(df['sharpe'], 95):.2f}")

Ví dụ đầu ra cho một chiến lược thực:

Chỉ số Phân vị thứ 5 (tệ nhất) Trung vị Phân vị thứ 95 (tốt nhất)
PnL +18.3% +41.7% +72.1%
MaxDD -23.4% -12.8% -5.1%
Sharpe 1.12 1.76 2.41

Bây giờ sự khác biệt rất rõ ràng:

  • Backtest cho thấy PnL +42% — nhưng trong 5% kịch bản tệ nhất, PnL chỉ là +18.3%
  • Backtest cho thấy MaxDD -12% — nhưng trong 5% kịch bản tệ nhất, drawdown là -23.4%
  • Sharpe 1.8 — nhưng giới hạn dưới là 1.12

Phân vị thứ 5 là "trường hợp tệ nhất thực tế" của bạn. Nếu chiến lược không còn sinh lời ở phân vị thứ 5, việc triển khai vào thực tế là rủi ro.

Trực quan hóa: Biểu đồ Quạt

Monte Carlo bootstrap được trực quan hóa tự nhiên dưới dạng biểu đồ quạt — một bó các đường equity:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(16, 6))

ax = axes[0]
for i in range(min(500, n_simulations)):
    sampled = np.random.choice(trade_returns, size=len(trade_returns), replace=True)
    equity = np.cumprod(1 + sampled)
    ax.plot(equity, alpha=0.02, color='#4FC3F7')

all_equities = []
for _ in range(n_simulations):
    sampled = np.random.choice(trade_returns, size=len(trade_returns), replace=True)
    equity = np.cumprod(1 + sampled)
    all_equities.append(equity)

all_equities = np.array(all_equities)
p5 = np.percentile(all_equities, 5, axis=0)
p50 = np.percentile(all_equities, 50, axis=0)
p95 = np.percentile(all_equities, 95, axis=0)

ax.fill_between(range(len(p5)), p5, p95, alpha=0.3, color='#7C4DFF', label='90% CI')
ax.plot(p50, color='#E040FB', linewidth=2, label='Median')
ax.set_title('Monte Carlo Bootstrap: Equity Curves')
ax.legend()

ax = axes[1]
ax.hist(df['final_pnl'] * 100, bins=80, color='#4FC3F7', alpha=0.7, edgecolor='#1A237E')
ax.axvline(pnl_5 * 100, color='#FF5252', linestyle='--', label=f'5th: {pnl_5:.1%}')
ax.axvline(pnl_50 * 100, color='#E040FB', linestyle='--', label=f'Median: {pnl_50:.1%}')
ax.axvline(pnl_95 * 100, color='#69F0AE', linestyle='--', label=f'95th: {pnl_95:.1%}')
ax.set_title('Distribution of Final PnL')
ax.set_xlabel('PnL, %')
ax.legend()

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

Biểu đồ quạt cung cấp sự hiểu biết trực quan về độ phân tán của các kết quả có thể. Quạt hẹp có nghĩa là chiến lược ổn định. Quạt rộng có nghĩa là kết quả phụ thuộc nhiều vào "may mắn" với thứ tự các giao dịch.

Trực quan hóa Monte Carlo: Biểu đồ Quạt và Histogram Phân phối Biểu đồ quạt (trái) thể hiện độ phân tán của các quỹ đạo equity có thể, và histogram (phải) thể hiện phân phối mật độ của lợi nhuận cuối cùng với các khoảng tin cậy được đánh dấu (5%, 50%, 95%).

Phân tích Nâng cao: Xác suất Phá sản

Phân tích xác suất phá sản: các đường equity sống sót hoặc rơi vào phá sản Trực quan hóa xác suất phá sản: các đường equity sống sót (xanh lam) đi lên trong khi các đường phá sản (đỏ) rơi xuống dưới ngưỡng không.

Bootstrap cho phép bạn trả lời một câu hỏi quan trọng: xác suất chiến lược thua mất X% vốn là bao nhiêu?

ruin_threshold = -0.20
prob_ruin = (df['max_dd'] < ruin_threshold).mean()
print(f"P(MaxDD < -20%) = {prob_ruin:.1%}")

prob_loss = (df['final_pnl'] < 0).mean()
print(f"P(PnL < 0) = {prob_loss:.1%}")

worst_5pct = df['final_pnl'].quantile(0.05)
cvar = df[df['final_pnl'] <= worst_5pct]['final_pnl'].mean()
print(f"CVaR(5%) = {cvar:.1%}")

Các chỉ số này không thể có được từ một lần chạy backtest duy nhất. Tuy nhiên, chúng rất quan trọng để đưa ra quyết định triển khai chiến lược.

Để biết thêm về lý do tại sao drawdown sâu lại nguy hiểm về mặt toán học và cách tính bất đối xứng lợi nhuận hoạt động, hãy đọc bài viết của chúng tôi Bất đối xứng Lỗ-Lãi.

Khi Nào Bootstrap Cổ điển Không Hoạt động

Phương pháp có những hạn chế quan trọng cần biết.

Tự tương quan của Lợi nhuận

Bootstrap cổ điển giả định rằng các giao dịch là độc lập. Trong thực tế, điều này thường không đúng — một chiến lược có thể có các chuỗi thắng và thua liên tiếp. Nếu tự tương quan đáng kể, hãy sử dụng block bootstrap:

def block_bootstrap(returns, block_size=10, n_simulations=10000):
    """Bootstrap preserving local dependency structure."""
    n = len(returns)
    results = []

    for _ in range(n_simulations):
        starts = np.random.randint(0, n - block_size + 1, size=n // block_size + 1)
        sampled = np.concatenate([returns[s:s+block_size] for s in starts])[:n]
        equity = np.cumprod(1 + sampled)
        results.append({
            "final_pnl": equity[-1] - 1,
            "max_dd": max_drawdown(equity),
        })

    return pd.DataFrame(results)

Block bootstrap bảo toàn các phụ thuộc cục bộ giữa các giao dịch liên tiếp, cung cấp khoảng tin cậy thực tế hơn cho MaxDD.

Block bootstrap resampling: các khối giao dịch tuần tự được xáo trộn và kết hợp lại Block bootstrap bảo toàn tự tương quan trong các khối bằng cách phân chia chuỗi giao dịch thành các khối và lấy mẫu lại chúng có hoàn trả.

Tính phi Dừng của Thị trường

Bootstrap hoạt động với phân phối giao dịch gốc. Nếu thị trường đã thay đổi cấu trúc (ví dụ: biến động giảm hoặc thanh khoản thay đổi), các giao dịch lịch sử có thể không đại diện. Để tính đến điều này:

  • Sử dụng cửa sổ trượt: chỉ bootstrap trên N giao dịch gần nhất
  • Tăng trọng số cho các giao dịch gần đây hơn: weighted bootstrap
  • Phân chia dữ liệu theo chế độ thị trường và bootstrap riêng biệt

Số lượng Giao dịch Nhỏ

Bootstrap đáng tin cậy khi n > 30 giao dịch. Nếu bạn có 10 giao dịch — không có lượng lấy mẫu lại nào có thể giúp ích. 491 giao dịch là mẫu tuyệt vời; bạn có thể tin tưởng kết quả.

So sánh các Phương pháp Đánh giá Độ Bền của Backtest

Phương pháp Cung cấp gì Độ phức tạp Thời gian Khi nào sử dụng
Backtest đơn Một ước lượng điểm Tối thiểu Giây Không bao giờ là kết quả cuối cùng
Walk-forward Chỉ số ngoài mẫu Trung bình Phút Để kiểm tra overfitting
Monte Carlo bootstrap Khoảng tin cậy Tối thiểu ~2 giây Luôn luôn trước khi triển khai
Monte Carlo path Đường giá mới Cao Phút-giờ Để stress testing
Cross-validation Chỉ số trung bình qua các fold Trung bình Phút Để điều chỉnh tham số

Monte Carlo bootstrap là phương pháp duy nhất trong thời gian tối thiểu cung cấp bức tranh đầy đủ về rủi ro.

Danh sách Kiểm tra: Giải thích Kết quả

Đây là cách chúng tôi khuyến nghị giải thích kết quả Monte Carlo bootstrap:

Triển khai vào thực tế nếu:

  • PnL ở phân vị thứ 5 là dương
  • MaxDD ở phân vị thứ 5 chấp nhận được với khẩu vị rủi ro của bạn
  • Xác suất phá sản < 1%
  • Sharpe ở phân vị thứ 5 > 0.5

Cần cải thiện nếu:

  • PnL ở phân vị thứ 5 gần zero
  • MaxDD ở phân vị thứ 5 tệ hơn đáng kể so với phân vị thứ 50
  • Biểu đồ quạt có độ phân tán rộng — chiến lược không ổn định

Không triển khai nếu:

  • PnL ở phân vị thứ 5 là âm
  • Xác suất phá sản > 5%
  • Khoảng tin cậy cho Sharpe bao gồm 0

Kinh nghiệm của Chúng tôi tại marketmaker.cc

Tại marketmaker.cc, chúng tôi phát triển engine backtest riêng và Monte Carlo bootstrap là một phần không thể thiếu trong pipeline của chúng tôi. Mỗi chiến lược đều trải qua bootstrap tự động trước khi được phê duyệt cho giao dịch thực.

Chúng tôi đã tích hợp bootstrap trực tiếp vào engine backtest: sau một lần chạy, bạn nhận được không chỉ PnL cuối cùng mà còn một báo cáo đầy đủ với các khoảng tin cậy, biểu đồ quạt, xác suất phá sản và so sánh block vs. standard bootstrap. Điều này mất thêm 2-3 giây — một cái giá không đáng kể để hiểu rủi ro thực sự.

Từ kinh nghiệm của chúng tôi: khoảng 30% chiến lược trông hấp dẫn theo ước lượng đơn điểm bị lọc ra sau Monte Carlo bootstrap. PnL ở phân vị thứ 5 của chúng trở nên âm hoặc MaxDD hóa ra không chấp nhận được. Nếu không có bootstrap, những chiến lược này sẽ đi vào thực tế và rất có thể dẫn đến thua lỗ.

Kết luận

Monte Carlo bootstrap là ~10 dòng code và ~2 giây tính toán. Nó biến đổi một con số từ backtest thành phân phối đầy đủ với các khoảng tin cậy. Đây có lẽ là ROI cao nhất trong số các công cụ phân tích định lượng:

  • Chi phí tối thiểu: triển khai trong 30 phút
  • Lợi ích tối đa: hiểu rõ rủi ro thực sự của chiến lược
  • Không phụ thuộc: chỉ cần NumPy

Nếu bạn chưa sử dụng bootstrap — hãy thêm vào pipeline của bạn ngay hôm nay. Đó là cách duy nhất để biết mức độ tin cậy của kết quả backtest.


Tài liệu Tham khảo

  1. Efron, B. — Bootstrap Methods: Another Look at the Jackknife (1979)
  2. Davison, A.C., Hinkley, D.V. — Bootstrap Methods and their Application (Cambridge)
  3. Aronson, D.R. — Evidence-Based Technical Analysis: Monte Carlo permutation
  4. QuantStart — Monte Carlo Simulation for Backtest Analysis
  5. Marcos Lopez de Prado — Advances in Financial Machine Learning, Chapter 12: Backtesting
  6. Kevin Davey — Building Winning Algorithmic Trading Systems: Monte Carlo Analysis
  7. NumPy — numpy.random.choice

Trích dẫn

@software{soloviov2026montecarlobootstrap,
  author = {Soloviov, Eugen},
  title = {Monte Carlo Bootstrap: How to Get Confidence Intervals for a Backtest in 10 Lines of Code},
  year = {2026},
  url = {https://marketmaker.cc/vi/blog/post/monte-carlo-bootstrap-backtest},
  version = {0.1.0},
  description = {Tại sao ước lượng đơn điểm từ backtest là một ảo tưởng nguy hiểm. Làm thế nào Monte Carlo bootstrap trong 2 giây tính toán cho bạn khoảng tin cậy 95% cho PnL và MaxDD, và tại sao đây là bước bắt buộc trước khi triển khai chiến lược vào thực tế.}
}
Tuyên bố miễn trừ trách nhiệm: Thông tin được cung cấp trong bài viết này chỉ nhằm mục đích giáo dục và thông tin, không cấu thành lời khuyên về tài chính, đầu tư hoặc giao dịch. Giao dịch tiền mã hóa tiềm ẩn rủi ro thua lỗ đáng kể.

Tác Giả

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

Đi Trước Thị Trường

Đăng ký nhận bản tin của chúng tôi để có những thông tin chuyên sâu độc quyền về AI trading, phân tích thị trường và các cập nhật nền tảng.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất kỳ lúc nào.