← Мақалаларға оралу
March 6, 2026
5 мин оқу

Монте-Карло бутстрэп: бэктест үшін сенімділік интервалдарын небәрі 10 жол кодпен қалай алуға болады

#algotrading
#backtest
#Monte Carlo
#bootstrap
#confidence intervals
#risk management
#statistics
🎯
Part 5 of 9 · Collection
Backtesting Without Fooling Yourself

📄 Бұл мақала зерттеу мақаласына айналды. Мұнда сипатталған бутстрэп сенімділік интервалдары серияттық тәуелділік жағдайында бақыланатын coverage тестінен өтті (iid vs trade-level vs block bootstrap, шындығы дәл белгілі 6000 эксперимент). Мақаланы онлайн (интерактивті нұсқа + PDF) bootstrap.marketmaker.cc сайтынан оқыңыз, код пен деректер github.com/suenot/bootstrap-coverage мекенжайында.

Сіз стратегияны бэктест арқылы жүргіздіңіз. PnL +42%, Sharpe 1.8, MaxDD -12% алдыңыз. Нәтижелер тамаша көрінеді. Сіз ботты өндіріске іске қосасыз, бір айдан кейін дродаун -28%-ға жеткенін және PnL нөлге қарай ұмтылып бара жатқанын байқайсыз.

Не дұрыс болмады? Бұл баг та емес, "нарық өзгерді" де емес. Мәселе — сіз шешімді бір ғана сан негізінде қабылдадыңыз, яғни бір нүктелік бағалау негізінде. Сіз стратегия +42% көрсеткенін білдіңіз, бірақ осы санға қаншалықты сенуге болатынын білмедіңіз.

Бір нүктелік бағалаулардың мәселесі

A single point estimate versus a full probability distribution Жалғыз деректер нүктесі (сол жақта) жаңылыстыратын сурет береді, ал толық үлестірім (оң жақта) мүмкін нәтижелердің шынайы ауқымын ашады.

Тарихи деректер бойынша бэктест — нарық оқиғаларының белгілі бір тізбегі арқылы жасалған бір ғана жүгіріс. Нәтиже мәмілелердің ретіне байланысты: бірдей мәмілелермен жасалған бірдей стратегия, бірақ басқа ретпен, мүлдем басқа максималды дродаун көрсете алады.

491 мәміле бар делік. Әрбір мәміле — белгілі бір кірістілік үлестірімі бар кездейсоқ оқиға. Тарихи бэктест бұл процестің тек бір ғана жүзеге асуын көрсетеді. Бұл сүйекті бір рет лақтырып, оның әрдайым төртке түсетінін тұжырымдаумен бірдей.

Бізге шын мәнінде керегі:

  • Нүктелік бағалау емес, керісінше интервал: "95% ықтималдықпен, түпкілікті PnL X пен Y аралығында болады"
  • Жалғыз максималды дродаун емес, керісінше үлестірім: "ең нашар сценарийлердің 5%-ында дродаун Z%-дан асады"
  • Орташа мән емес, керісінше құйрықтар: сәттілік сізге қарамаса не болады?

Дәл осы үшін Монте-Карло бутстрэп бар.

Монте-Карло бутстрэп деген не

Monte Carlo bootstrap resampling: thousands of alternative equity paths generated from trade data Бутстрэп бастапқы деректер жиынтығынан мәмілелерді қайта қоя отырып ресэмплдеу арқылы мыңдаған балама эквити траекторияларын жасайды.

Бутстрэп — 1979 жылы Брэдли Эфрон ұсынған ресэмплдеу әдісі. Идея қарапайым әрі әдемі: деректер сынамасы болса, біз бастапқы жиынтықтан қайта қоя отырып элементтерді кездейсоқ таңдау арқылы мыңдаған "жаңа" сынама жасай аламыз.

Бэктест контекстінде бұл былай жұмыс істейді:

  1. Сізде әрбір мәміле үшін кірістіліктер массиві бар — мысалы, 491 мән
  2. Сіз осы массивтен қайта қоя отырып кездейсоқ түрде 491 мән таңдайсыз — кейбір мәмілелер екі рет кездеседі, кейбіреулері мүлдем кездеспейді
  3. Осы жаңа сынамадан эквити қисығын саласыз
  4. Мұны 10 000 рет қайталайсыз
  5. Бір сан емес, түпкілікті метрикалардың үлестірімін аласыз

Әрбір итерация — "балама сценарий": мәмілелердің реті мен жиынтығы сәл өзгеше болса, не болуы мүмкін еді.

10 жолда іске асыру

Толық жұмыс істейтін код мынау:

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)
    })

