बैकटेस्ट के लिए बूटस्ट्रैपिंग कॉन्फिडेंस बैंड कैसे बनाएं
एक इक्विटी कर्व केवल एक नमूना होता है। बैकटेस्ट Sharpe ratio पर बूटस्ट्रैप कॉन्फिडेंस इंटरवल अक्सर शून्य को शामिल करता है। इसे बनाने और समझने की विधि यहाँ विस्तार से दी गई है।
बैकटेस्ट कॉन्फिडेंस इंटरवल का महत्व
0.88
एक बैकटेस्ट कॉन्फिडेंस इंटरवल एक ही प्रश्न का उत्तर देता है: इक्विटी कर्व का कितना हिस्सा रणनीति का परिणाम है, और कितना हिस्सा इतिहास के उस विशिष्ट कालखंड का है जिस पर इसे चलाया गया था।
बैकटेस्ट की बूटस्ट्रैपिंग (bootstrapping) इस इंटरवल को रिटर्न सीरीज़ को कई बार रीसैंपल करके तैयार करती है। प्रत्येक रीसैंपल पर सांख्यिकीय गणना को दोबारा किया जाता है, और प्राप्त परिणामों के परसेंटाइल (percentiles) का विश्लेषण किया जाता है।
एक वर्ष के दैनिक अवलोकनों पर मापा गया 1.4 का Sharpe ratio एक पचानवे प्रतिशत का इंटरवल बनाता है जो इतना विस्तृत होता है कि उसमें शून्य भी शामिल हो सकता है। नीचे दिए गए पैनल इसका कारण स्पष्ट करते हैं।
एक इक्विटी कर्व एक नमूना क्यों है
एक बैकटेस्ट आपको प्रत्येक सांख्यिकीय माप के लिए एक संख्या देता है: एक Sharpe ratio, एक वार्षिक रिटर्न, एक अधिकतम गिरावट (drawdown), और एक हिट रेट। इनमें से कोई भी रणनीति का वास्तविक मूल्य नहीं है। प्रत्येक संख्या ट्रेडिंग दिनों की एक सीमित अवधि से प्राप्त अनुमान है, और यदि उसी लंबाई की कोई दूसरी अवधि ली जाती, तो परिणाम कुछ और होता।
नीचे दिया गया पैनल रणनीति को पूरी तरह से विश्लेषण से हटा देता है। यह सबसे सरल संभव पोजीशन को मापता है, जिसमें 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प्रत्येक पंक्ति लगभग 250 सत्रों को कवर करती है, जो एक मानक ट्रेडिंग वर्ष के करीब है। इनमें से किसी भी अवधि में पोजीशन के बारे में कुछ भी नहीं बदला। 2012 में वार्षिक Sharpe ratio 1.06 मापा गया; 2025 में यह 0.88 था, जिसमें चार्ट पर 14 वर्षों को दर्शाया गया है। एक व्यापक इंडेक्स को एक वर्ष तक रखने से मुख्य संख्या में इतना बदलाव आता है जिसे अधिकांश पाठक 'नॉइज़' (noise) से अधिक मानते हैं, और समान अवधि का एक रणनीति बैकटेस्ट कम से कम उतना ही उतार-चढ़ाव विरासत में लेता है। कॉलम के पीछे की वार्षिककरण परंपरा हमारी Sharpe ratio गाइड में दी गई है, और अवधि-रिटर्न की कार्यप्रणाली मासिक रिटर्न कैसे मापे जाते हैं में उपलब्ध है।
बैकटेस्ट Sharpe के आसपास बैंड कितना चौड़ा होता है?
Sharpe अनुमान की मानक त्रुटि (standard error) लगभग अवलोकनों की संख्या के वर्गमूल (square root) के साथ कम होती है। उस फॉर्मूले पर निर्भर रहने के बजाय, फैलाव (spread) को सीधे मापें। बीस वर्षों के सत्रों को एक निश्चित लंबाई की गैर-अतिव्यापी (non-overlapping) विंडो में विभाजित करें, प्रत्येक विंडो के भीतर वार्षिक Sharpe की गणना करें, और देखें कि वे संख्याएं एक-दूसरे से कितनी दूर हैं।
हर आंकड़े के पीछे का पूरा 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प्रति विंडो 21 sessions पर, मापे गए Sharpe का 5वां से 95वां पर्सेंटाइल -3.37 से 6.97 तक चलता है, जो 238 विंडो में 10.34 Sharpe पॉइंट्स का फैलाव है। प्रत्येक विंडो को 504 sessions तक बढ़ाएं और फैलाव घटकर 1.67 पॉइंट्स रह जाता है, जिसे अब केवल 8 विंडो पर मापा जाता है। दो चीजें एक साथ बदलती हैं। नमूना आकार (sample size) के साथ अनुमान अधिक सटीक हो जाता है, और इतिहास द्वारा प्रदान किए जा सकने वाले स्वतंत्र नमूनों की संख्या कम हो जाती है। यही तनाव पूरा विषय है।
बैकटेस्ट कॉन्फिडेंस इंटरवल को बूटस्ट्रैप कैसे करें
यह प्रक्रिया इतनी संक्षिप्त है कि इसे पूरी तरह से लिखा जा सकता है।
- रणनीति की वास्तविक रिटर्न सीरीज़ से शुरुआत करें, जिसमें प्रति अवधि एक आंकड़ा हो, और कुल N आंकड़े हों।
- उस सूची से यादृच्छिक रूप से N रिटर्न चुनें, जिसमें प्रतिस्थापन (replacement) की अनुमति हो। कुछ रिटर्न दो बार आ सकते हैं, और कुछ बिल्कुल नहीं।
- रीसैंपल पर सांख्यिकीय मान (statistic) की पुनः गणना करें।
- इस प्रक्रिया को कई हज़ार बार दोहराएं और प्रत्येक मान को सुरक्षित रखें।
- संग्रहीत मानों को क्रमबद्ध करें और पचानवे प्रतिशत इंटरवल के लिए ढाईवें और संतानवे दशमलव पांचवें परसेंटाइल को देखें।
दूसरे चरण से पहले रैंडम नंबर जनरेटर को सीड (seed) करें, और प्रकाशित इंटरवल के साथ उस सीड को रिकॉर्ड करें। बूटस्ट्रैप एक मोंटे कार्लो अनुमान है: बिना सीड वाले दो रन अंतिम अंकों में भिन्न हो सकते हैं, और जिस समीक्षक के पास आपके इंटरवल को दोबारा चलाने का साधन नहीं है, वह इसकी जांच नहीं कर पाएगा। यह वही अनुशासन है जिसका वर्णन एक पुनरुत्पादनीय बैकटेस्ट सेटअप में किया गया है।
औसत रिटर्न और Sharpe अनुपात दूसरे चरण से आसानी से गुजर जाते हैं, क्योंकि दोनों रिटर्न सूची को एक सेट के रूप में पढ़ते हैं। अधिकतम ड्रॉडाउन ऐसा नहीं करता है। ड्रॉडाउन क्रम में पाथ को पढ़ता है। एक रीसैंपल जो रिटर्न को पुनर्व्यवस्थित करता है, वह ऐसा सबसे खराब ड्रॉडाउन उत्पन्न करता है जिसे वास्तविक ट्रेड अनुक्रम के किसी भी क्रम ने उत्पन्न नहीं किया था। इसे बूटस्ट्रैप करना अभी भी सार्थक है, बशर्ते आउटपुट को उसके वास्तविक स्वरूप के लिए लेबल किया गया हो: यह पुनर्व्यवस्थित इतिहासों में ड्रॉडाउन का वितरण है, न कि अगले ड्रॉडाउन का पूर्वानुमान। इसकी परिभाषा अधिकतम ड्रॉडाउन में दी गई है।
बाजार रिटर्न के लिए 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 DESCSPY के लिए, 2514 सत्रों में मापा गया एब्सोल्यूट दैनिक रिटर्न का लैग-वन ऑटोकोरिलेशन 0.366 है, जबकि उन्हीं दिनों के साइन्ड रिटर्न के लिए यह -0.133 है। चार्ट में किसी भी नाम पर दोनों बार (bars) की तुलना करें। रिटर्न सीरीज को शफल करने से वह क्लस्टरिंग नष्ट हो जाती है, और इस डेटा पर चलाया गया IID बूटस्ट्रैप सैंपल द्वारा समर्थित अंतराल की तुलना में एक संकरा अंतराल रिपोर्ट करता है। यह त्रुटि सबसे कम सहायक दिशा में होती है: यह रणनीति को वास्तविकता से बेहतर दिखाती है।
मूविंग ब्लॉक बूटस्ट्रैप और ब्लॉक की लंबाई का चयन
इसका समाधान एकल रिटर्न के बजाय निरंतर ब्लॉक को रीसैंपल करना है। ब्लॉक की लंबाई L चुनें, यादृच्छिक रूप से प्रतिस्थापन के साथ L क्रमिक रिटर्न के ब्लॉक निकालें, और उन्हें तब तक एक साथ जोड़ें जब तक कि सिंथेटिक श्रृंखला N लंबाई की न हो जाए। एक ब्लॉक के भीतर निर्भरता बरकरार रहती है: वे रिटर्न अपने मूल क्रम में रहते हैं। केवल ब्लॉकों के बीच के जोड़ कृत्रिम होते हैं।
ब्लॉक की लंबाई दो त्रुटियों के बीच संतुलन बनाती है, और कोई भी सेटिंग दोनों से नहीं बच सकती। छोटे ब्लॉक IID बूटस्ट्रैप की तरह व्यवहार करते हैं और अंतराल को कम करके आंकते हैं, जो कि बायस (bias) है। लंबे ब्लॉक अधिक निर्भरता को संरक्षित करते हैं लेकिन चुनने के लिए कम विशिष्ट ब्लॉक छोड़ते हैं, इसलिए प्रत्येक रीसैंपल एक ही इतिहास के बड़े हिस्सों को दोहराता है और अंतराल स्वयं शोरयुक्त हो जाता है, जो कि वेरिएंस (variance) है। प्रकाशित सामान्य नियम लंबाई को N की एक-तिहाई घात के साथ स्केल करते हैं। उन्हें शुरुआती बिंदुओं के रूप में मानें।
एक सस्ता डायग्नोस्टिक यह जांचना है कि वेरिएंस क्षितिज के साथ कैसे स्केल करता है। स्वतंत्रता के तहत, k-दिवसीय रिटर्न योग का वेरिएंस एक-दिवसीय वेरिएंस के k गुना के बराबर होता है, और दोनों का अनुपात 1 पर स्थित होता है।
हर आंकड़े के पीछे का पूरा 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 k2 sessions पर अनुपात 0.844 मापा गया; 63 sessions पर यह 0.584 मापा गया, जिसकी गणना 78 गैर-अतिव्यापी ब्लॉकों पर की गई। 1 के करीब के मानों का अर्थ है कि योग उसी तरह स्केल करता है जैसे स्वतंत्र ड्रा उस क्षितिज पर करते। 1 से दूर के मान उन क्षितिजों को चिह्नित करते हैं जहां निर्भरता अभी भी काम कर रही है, और वह वह सीमा है जिसे ब्लॉक की लंबाई को कवर करना होगा। सबसे छोटा ब्लॉक चुनें जो इसे स्पष्ट करे, फिर देखें कि ब्लॉक की संख्या कितनी कम हो गई है।
बकेटेड रीसैंपलिंग और प्रत्येक अंतराल के बगल में गणना
बकेट के भीतर रीसैंपलिंग, चाहे वह अस्थिरता (volatility) के आधार पर हो या कैलेंडर माह के अनुसार, उस कंडीशनिंग को बनाए रखती है जिसने इस प्रश्न को महत्वपूर्ण बनाया है। यदि दावा यह है कि कोई रणनीति उच्च-अस्थिरता वाले दौर में अपना Sharpe अनुपात अर्जित करती है, तो केवल उच्च-अस्थिरता वाले दिनों से तैयार किया गया अंतराल ही उस दावे का परीक्षण करता है। इसकी कीमत गणितीय है। दो सौ पचास अवलोकनों को चार बकेट में विभाजित करने पर प्रति बकेट लगभग साठ अवलोकन बचते हैं, और साठ-अवलोकन वाला बूटस्ट्रैप पूर्ण-नमूना संस्करण की तुलना में लगभग दोगुना चौड़ा अंतराल देता है।
इसलिए, हर बार प्रत्येक अंतराल के बगल में बकेट की गणना दर्ज करें। ऊपर दिए गए पैनलों में block_count कॉलम इसी आदत का दृश्य रूप है: नौ विंडो से पढ़ा गया पांचवां पर्सेंटाइल, दो सौ विंडो से पढ़े गए पर्सेंटाइल से बिल्कुल अलग होता है, भले ही दोनों का परिणाम दो दशमलव तक ही क्यों न हो।
जिसे बूटस्ट्रैप ठीक नहीं कर सकता
बूटस्ट्रैप केवल एक चीज़ को मापता है: आपके पास मौजूद डेटा से गणना किए गए सांख्यिकीय आंकड़ों में सैंपलिंग नॉइज़ (sampling noise)। यह इस पर मौन रहता है कि क्या वह डेटा कभी प्राप्त किया जा सकता था।
look-ahead bias से दूषित बैकटेस्ट एक ऐसा रिटर्न सीरीज़ उत्पन्न करता है जिसे कभी ट्रेड नहीं किया जा सकता था, और उसका बूटस्ट्रैप एक कल्पना के इर्द-गिर्द एक सटीक, आश्वस्त बैंड प्रदान करता है। आज के इंडेक्स सदस्यों से बना यूनिवर्स survivorship bias को साथ लेकर चलता है, और उस यूनिवर्स का हर रीसैंपल इसे विरासत में प्राप्त करता है। तीसरी कमी सिलेक्शन इफेक्ट है: दो सौ वेरिएंट चलाएं, सर्वश्रेष्ठ को रखें, और उसका बूटस्ट्रैप इंटरवल उस एक वेरिएंट के सैंपलिंग नॉइज़ का वर्णन करता है, जबकि उन एक सौ निन्यानवे प्रयासों को अनदेखा कर देता है जो उसे चुनने में लगे थे। ईमानदार बैंड उपयोगी होते हैं। वे आउट-ऑफ-सैंपल डेटा का विकल्प नहीं हैं।
डेटा नोट्स और विधि
- रिटर्न दैनिक बार (daily bars) से क्लोज़-टू-क्लोज़ प्राइस रिटर्न हैं। डिविडेंड को शामिल नहीं किया गया है, जो हर रिटर्न और शार्प (Sharpe) आंकड़े के स्तर को लगभग डिविडेंड यील्ड जितना कम कर देता है। जिस फैलाव (dispersion) के बारे में यह पोस्ट है, वह लगभग अप्रभावित है।
- विंडोज़ और ब्लॉक्स ओवरलैप नहीं होते हैं, इसलिए प्रत्येक मापा गया सांख्यिकीय आंकड़ा इतिहास के एक अलग हिस्से का उपयोग करता है। ओवरलैपिंग विंडोज़ नमूनों की स्पष्ट संख्या को बढ़ा-चढ़ाकर दिखा सकती हैं।
- क्वांटाइल एक नियतात्मक एस्टिमेटर (deterministic estimator) का उपयोग करते हैं, इसलिए पैनल को फिर से चलाने पर एक नया अनुमान मिलने के बजाय वही परसेंटाइल मिलता है।
- ऑटोकोरिलेशन पैनल छह बड़े नामों का उपयोग करता है जिनमें विंडो के भीतर कोई शेयर स्प्लिट नहीं है, और पैंतीस प्रतिशत से अधिक की चाल वाले किसी भी दिन को हटा देता है, जो प्राइस-एडजस्टमेंट आर्टिफैक्ट्स को कोरिलेशन से बाहर रखता है।
अक्सर पूछे जाने वाले प्रश्न
बैकटेस्ट के बारे में bootstrap confidence interval क्या बताता है?
यह उन मानों की सीमा बताता है जो सांख्यिकीय रूप से तब प्राप्त हो सकते हैं यदि उसी प्रक्रिया को समान अवधि के लिए दोबारा सैंपल किया जाए। यदि पचानवे प्रतिशत अंतराल में शून्य शामिल है, तो इसका अर्थ है कि सैंपल इतना छोटा है कि रणनीति के लाभ (edge) को स्पष्ट रूप से अलग नहीं किया जा सकता।
कितने bootstrap resamples पर्याप्त होते हैं?
पचानवे प्रतिशत अंतराल के लिए, कुछ हजार resamples आमतौर पर स्थिर होते हैं, और दस हजार एक सामान्य डिफ़ॉल्ट है जिसकी लागत कम होती है। गहरे टेल क्वांटाइल्स (tail quantiles) के लिए अधिक की आवश्यकता होती है: एक हजार ड्रा में से पहले पर्सेंटाइल को लगभग दस मानों से पढ़ा जाता है।
moving block bootstrap के लिए ब्लॉक की लंबाई क्या होनी चाहिए?
कोई भी लंबाई सार्वभौमिक रूप से सही नहीं है। अंगूठे के नियम (rules of thumb) इसे सैंपल आकार की एक-तिहाई घात (power) के साथ स्केल करते हैं, और एक व्यावहारिक जांच वह क्षितिज है जिस पर variance रैखिक रूप से स्केल करना बंद कर देता है, जिसे ऊपर दिया गया variance ratio पैनल मापता है। अंतराल के साथ-साथ उस लंबाई को भी प्रकाशित करें जिसका आपने उपयोग किया है।
क्या आप maximum drawdown को bootstrap कर सकते हैं?
हाँ, लेकिन एक क्रम संबंधी चेतावनी के साथ। Drawdown रिटर्न के अनुक्रम पर निर्भर करता है, इसलिए शफलिंग द्वारा बनाया गया resample पुनर्गठित इतिहास के drawdown को दर्शाता है। एक block bootstrap छोटी अवधियों को बरकरार रखता है और पाथ सांख्यिकी (path statistics) के लिए बेहतर उपकरण है।
क्या bootstrap ओवरफिटिंग को ठीक करता है?
नहीं। यह एक रिटर्न सीरीज़ के भीतर सैंपलिंग शोर को मापता है। लुक-अहेड बायस और कई वेरिएंट्स के परीक्षण से उत्पन्न चयन प्रभाव, दोनों ही इसके दायरे से बाहर हैं।
यहाँ प्रत्येक पैनल में वह SQL शामिल है जिसने इसे तैयार किया है। Ticker या विंडो की लंबाई बदलें और इसे स्वयं Strasmore टर्मिनल पर चलाएं।