Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

آموزش محاسبه بازه اطمینان بوت‌استرپ در بک‌تست

هر منحنی بازدهی تنها یک نمونه آماری است. با استفاده از روش بوت‌استرپینگ، بازه اطمینان نسبت Sharpe را محاسبه کنید تا متوجه شوید آیا استراتژی شما واقعاً سودآور است یا خیر.

بازه اطمینان در بک‌تست

بازه اطمینان در یک بک‌تست به این پرسش پاسخ می‌دهد که چه میزان از منحنی بازدهی حاصل استراتژی است و چه میزان از آن به مقطع زمانی خاصی بستگی دارد که استراتژی در آن اجرا شده است. روش بوت‌استرپینگ (Bootstrapping) در بک‌تست، این بازه را از طریق نمونه‌گیری مجدد از سری بازدهی‌ها در دفعات متعدد، محاسبه دوباره شاخص آماری برای هر نمونه و استخراج صدک‌های حاصل، ایجاد می‌کند. نسبت شارپ 1.4 که در طول یک سال از مشاهدات روزانه اندازه‌گیری شده باشد، بازه اطمینانی 95 درصدی به همراه دارد که به‌اندازه‌ای گسترده است که عدد صفر را نیز در بر می‌گیرد؛ پنل‌های زیر دلیل این موضوع را نشان می‌دهند.

چرا یک منحنی سهام تنها یک نمونه است

یک بک‌تست برای هر شاخص آماری تنها یک عدد به شما می‌دهد: یک نسبت Sharpe، یک بازده سالانه، یک حداکثر افت سرمایه (drawdown) و یک نرخ موفقیت. هیچ‌کدام از این‌ها ارزش واقعی استراتژی نیستند. هر یک، برآوردی است که از یک دوره محدود از روزهای معاملاتی به‌دست آمده و بازه زمانی متفاوتی با همان طول، خروجی متفاوتی را ثبت می‌کرد.

پنل زیر استراتژی را به‌طور کامل از معادله حذف می‌کند. این پنل ساده‌ترین موقعیت معاملاتی ممکن، یعنی نگهداری SPY به‌صورت سال‌به‌سال تقویمی و بر اساس بازده قیمت close-to-close را اندازه‌گیری می‌کند.

پرس‌وجونسبت شارپ سالانه SPY به تفکیک سال‌های تقویمی
کد دقیق SQL پشت هر عدد
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2011-12-01'
      AND date <  '2026-01-01'
)
SELECT
    toString(toYear(date))                            AS year,
    count()                                           AS obs_count,
    round(avg(ret) * 252 * 100, 2)                    AS ann_return_pct,
    round(stddevSamp(ret) * sqrt(252) * 100, 2)       AS ann_vol_pct,
    round(avg(ret) / stddevSamp(ret) * sqrt(252), 2)  AS sharpe_ratio
FROM daily
WHERE date >= '2012-01-01'
  AND ret IS NOT NULL
GROUP BY year
ORDER BY year
Run this yourself

هر ردیف حدود 250 جلسه معاملاتی را پوشش می‌دهد که به سال معاملاتی استاندارد نزدیک است. هیچ‌چیز در مورد این موقعیت در طول هیچ‌یک از این دوره‌ها تغییر نکرده است. در 2012 نسبت Sharpe سالانه برابر با 1.06 اندازه‌گیری شد؛ در 2025 این مقدار 0.88 بود، در حالی که 14 سال در نمودار ترسیم شده است. یک سال نگهداری یک شاخص گسترده، عدد اصلی را بیش از آن چیزی تغییر می‌دهد که اکثر خوانندگان آن را در دسته نویز طبقه‌بندی کنند، و یک بک‌تست استراتژی با همان طول، حداقل به همان اندازه از این نوسانات تأثیر می‌پذیرد. قرارداد سالانه‌سازی در ستون مربوطه در راهنمای نسبت Sharpe ما آمده است و مکانیسم بازده دوره‌ای در نحوه اندازه‌گیری بازده‌های ماهانه توضیح داده شده است.

