स्टॉक डेटा में सर्वाइवरशिप बायस क्या है?
स्टॉक डेटा में सर्वाइवरशिप बायस के कारण डीलिस्ट और अधिग्रहित टिकर गायब हो जाते हैं। यह लेख बताता है कि कैसे यह बैकटेस्ट को प्रभावित करता है और पूर्ण डेटासेट को कैसे मापा जाए।
स्टॉक डेटा में सर्वाइवरशिप बायस (survivorship bias) वह अंतर है जो तब पैदा होता है जब किसी फाइल में केवल वे टिकर (tickers) शामिल होते हैं जो आज भी लिस्टेड हैं। हर वह कंपनी जो डीलिस्ट हो गई, अधिग्रहित (acquired) कर ली गई, जिसका नाम बदल गया या जो बंद हो गई, वह चुपचाप गायब हो जाती है। ऐसे यूनिवर्स में किया गया बैकटेस्ट बाजार की वास्तविक स्थिति के बजाय इतिहास के एक छने हुए नमूने (filtered sample) को मापता है। फाइल में कुछ भी गलत नहीं दिखता। हर पंक्ति में एक वास्तविक कीमत होती है, समस्या उन पंक्तियों की है जिन्हें किसी ने लिखा ही नहीं।
स्टॉक डेटा में सर्वाइवरशिप बायस क्या है?
सोचिए कि "सभी अमेरिकी स्टॉक" का डेटासेट कैसे तैयार किया जाता है। कोई सिस्टम किसी प्रदाता से प्रतीकों (symbols) की सूची मांगता है, फिर सूची के प्रत्येक नाम के लिए मूल्य इतिहास निकालता है। यह सूची निर्माण के आधार पर वर्तमान होती है। यह इस सवाल का जवाब देती है कि "अभी क्या ट्रेड हो रहा है", और अनुरोध किए जाने से पहले जिन प्रतीकों ने ट्रेड करना बंद कर दिया था, वे इस उत्तर से गायब हो जाते हैं, साथ ही उनका पूरा इतिहास भी।
यह शब्द अब्राहम वाल्ड के 1943 के उन बमवर्षक विमानों पर किए गए काम से आया है जो वापस लौट आए थे: कवच (armour) वहां लगाया जाना चाहिए था जहां जीवित विमानों पर छेद नहीं थे, क्योंकि जिन विमानों को उन जगहों पर गोली लगी थी, वे कभी वापस ही नहीं आए। आज की लिस्टिंग से बना स्टॉक डेटाबेस भी उसी आकार का है। जो नमूना आप देख सकते हैं, वह वही है जो घर वापस आ गया, और इसमें सदस्यता उसी परिणाम से तय होती है जिसे एक बैकटेस्ट मापने की कोशिश कर रहा है।
हर साल कितने टिकर बंद हो जाते हैं?
छेद के आकार से शुरुआत करें। नीचे दिया गया पैनल दैनिक इक्विटी टेप के हर प्रतीक को उसके अंतिम बार (final bar) के कैलेंडर वर्ष के अनुसार समूहित करता है, केवल उन प्रतीकों को रखते हुए जो जनवरी 2026 से पहले ही बंद हो चुके थे।
हर आंकड़े के पीछे का पूरा SQL
SELECT
toString(toYear(last_bar)) AS year,
count() AS gone_dark_count,
round(avg(bars_on_tape)) AS avg_bars_on_tape
FROM
(
SELECT
ticker,
max(date) AS last_bar,
count() AS bars_on_tape
FROM global_markets.stocks_daily_aggs
WHERE date >= '2015-01-01'
AND ticker NOT IN ('SPCX')
GROUP BY ticker
HAVING max(date) >= toDate('2016-01-01')
AND max(date) < toDate('2026-01-01')
)
GROUP BY year
ORDER BY year2025 में, 1283 प्रतीकों ने अंतिम दैनिक बार प्रिंट किया और फिर कभी वापस नहीं आए। ये एक हफ्ते पुराने शेल (shells) नहीं हैं: उस समूह का औसत 2015 के बाद से 999 दैनिक बार का था। पैनल में दिए गए 10 वर्षों में यह संख्या बदलती रहती है, और कोई भी वर्ष शून्य के करीब नहीं है। प्रतीक सामान्य कारणों से बाहर निकलते हैं: एक अधिग्रहण पूरा होता है, एक फंड बंद हो जाता है, एक विलय (merger) एक लिस्टिंग को दूसरे में मिला देता है, या लिस्टिंग मानक पूरे नहीं होते।
दस लाइन की स्क्रिप्ट में बायस को देखें
अंकगणित को वर्णित करने की तुलना में देखना आसान है। नीचे दी गई स्क्रिप्ट दो छोटी CSV फाइलें लिखती है: एक केवल सर्वाइवर्स (survivors-only) का यूनिवर्स, और वही यूनिवर्स जिसमें ट्रेडिंग बंद कर चुके नाम वापस जोड़ दिए गए हैं। यह प्रत्येक फाइल का औसत निकालती है और अंतर प्रिंट करती है। यह केवल पायथन स्टैंडर्ड लाइब्रेरी का उपयोग करती है, इसलिए कुछ भी इंस्टॉल करने की आवश्यकता नहीं है।
python3 <<'PY'
import csv, statistics
survivors = [("AAA", 0.42), ("BBB", 0.18), ("CCC", 0.63), ("DDD", 0.07)]
delisted = [("EEE", -1.00), ("FFF", -0.55), ("GGG", 0.31)]
def write_csv(path, rows):
with open(path, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["ticker", "total_return"])
w.writerows(rows)
def mean_return(path):
with open(path, newline="") as f:
return statistics.mean(float(r["total_return"]) for r in csv.DictReader(f))
write_csv("survivors_only.csv", survivors)
write_csv("full_universe.csv", survivors + delisted)
a = mean_return("survivors_only.csv")
b = mean_return("full_universe.csv")
print(f"survivors only: {a:+.1%} across {len(survivors)} names")
print(f"full universe: {b:+.1%} across {len(survivors) + len(delisted)} names")
print(f"survivorship bias: {a - b:+.1%}")
PY
उस स्क्रिप्ट में सात रिटर्न आंकड़े काल्पनिक हैं, न कि बाजार डेटा। वे तंत्र को दृश्यमान बनाने के लिए हैं। केवल सर्वाइवर्स का औसत +32.5% है, पूर्ण यूनिवर्स का औसत +0.9% है, और 31.6 अंकों का अंतर ही बायस है। सर्वाइवर्स-ओनली फाइल में कुछ भी गलत नहीं है। उन चार नामों में से प्रत्येक ने वास्तव में उतना ही रिटर्न दिया जितना लिखा है। फाइल बस उन तीन नामों का उल्लेख नहीं करती है जो औसत को नीचे खींच लेते।
वास्तविक कोहोर्ट्स पर वही माप
अब टेप पर वही गणना चलाएं। नीचे दिया गया पैनल 2016 से 2022 तक प्रत्येक वर्ष के जनवरी में एक शुरुआती यूनिवर्स बनाता है, उन प्रतीकों को रखता है जो महीने की शुरुआत में $5 से ऊपर थे और जिनका औसत दैनिक वॉल्यूम 250,000 शेयरों से अधिक था, फिर उनमें से प्रत्येक को उसके सबसे हालिया क्लोज तक ट्रैक करता है। जो नाम ट्रेडिंग बंद कर चुके हैं, उन्हें हटाए जाने के बजाय उनकी अंतिम प्रिंटेड कीमत पर रखा जाता है। सर्वाइवर्स कॉलम केवल उन प्रतीकों का औसत निकालता है जो अभी भी बार प्रिंट कर रहे हैं। पूर्ण यूनिवर्स कॉलम उन सभी का औसत निकालता है जो शुरुआत में वहां थे।
हर आंकड़े के पीछे का पूरा SQL
WITH
entry AS
(
SELECT
ticker,
toYear(date) AS cohort_start,
argMin(toFloat64(close), date) AS entry_close
FROM global_markets.stocks_daily_aggs
WHERE toMonth(date) = 1
AND date BETWEEN '2016-01-01' AND '2022-01-31'
AND ticker NOT IN ('SPCX')
GROUP BY ticker, cohort_start
HAVING argMin(toFloat64(close), date) >= 5
AND avg(volume) >= 250000
),
outcome AS
(
SELECT
ticker,
argMax(toFloat64(close), date) AS final_close,
max(date) AS last_bar
FROM global_markets.stocks_daily_aggs
WHERE date >= '2016-01-01'
GROUP BY ticker
)
SELECT
toString(e.cohort_start) AS cohort_year,
count() AS cohort_size,
countIf(o.last_bar < today() - 45) AS gone_count,
round(100 * avgIf(o.final_close / e.entry_close - 1, o.last_bar >= today() - 45), 1) AS survivors_only_pct,
round(100 * avg(o.final_close / e.entry_close - 1), 1) AS full_universe_pct,
round(100 * (avgIf(o.final_close / e.entry_close - 1, o.last_bar >= today() - 45)
- avg(o.final_close / e.entry_close - 1)), 1) AS gap_pct
FROM entry AS e
INNER JOIN outcome AS o ON o.ticker = e.ticker
GROUP BY e.cohort_start
HAVING countIf(o.last_bar >= today() - 45) > 0
ORDER BY e.cohort_start2016 कोहोर्ट के लिए, इस पेज के बनने तक 2728 नामों में से 968 टेप से बाहर हो चुके थे। केवल सर्वाइवर्स का औसत निकालने पर 212% मिलता है। पूरे कोहोर्ट का औसत निकालने पर 149.3% मिलता है। गैप कॉलम में उन दो रीडिंग के बीच की दूरी है: उस कोहोर्ट के लिए 62.7 अंक, और 9.1 कोहोर्ट के लिए 2022 अंक, जिसके पास सदस्य खोने के लिए बहुत कम समय था। ये अलग-अलग लंबाई की विंडो पर कुल रिटर्न हैं, इसलिए पंक्तियों के स्तरों की तुलना करने के बजाय गैप कॉलम को नीचे पढ़ें। वही यूनिवर्स प्रश्न मासिक रिटर्न कैसे मापा जाता है के नीचे और हर समान-भार वाले औसत के नीचे स्थित है।
गायब हुए नाम कहां गए
डीलिस्टिंग का मतलब स्वचालित रूप से सफाया नहीं है। नीचे दिया गया पैनल एक शुरुआती यूनिवर्स, जनवरी 2019, को लेता है और उसे इस आधार पर छांटता है कि आगे क्या हुआ।
हर आंकड़े के पीछे का पूरा SQL
WITH
entry AS
(
SELECT
ticker,
argMin(toFloat64(close), date) AS entry_close
FROM global_markets.stocks_daily_aggs
WHERE date BETWEEN '2019-01-01' AND '2019-01-31'
AND ticker NOT IN ('SPCX')
GROUP BY ticker
HAVING argMin(toFloat64(close), date) >= 5
AND avg(volume) >= 250000
),
outcome AS
(
SELECT
ticker,
argMax(toFloat64(close), date) AS final_close,
max(date) AS last_bar
FROM global_markets.stocks_daily_aggs
WHERE date >= '2019-01-01'
GROUP BY ticker
)
SELECT
if(o.last_bar >= today() - 45,
'Still trading',
concat('Last bar in ', toString(toYear(o.last_bar)))) AS outcome_label,
count() AS cohort_size,
round(100 * avg(o.final_close / e.entry_close - 1), 1) AS avg_return_pct,
round(100 * countIf(o.final_close > e.entry_close) / count(), 1) AS share_above_entry_pct
FROM entry AS e
INNER JOIN outcome AS o ON o.ticker = e.ticker
GROUP BY outcome_label
ORDER BY outcome_labelStill trading समूह में 1954 नाम हैं, जिनका औसत जनवरी 2019 की एंट्री कीमत से 139.8% है, और उनमें से 72.1% अपनी शुरुआती कीमत से ऊपर हैं। 9 पंक्तियों को पढ़ें तो निकास (exits) मिश्रित दिखते हैं। प्रीमियम पर खरीदी गई कंपनी एक मजबूत अंतिम प्रिंट पर टेप छोड़ सकती है, जबकि जो डीलिस्टिंग नोटिस की ओर फीकी पड़ जाती है, वह कमजोर प्रिंट पर निकलती है। दोनों ही सर्वाइवर्स-ओनली फाइल से गायब हैं। उन्हें वापस रखें और औसत बदल जाएगा।
सर्वाइवरशिप बायस बनाम लुक-अहेड बायस
ये दोनों एक साथ फाइल किए जाते हैं, और वे बैकटेस्ट को अलग-अलग जगहों पर तोड़ते हैं। लुक-अहेड बायस एक टाइमिंग समस्या है: टेस्ट उस तथ्य का उपयोग करता है जो उस समय किसी के पास नहीं था, जैसे कि पुनर्गठित आय का आंकड़ा या कोई समायोजन जो महीनों बाद लागू किया गया था। सर्वाइवरशिप बायस एक सदस्यता समस्या है: टेस्ट जिस रोस्टर को ट्रेड करता है, वह वह रोस्टर है जो आज मौजूद है, न कि वह जो तब मौजूद था। एक बैकटेस्ट टाइमिंग पर बेदाग हो सकता है और फिर भी ऐसे यूनिवर्स को ट्रेड कर सकता है जिसे hindsight (बाद की समझ) के साथ इकट्ठा किया गया हो। मूल्य समायोजन एक संबंधित जाल लाते हैं, क्योंकि स्प्लिट एडजस्टेड प्राइस हिस्ट्री स्प्लिट तिथि से पहले के हर बार के स्तर को फिर से लिखती है।
बैकटेस्ट में सर्वाइवरशिप बायस को कैसे ठीक करें
- प्रत्येक तिथि के अनुसार यूनिवर्स को फिर से बनाएं। पॉइंट-इन-टाइम सदस्यता का मतलब है कि रोस्टर को उस दिन के लिए पुनर्निर्मित किया जाता है जिस दिन टेस्ट खड़ा है, उन रिकॉर्ड्स से जो उस दिन मौजूद थे। टेस्ट फिर उन नामों को रखता है जो तब लाइव थे, जिनमें वे भी शामिल हैं जो बाद में गायब हो गए।
- एक सिक्योरिटी मास्टर रखें। एक सिक्योरिटी मास्टर समय के साथ पहचानकर्ताओं को मैप करता है: कौन सा प्रतीक किन तारीखों के बीच किस कंपनी की ओर इशारा करता था, और विलय या नाम बदलने के बाद उसकी जगह किसने ली। प्राइस फाइलें प्रतीकों पर आधारित होती हैं। कंपनियां नहीं।
- डीलिस्ट किए गए नामों को उनकी अंतिम वास्तविक कीमत पर बंद करें। जब कोई नाम टेप से हटता है, तो पोजीशन औसत से गायब होने के बजाय अपने अंतिम प्रिंट पर बाहर निकल जाती है। विलोपन ही ऊपर दिए गए पैनलों में गैप खोलता है। एक प्रजनन योग्य बैकटेस्ट उस नियम को यूनिवर्स के बगल में कोड में पिन करता है।
- मृत प्रतीकों पर प्रदाता के दस्तावेज़ पढ़ें। जो वेंडर इसे गंभीरता से लेते हैं, वे इसे लिख लेते हैं। Massive अपने टिकर एंडपॉइंट के माध्यम से सक्रिय फ्लैग के साथ डीलिस्ट किए गए प्रतीकों को उजागर करता है, और एक अलग टिकर इवेंट एंडपॉइंट में प्रतीक परिवर्तन, डीलिस्टिंग, विलय और अधिग्रहण को रिकॉर्ड के रूप में रखता है।
"हम डेटा को नहीं बदलते हैं और न ही टिकर परिवर्तनों को एक एकल एग्रीगेट सीरीज में जोड़ते हैं।"
Massive नॉलेज बेस, "Massive टिकर परिवर्तनों और अधिग्रहणों को कैसे संभालता है?", अगस्त 2026 को पढ़ा गया।
यदि किसी प्रदाता के दस्तावेज़ डीलिस्ट किए गए प्रतीकों के बारे में कुछ नहीं कहते हैं, तो डाउनलोड को तब तक केवल सर्वाइवर्स-ओनली मानें जब तक आपने जांच न कर ली हो। स्रोतों के बीच कवरेज व्यापक रूप से भिन्न होती है: फ्री स्टॉक मार्केट डेटा API पर हमारे नोट्स देखें।
रीसायकल किए गए टिकर का जाल
लिस्टिंग समाप्त होने के बाद प्रतीक वापस प्रचलन में आ जाते हैं। नीचे दिया गया पैनल उन प्रतीकों की गणना करता है जिन्होंने कम से कम 200 दैनिक बार प्रिंट किए, कम से कम 250 कैलेंडर दिनों तक शांत रहे, फिर 2022 के बाद से एक बिल्कुल नई लिस्टिंग के रूप में दिखाई दिए।
हर आंकड़े के पीछे का पूरा SQL
SELECT
toString(toYear(relisted_on)) AS year,
count() AS relisting_count,
round(avg(dark_stretch)) AS avg_dark_stretch,
max(dark_stretch) AS longest_dark_stretch
FROM
(
SELECT
ticker,
any(relist_date) AS relisted_on,
dateDiff('day', max(date), any(relist_date)) AS dark_stretch
FROM
(
SELECT
d.ticker AS ticker,
d.date AS date,
i.relist_date AS relist_date
FROM global_markets.stocks_daily_aggs AS d
INNER JOIN
(
SELECT
ticker,
min(toDate(listing_date)) AS relist_date
FROM global_markets.stocks_ipos
WHERE toDate(listing_date) BETWEEN '2022-01-01' AND today()
AND ticker NOT IN ('SPCX')
GROUP BY ticker
) AS i ON i.ticker = d.ticker
WHERE d.date < i.relist_date
)
GROUP BY ticker
HAVING dateDiff('day', max(date), any(relist_date)) >= 250
AND count() >= 200
)
GROUP BY year
ORDER BY year2022 में, 16 प्रतीक इस तरह वापस आए, औसतन 3247 कैलेंडर दिनों की चुप्पी के बाद। उस पंक्ति में सबसे लंबा शांत दौर 5734 दिनों का था। केवल प्रतीक पर आधारित प्राइस फाइल एक कंपनी के इतिहास को दूसरी कंपनी पर चिपका देती है, और यह जोड़ पूरी तरह से साफ दिखता है। यह वह मामला है जिसे एक सिक्योरिटी मास्टर पकड़ लेता है और एक साधारण टिकर कॉलम नहीं पकड़ सकता।
डेटा नोट्स और परिभाषाएं
एक प्रतीक को तब तक ट्रेडिंग में माना जाता है जब तक उसका नवीनतम दैनिक बार इस पेज के जनरेट होने के 45 कैलेंडर दिनों के भीतर आता है। वह बफर टेप के सामने के एक से दो दिन के अंतराल और उन पतले नामों को कवर करता है जो सत्र छोड़ देते हैं।
रिटर्न जनवरी एंट्री क्लोज से उस प्रतीक के लिए उपलब्ध अंतिम क्लोज तक के कुल मूल्य रिटर्न हैं, बिना किसी डिविडेंड और बिना किसी वार्षिककरण (annualising) के। पहले के कोहोर्ट्स के पास कंपाउंडिंग के लिए अधिक समय था, इसलिए स्तर पंक्तियों के बीच तुलनीय नहीं हैं। दो औसतों के बीच का अंतर तुलनीय है।
एंट्री यूनिवर्स उन प्रतीकों को रखते हैं जिनकी कीमत $5 या उससे अधिक है और एंट्री महीने के दौरान औसत दैनिक वॉल्यूम 250,000 शेयर या उससे अधिक है। दैनिक टेप में ऑपरेटिंग कंपनियों के साथ फंड, यूनिट और वारंट होते हैं, और यहां कुछ भी उन्हें अलग नहीं करता है, इसलिए एक फंड का बंद होना कॉर्पोरेट डीलिस्टिंग के साथ गिना जाता है। वेंडर फीड में ज्ञात पहचान संघर्ष वाले प्रतीकों को बाद में फिल्टर करने के बजाय हर पैनल में SQL से बाहर रखा गया है।
FAQ
स्टॉक बैकटेस्ट में सर्वाइवरशिप बायस क्या है?
यह वह त्रुटि है जो तब रह जाती है जब ट्रेडेबल यूनिवर्स उन प्रतीकों से बनाया जाता है जो आज भी मौजूद हैं। जो कंपनियां डीलिस्ट या अधिग्रहित हो गईं, वे कभी टेस्ट में प्रवेश नहीं करतीं, इसलिए रणनीति को केवल वे नाम पेश किए जाते हैं जो वर्तमान तक पहुंच गए।
मुझे कैसे पता चलेगा कि मेरे डेटासेट में सर्वाइवरशिप बायस है?
अपनी फाइल द्वारा कई साल पहले की तारीख के लिए कवर किए गए यूनिवर्स को लें और प्रत्येक प्रतीक के अंतिम बार की जांच करें। यदि 2016 की सूची का हर नाम आज भी कीमतें प्रिंट कर रहा है, तो सूची वर्तमान लिस्टिंग से बनाई गई थी और मृत नाम कभी उसमें थे ही नहीं।
क्या सर्वाइवरशिप बायस हमेशा बैकटेस्ट को बेहतर दिखाता है?
नहीं। दिशा इस बात पर निर्भर करती है कि नाम टेप से कैसे निकले। एक अधिग्रहण प्रीमियम पर बंद हो सकता है, और उन नामों को छोड़ने से कोहोर्ट कम हो जाता है, जबकि डीलिस्टिंग नोटिस की ओर लंबी गिरावट इसका उल्टा करती है। ऊपर दिए गए पैनल वास्तविक कोहोर्ट्स पर दोनों के शुद्ध प्रभाव को मापते हैं।
पॉइंट-इन-टाइम यूनिवर्स क्या है?
यह टेस्ट में प्रत्येक तिथि के अनुसार पुनर्निर्मित एक सदस्यता सूची है, जिसमें केवल उन रिकॉर्ड्स का उपयोग किया जाता है जो उस तिथि पर मौजूद थे। इसमें वे नाम शामिल हैं जो तब ट्रेडेबल थे और बाद में गायब हो गए, और उन नामों को बाहर रखा गया है जो अभी तक लिस्ट नहीं हुए थे।
क्या सर्वाइवरशिप बायस और लुक-अहेड बायस एक ही हैं?
नहीं। लुक-अहेड बायस टाइमिंग के बारे में है: टेस्ट किसी के पास जानकारी होने से पहले उसका उपयोग करता है। सर्वाइवरशिप बायस सदस्यता के बारे में है: टेस्ट hindsight के साथ इकट्ठा किए गए रोस्टर को ट्रेड करता है। एक बैकटेस्ट एक पर साफ और दूसरे पर टूटा हुआ हो सकता है।
यहां हर पैनल में वह SQL है जिसने इसे तैयार किया है। एंट्री महीना बदलें या लिक्विडिटी फिल्टर को ढीला करें, फिर अपने स्वयं के यूनिवर्स नियमों के तहत अंतर को देखने के लिए इसे Strasmore टर्मिनल पर चलाएं।