Орындалу уақыты: қарапайым ноутбукте ~2 секунд. Стратегияңыздың 10 000 балама тарихы.

Сенімділік интервалдарын алу

Confidence intervals for PnL, MaxDD, and Sharpe Ratio with 5th, 50th, and 95th percentiles Стратегияның негізгі метрикалары үшін сенімділік интервалдары: PnL, MaxDD және Sharpe Ratio, 5-ші, 50-ші және 95-ші процентиль жолақтарымен көрсетілген.

Енді бізде бір сан емес, үлестірім бар. Одан пайдалы ақпаратты былай алуға болады:

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}")

Нақты стратегия үшін мысал шығысы:

Metric 5th percentile (worst) Median 95th percentile (best)
PnL +18.3% +41.7% +72.1%
MaxDD -23.4% -12.8% -5.1%
Sharpe 1.12 1.76 2.41

Енді айырмашылық анық көрінеді:

  • Бэктест PnL +42% көрсетті — бірақ ең нашар 5% сценарийде PnL небәрі +18.3%
  • Бэктест MaxDD -12% көрсетті — бірақ ең нашар 5% сценарийде дродаун -23.4%
  • Sharpe 1.8 — бірақ төменгі шек 1.12

5-ші процентиль — сіздің "реалистік ең нашар жағдайыңыз". Егер стратегия 5-ші процентильде пайда әкелмей қалса, оны өндіріске шығару қауіпті.

Визуализация: Fan Chart

Монте-Карло бутстрэп табиғи түрде fan chart ретінде визуализацияланады — эквити қисықтарының желпуіші:

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()

Fan chart мүмкін нәтижелердің таралуы туралы интуитивті түсінік береді. Тар желпуіш стратегияның тұрақты екенін білдіреді. Кең желпуіш нәтиженің мәмілелер ретіндегі "сәттілікке" қатты тәуелді екенін білдіреді.

Monte Carlo Visualization: Fan Chart and Distribution Histogram Fan chart (сол жақта) мүмкін эквити траекторияларының таралуын көрсетеді, ал гистограмма (оң жақта) белгіленген сенімділік интервалдарымен (5%, 50%, 95%) түпкілікті кірістіліктердің тығыздық үлестірімін көрсетеді.

Тереңдетілген талдау: банкроттық ықтималдығы

Probability of ruin analysis: equity curves either surviving or falling into ruin Банкроттық ықтималдығының визуализациясы: аман қалған эквити жолдары (циан) жоғары қарай иіледі, ал банкрот болған жолдар (қызыл) нөлдік-эквити шегінен төмен түседі.

Бутстрэп маңызды сұраққа жауап беруге мүмкіндік береді: стратегияның капиталдың X%-ын жоғалту ықтималдығы қандай?

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%}")

Бұл метрикаларды бір ғана бэктест жүгірісінен алу мүмкін емес. Дегенмен стратегияны іске қосу туралы шешім қабылдау үшін олар өте маңызды.

Терең дродаундардың неге математикалық тұрғыда қауіпті екені және кірістілік асимметриясы қалай жұмыс істейтіні туралы толығырақ Loss-Profit Asymmetry мақаласынан оқыңыз.

Классикалық бутстрэп қашан жұмыс істемейді

Әдістің білуге тұрарлық шектеулері бар.

Кірістіліктердің автокорреляциясы

Классикалық бутстрэп мәмілелердің тәуелсіз болатынын болжайды. Шындығында бұл жиі олай емес — стратегияда жеңіс пен жеңілістің серияларымен жүруі мүмкін. Автокорреляция айтарлықтай болса, блокты бутстрэпті қолданыңыз:

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)

Блокты бутстрэп қатарлас мәмілелер арасындағы жергілікті тәуелділікті сақтап, MaxDD үшін неғұрлым шынайы сенімділік интервалдарын береді.

Block bootstrap resampling: sequential trade blocks shuffled and recombined Блокты бутстрэп мәмілелер тізбегін блоктарға бөліп, оларды қайта қоя отырып ресэмплдеу арқылы блоктар ішіндегі автокорреляцияны сақтайды.

Нарықтың стационарлы еместігі

Бутстрэп бастапқы мәміле үлестірімімен жұмыс істейді. Нарық құрылымдық түрде өзгерсе (мысалы, құбылмалылық төмендесе немесе өтімділік өзгерсе), тарихи мәмілелер репрезентативті болмауы мүмкін. Мұны ескеру үшін:

  • Жылжымалы терезені қолданыңыз: тек соңғы N мәміле бойынша бутстрэп жасаңыз
  • Соңғы мәмілелерге көбірек салмақ беріңіз: өлшемделген бутстрэп
  • Деректерді нарықтық режимдер бойынша бөліп, бөлек-бөлек бутстрэп жасаңыз