دامنه نوسان حول نسبت شارپ در بک‌تست چقدر است؟

خطای استاندارد برآورد نسبت شارپ تقریباً با جذر تعداد مشاهدات کاهش می‌یابد. به‌جای تکیه بر این فرمول، دامنه تغییرات را مستقیماً اندازه‌گیری کنید. بیست سال داده‌های معاملاتی را به بازه‌های زمانی غیرهمپوشان با طول ثابت تقسیم کنید، نسبت شارپ سالانه را در هر بازه محاسبه کنید و ببینید این اعداد تا چه حد از یکدیگر فاصله دارند.

پرس‌وجوپراکندگی نسبت شارپ در بازه‌های زمانی غیرمتداخل SPY، ۲۰۰۶ تا ۲۰۲۵
کد دقیق SQL پشت هر عدد
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2005-12-01'
      AND date <  '2026-01-01'
),
indexed AS
(
    SELECT
        ret,
        row_number() OVER (ORDER BY date) AS i
    FROM daily
    WHERE date >= '2006-01-01'
      AND ret IS NOT NULL
),
blocks AS
(
    SELECT
        w,
        intDiv(i, w)                                            AS blk,
        count()                                                 AS n,
        avg(ret) / nullIf(stddevSamp(ret), 0) * sqrt(252)        AS sharpe
    FROM indexed
    CROSS JOIN (SELECT arrayJoin([21, 63, 126, 252, 504]) AS w) AS ws
    GROUP BY w, blk
    HAVING n = w
)
SELECT
    concat(toString(w), ' sessions')                          AS horizon,
    count()                                                   AS block_count,
    round(quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_p05,
    round(quantileDeterministic(0.50)(sharpe, blk), 2)        AS sharpe_p50,
    round(quantileDeterministic(0.95)(sharpe, blk), 2)        AS sharpe_p95,
    round(quantileDeterministic(0.95)(sharpe, blk)
        - quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_band_width
FROM blocks
WHERE sharpe IS NOT NULL
GROUP BY w
ORDER BY w
Run this yourself

در 21 sessions برای هر بازه، صدک پنجم تا نود و پنجم نسبت شارپ اندازه‌گیری‌شده از -3.37 تا 6.97 متغیر است که نشان‌دهنده دامنه‌ای به میزان 10.34 واحد شارپ در طول 238 بازه است. اگر هر بازه را به 504 sessions افزایش دهید، این دامنه به 1.67 واحد کاهش می‌یابد که اکنون تنها در طول 8 بازه اندازه‌گیری می‌شود. دو عامل همزمان تغییر می‌کنند. برآورد با افزایش حجم نمونه دقیق‌تر می‌شود، اما تعداد نمونه‌های مستقلی که تاریخچه می‌تواند ارائه دهد، کاهش می‌یابد. این تنش، تمام موضوع بحث ماست.

چگونه یک بازه اطمینان برای بک‌تست با روش بوت‌استرپ ایجاد کنیم

این رویه به اندازه‌ای کوتاه است که می‌توان آن را به‌طور کامل شرح داد.

  1. با سری بازده‌های محقق‌شده استراتژی شروع کنید؛ برای هر دوره یک عدد و در مجموع N عدد.
  2. تعداد N بازده را به‌صورت تصادفی و با جای‌گذاری از آن فهرست انتخاب کنید. برخی دو بار ظاهر می‌شوند و برخی اصلاً انتخاب نمی‌شوند.
  3. آماره موردنظر را روی نمونه بازتولیدشده دوباره محاسبه کنید.
  4. این کار را چندین هزار بار تکرار کرده و تمام مقادیر به‌دست‌آمده را ذخیره کنید.
  5. مقادیر ذخیره‌شده را مرتب کرده و صدک‌های 2.5 و 97.5 را برای یک بازه 95 درصدی استخراج کنید.

پیش از مرحله 2، مولد اعداد تصادفی را مقداردهی اولیه (Seed) کنید و آن مقدار را در کنار بازه منتشرشده ثبت نمایید. بوت‌استرپ یک برآورد مونت‌کارلو است: دو اجرای بدون مقداردهی اولیه در ارقام آخر با هم اختلاف خواهند داشت و بازبینی‌کننده‌ای که نتواند بازه شما را دوباره اجرا کند، راهی برای بررسی صحت آن ندارد. این همان انضباطی است که در یک ساختار بک‌تست قابل‌تکرار شرح داده شده است.

میانگین بازده و نسبت Sharpe به‌راحتی از مرحله 2 عبور می‌کنند، زیرا هر دو، فهرست بازده‌ها را به‌عنوان یک مجموعه می‌خوانند. اما حداکثر افت سرمایه (Maximum drawdown) چنین نیست. افت سرمایه، مسیر را به‌ترتیب می‌خواند. نمونه‌ای که بازده‌ها را بازآرایی می‌کند، منجر به بدترین افت سرمایه‌ای می‌شود که هیچ ترتیبی از توالی واقعی معاملات ایجاد نکرده است. با این حال، بوت‌استرپ کردن آن همچنان ارزشمند است، مشروط بر اینکه خروجی به‌درستی برچسب‌گذاری شود: توزیع افت سرمایه در تاریخچه‌های بازآرایی‌شده، نه پیش‌بینی افت سرمایه بعدی. تعریف آن در حداکثر افت سرمایه آمده است.

چرا روش بوت‌استرپ IID برای بازده‌های بازار نادرست است

گام دوم فرض می‌کند که هر بازده، مستقل و دارای توزیع یکسان است؛ یعنی همان فرض IID. بازده‌های روزانه این فرض را به گونه‌ای نقض می‌کنند که عرض بازه را تغییر می‌دهد. بازده‌های علامت‌دار تنها حافظه یک‌روزه ضعیفی دارند، اما بزرگی آن‌ها خوشه‌بندی می‌شود: حرکت‌های بزرگ در کنار حرکت‌های بزرگ قرار می‌گیرند و روزهای آرام نیز به صورت متوالی ظاهر می‌شوند.

پرس‌وجوخودهمبستگی مرتبه اول: بازده‌های علامت‌دار در برابر بازده‌های مطلق، ۲۰۱۶ تا ۲۰۲۵
کد دقیق SQL پشت هر عدد
WITH daily AS
(
    SELECT
        ticker,
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('SPY', 'MSFT', 'KO', 'XOM', 'JNJ', 'PG')
      AND date >= '2015-11-01'
      AND date <  '2026-01-01'
),
lagged AS
(
    SELECT
        ticker,
        date,
        ret,
        lagInFrame(ret, 1) OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS ret_prev
    FROM daily
    WHERE ret IS NOT NULL
      AND abs(ret) < 0.35
)
SELECT
    ticker,
    count()                                AS obs_count,
    round(corr(ret, ret_prev), 3)          AS return_autocorr,
    round(corr(abs(ret), abs(ret_prev)), 3) AS abs_return_autocorr
FROM lagged
WHERE date >= '2016-01-01'
  AND ret_prev IS NOT NULL
GROUP BY ticker
ORDER BY abs_return_autocorr DESC
Run this yourself

برای SPY، خودهمبستگی مرتبه اول بازده‌های مطلق روزانه به میزان 0.366 در طول 2514 جلسه معاملاتی اندازه‌گیری شده است، در حالی که این رقم برای بازده‌های علامت‌دار در همان روزها -0.133 است. دو ستون مربوط به هر نماد را در نمودار مقایسه کنید. برهم‌زدن (Shuffling) یک سری بازده، این خوشه‌بندی را از بین می‌برد و اجرای روش بوت‌استرپ IID روی این داده‌ها، بازه‌ای باریک‌تر از آنچه نمونه پشتیبانی می‌کند، گزارش می‌دهد. این خطا در نامطلوب‌ترین جهت ممکن رخ می‌دهد: چرا که عملکرد استراتژی را بیش از واقعیت نشان می‌دهد.

بوت‌استرپ بلوکی متحرک و انتخاب طول بلوک

راهکار اصلاحی، نمونه‌گیری مجدد از بلوک‌های متوالی به‌جای بازده‌های منفرد است. طول بلوک L را انتخاب کنید، بلوک‌هایی از L بازده متوالی را به‌صورت تصادفی و با جای‌گذاری انتخاب کرده و آن‌ها را پشت‌سرهم قرار دهید تا سری مصنوعی به طول N برسد. وابستگی درون هر بلوک دست‌نخورده باقی می‌ماند: این بازده‌ها ترتیب اصلی خود را حفظ می‌کنند. تنها محل اتصال بلوک‌ها مصنوعی است.

طول بلوک، دو نوع خطا را در برابر یکدیگر قرار می‌دهد و هیچ تنظیماتی از هر دو مصون نیست. بلوک‌های کوتاه مانند بوت‌استرپ IID عمل کرده و بازه را کمتر از مقدار واقعی برآورد می‌کنند که این همان سوگیری (bias) است. بلوک‌های بلند، وابستگی بیشتری را حفظ می‌کنند اما بلوک‌های متمایز کمتری برای انتخاب باقی می‌گذارند؛ در نتیجه هر نمونه‌گیری مجدد، بخش‌های بزرگی از تاریخچه مشابه را تکرار می‌کند و خودِ بازه دچار نوسان می‌شود که این همان واریانس است. قواعد سرانگشتی منتشرشده، طول بلوک را با توان یک‌سوم N مقیاس‌بندی می‌کنند. با آن‌ها به‌عنوان نقاط شروع برخورد کنید.

یک روش تشخیصی کم‌هزینه‌تر، بررسی نحوه مقیاس‌بندی واریانس با افق زمانی است. در شرایط استقلال، واریانس مجموع بازده k-روزه برابر با k برابرِ واریانس یک‌روزه است و نسبت این دو برابر با 1 است.

پرس‌وجونسبت واریانس بر اساس طول بلوک: آیا واریانس SPY مشابه توزیع‌های مستقل مقیاس‌بندی می‌شود؟
کد دقیق SQL پشت هر عدد
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2005-12-01'
      AND date <  '2026-01-01'
),
indexed AS
(
    SELECT
        ret,
        row_number() OVER (ORDER BY date) AS i
    FROM daily
    WHERE date >= '2006-01-01'
      AND ret IS NOT NULL
),
base AS
(
    SELECT varSamp(ret) AS var_1d FROM indexed
),
blocks AS
(
    SELECT
        k,
        intDiv(i, k)  AS blk,
        count()       AS n,
        sum(ret)      AS block_ret
    FROM indexed
    CROSS JOIN (SELECT arrayJoin([2, 3, 5, 10, 21, 42, 63]) AS k) AS ks
    GROUP BY k, blk
    HAVING n = k
)
SELECT
    concat(toString(k), ' sessions')                          AS block_length,
    count()                                                   AS block_count,
    round(varSamp(block_ret) / (k * any(var_1d)), 3)          AS variance_ratio
FROM blocks
CROSS JOIN base
GROUP BY k
ORDER BY k
Run this yourself

در 2 sessions این نسبت برابر با 0.844 اندازه‌گیری شد؛ در 63 sessions این مقدار 0.584 بود که بر اساس 78 بلوک غیرهم‌پوشان محاسبه شد. مقادیر نزدیک به 1 به این معناست که مجموع، همان‌طور مقیاس‌بندی می‌شود که در انتخاب‌های مستقل در آن افق زمانی انتظار می‌رود. مقادیر دور از 1، افق‌هایی را نشان می‌دهند که وابستگی همچنان در آن‌ها نقش دارد و این همان محدوده‌ای است که طول بلوک باید پوشش دهد. کوتاه‌ترین بلوکی را انتخاب کنید که این محدوده را پوشش دهد، سپس بررسی کنید که تعداد بلوک‌ها به چه میزانی کاهش یافته است.

نمونه‌برداری دسته‌بندی‌شده و تعداد مشاهدات در کنار هر بازه

نمونه‌برداری درون دسته‌ها (Buckets)، چه بر اساس رژیم نوسان و چه بر اساس ماه تقویمی، شرطی‌سازی‌هایی را حفظ می‌کند که پرسش را از ابتدا جذاب کرده بود. اگر ادعا این است که یک استراتژی نسبت شارپ (Sharpe ratio) خود را در رژیم‌های با نوسان بالا کسب می‌کند، بازه‌ای که تنها با استخراج داده‌ها از روزهای با نوسان بالا ساخته شده، همان ابزاری است که این ادعا را می‌آزماید. هزینه این کار، ماهیت محاسباتی دارد. تقسیم 250 مشاهده به چهار دسته، تقریباً 60 مشاهده در هر دسته باقی می‌گذارد و یک بوت‌استرپ 60 مشاهده‌ای، بازه‌ای ایجاد می‌کند که حدوداً دو برابر عریض‌تر از نسخه مبتنی بر کل نمونه است.

بنابراین، تعداد مشاهدات هر دسته را در کنار هر بازه، همیشه گزارش کنید. ستون block_count در پنل‌های بالا، نمودِ عینی همین عادت است: صدک پنجمِ استخراج‌شده از نُه پنجره، با صدک پنجمِ استخراج‌شده از دویست پنجره ماهیت متفاوتی دارد، حتی اگر هر دو با دقت دو رقم اعشار چاپ شوند.

آنچه بوت‌استرپ نمی‌تواند اصلاح کند

بوت‌استرپ تنها یک مورد را کمی‌سازی می‌کند: نویز نمونه‌گیری در آماره‌ای که از داده‌های موجود محاسبه شده است. این روش در مورد اینکه آیا آن داده‌ها اساساً قابل دستیابی بوده‌اند یا خیر، سکوت می‌کند.

بک‌تستی که به سوگیری نگاه به آینده آلوده باشد، سری بازدهی تولید می‌کند که هرگز قابل معامله نبوده است؛ بوت‌استرپِ چنین داده‌ای، بازه‌ای دقیق و با اطمینان کاذب حول یک واقعیتِ ساختگی ارائه می‌دهد. جهانی از دارایی‌ها که از اعضای کنونی شاخص‌ها تشکیل شده باشد، حامل سوگیری بقا است و هر نمونه‌گیری مجدد از آن جهان، این سوگیری را به ارث می‌برد. شکاف سوم، اثر انتخاب است: دویست مدل مختلف را اجرا کنید و بهترین را نگه دارید؛ در این صورت، بازه بوت‌استرپِ آن مدل، تنها نویز نمونه‌گیری همان یک مدل را توصیف می‌کند و صد و نود و نه مدل دیگری را که در فرآیند انتخاب کنار گذاشته شده‌اند، نادیده می‌گیرد. بازه‌های صادقانه مفید هستند، اما جایگزینی برای داده‌های خارج از نمونه (out-of-sample) محسوب نمی‌شوند.

یادداشت‌های داده و روش‌شناسی
  • بازدهی‌ها، بازده قیمت از بسته شدن تا بسته شدن (close-to-close) بر اساس نمودارهای روزانه هستند. سود سهام لحاظ نشده است که این امر سطح هر بازدهی و شاخص Sharpe را تقریباً به اندازه نرخ سود سهام کمتر نشان می‌دهد. پراکندگی مورد بحث در این مطلب، تقریباً تحت تأثیر این موضوع قرار نمی‌گیرد.
  • پنجره‌ها و بلوک‌ها هم‌پوشانی ندارند، بنابراین هر آماره اندازه‌گیری‌شده از بخش مجزایی از تاریخچه استفاده می‌کند. پنجره‌های هم‌پوشان، تعداد ظاهری نمونه‌ها را به‌طور کاذب افزایش می‌دهند.
  • چندک‌ها از یک برآوردگر قطعی استفاده می‌کنند، بنابراین اجرای مجدد یک پنل، همان صدک قبلی را بازمی‌گرداند و نه یک تقریب جدید.
  • پنل خودهمبستگی از شش نماد بزرگ که در طول پنجره زمانی دچار شکست سهام (share split) نشده‌اند استفاده می‌کند و هر روزی را که حرکتی بیش از 35 درصد داشته باشد حذف می‌کند تا مصنوعات تعدیل قیمت از محاسبات همبستگی دور بمانند.

پرسش‌های متداول

بازه اطمینان بوت‌استرپ (bootstrap confidence interval) درباره یک بک‌تست چه می‌گوید؟

این بازه، محدوده‌ای از مقادیر را نشان می‌دهد که اگر همان فرآیند دوباره در همان تعداد دوره نمونه‌گیری می‌شد، آن آماره به‌طور معقولی می‌توانست در آن قرار بگیرد. اگر یک بازه 95 درصدی شامل عدد صفر باشد، به این معناست که نمونه آماری برای تفکیک استراتژی از «عدم وجود برتری» (no edge) بسیار کوتاه است.

چه تعداد بازنمونه‌گیری (resample) بوت‌استرپ کافی است؟

برای یک بازه 95 درصدی، چند هزار بازنمونه‌گیری معمولاً پایدار است و 10,000 مورد یک پیش‌فرض رایج است که هزینه محاسباتی ناچیزی دارد. برای چندک‌های انتهایی (tail quantiles) به تعداد بیشتری نیاز است: صدک اول از 1,000 برداشت، تنها از حدود ده مقدار استخراج می‌شود.

در روش moving block bootstrap از چه طول بلوکی باید استفاده کرد؟

طول کاملاً استانداردی برای این کار وجود ندارد. قواعد سرانگشتی، طول بلوک را با توان یک‌سوم اندازه نمونه مقیاس‌بندی می‌کنند. یک روش عملی برای بررسی، تعیین افقی است که در آن واریانس دیگر به‌صورت خطی مقیاس نمی‌شود؛ موضوعی که پنل نسبت واریانس در بالا آن را اندازه‌گیری می‌کند. طول بلوک استفاده‌شده را در کنار بازه اطمینان گزارش کنید.

آیا می‌توان حداکثر افت سرمایه (maximum drawdown) را بوت‌استرپ کرد؟

بله، اما با یک ملاحظه در مورد ترتیب. افت سرمایه به توالی بازدهی‌ها بستگی دارد؛ بنابراین بازنمونه‌گیری از طریق جابه‌جایی تصادفی (shuffling)، افت سرمایه یک تاریخچه بازآرایی‌شده را گزارش می‌دهد. روش block bootstrap دوره‌های کوتاه را دست‌نخورده نگه می‌دارد و ابزار مناسب‌تری برای آماره‌های مبتنی بر مسیر (path statistics) است.

آیا بوت‌استرپ خطای بیش‌برازش (overfitting) را اصلاح می‌کند؟

خیر. این روش تنها نویز نمونه‌گیری در یک سری بازدهی را اندازه‌گیری می‌کند. سوگیری نگاه به آینده (look-ahead bias) و اثر انتخاب ناشی از تست کردن واریانت‌های متعدد، خارج از محدوده دید این روش قرار دارند.


هر پنل در اینجا حاوی کد SQL است که آن را تولید کرده است. تیکر یا طول پنجره زمانی را تغییر دهید و آن را شخصاً در ترمینال Strasmore اجرا کنید.

#backtesting#bootstrap#statistics#confidence intervals#sharpe ratio