← Макалаларга кайтуу
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 соода аналитикасы, рынок талдоолору жана платформа жаңылыктары үчүн биздин жаңылыктар бюллетенине жазылыңыз.

Биз сиздин купуялыгыңызды урматтайбыз. Каалаган убакта жазылымдан чыга аласыз.