Мәмілелер саны аз болғанда

Бутстрэп n > 30 мәміле кезінде сенімді. Егер сізде 10 мәміле болса — қанша ресэмплдеу жасасаңыз да көмектеспейді. 491 мәміле — керемет сынама; нәтижелерге сенуге болады.

Бэктест тұрақтылығын бағалау тәсілдерін салыстыру

Method What it provides Complexity Time When to use
Single backtest One point estimate Minimal Seconds Never as a final result
Walk-forward Out-of-sample metrics Medium Minutes To check for overfitting
Monte Carlo bootstrap Confidence intervals Minimal ~2 sec Always before production
Monte Carlo path New price paths High Minutes-hours For stress testing
Cross-validation Average metrics across folds Medium Minutes For parameter tuning

Монте-Карло бутстрэп — минималды уақытта тәуекелдердің толық бейнесін беретін жалғыз әдіс.

Чек-лист: нәтижелерді интерпретациялау

Монте-Карло бутстрэп нәтижелерін былай интерпретациялауды ұсынамыз:

Өндіріске шығарыңыз, егер:

  • 5-ші процентильдегі PnL оң болса
  • 5-ші процентильдегі MaxDD тәуекелге деген талғамыңызға қолайлы болса
  • банкроттық ықтималдығы < 1% болса
  • 5-ші процентильдегі Sharpe > 0.5 болса

Қосымша жұмыс керек, егер:

  • 5-ші процентильдегі PnL нөлге жақын болса
  • 5-ші процентильдегі MaxDD 50-ші процентильдегіден айтарлықтай нашар болса
  • fan chart-тың таралуы кең болса — стратегия тұрақсыз

Шығармаңыз, егер:

  • 5-ші процентильдегі PnL теріс болса
  • банкроттық ықтималдығы > 5% болса
  • Sharpe сенімділік интервалы 0-ді қамтыса

marketmaker.cc-тегі біздің тәжірибе

marketmaker.cc сайтында біз өзіміздің бэктест қозғалтқышын жасаймыз, және Монте-Карло бутстрэп біздің құбырымыздың ажырамас бөлігі. Әрбір стратегия live трейдингке бекітілмес бұрын бутстрэптен автоматты түрде өтеді.

Біз бутстрэпті тікелей бэктест қозғалтқышына кірістірдік: жүгірістен кейін сіз тек түпкілікті PnL ғана емес, сенімділік интервалдары, fan chart, банкроттық ықтималдығы және блокты бутстрэп пен стандартты бутстрэптің салыстырмасы бар толық есеп аласыз. Бұған қосымша 2-3 секунд кетеді — нақты тәуекелдерді түсіну үшін бұл шамалы баға.

Біздің тәжірибемізде: бір нүктелік бағалау бойынша тартымды көрінетін стратегиялардың шамамен 30%-ы Монте-Карло бутстрэптен кейін сүзгіден өтпей қалады. Олардың 5-ші процентильдегі PnL теріс болып шығады немесе MaxDD қолайсыз болып табылады. Бутстрэпсіз бұл стратегиялар өндіріске шығып, өте жоғары ықтималдықпен шығынға әкелер еді.

Қорытынды

Монте-Карло бутстрэп — небәрі ~10 жол код және ~2 секунд есептеу. Ол бэктесттен алынған бір санды сенімділік интервалдары бар толық үлестірімге айналдырады. Бұл кез келген сандық талдау құралдарының ішіндегі ROI-і ең жоғарысы шығар:

  • Минималды шығын: 30 минутта іске асыру
  • Максималды пайда: стратегияның нақты тәуекелдерін түсіну
  • Тәуелділік жоқ: тек NumPy

Егер сіз бутстрэпті әлі қолданбасаңыз — оны бүгін-ақ құбырыңызға қосыңыз. Бэктест нәтижелеріңізге қаншалықты сенуге болатынын білудің жалғыз жолы осы.


References

  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

Citation

@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/ru/blog/post/monte-carlo-bootstrap-backtest},
  version = {0.1.0},
  description = {Why a single-point estimate from a backtest is a dangerous illusion. How Monte Carlo bootstrap in 2 seconds of computation gives you a 95\% confidence interval for PnL and MaxDD, and why this is a mandatory step before launching a strategy in production.}
}
blog.disclaimer

Authors

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

Нарықтан бір қадам алда болыңыз

AI сауда талдаулары, нарық аналитикасы және платформа жаңалықтары үшін біздің ақпараттық бюллетеньге жазылыңыз.

Біз сіздің жекелігіңізді құрметтейміз. Кез келген уақытта жазылымнан шығуға болады.