h5i-db: बैकटेस्टिंग के लिए पॉइंट-इन-टाइम डेटा का उपयोग
पॉइंट-इन-टाइम डेटा बैकटेस्टिंग में लुक-अहेड बायस को रोकता है। जानें कि कैसे h5i-db हर राइट को वर्शन करता है और फाइलिंग लैग डेटा पुराने इतिहास की सटीकता को कैसे प्रभावित करता है।
2026-07-01
पॉइंट-इन-टाइम डेटा एक रिकॉर्ड है जो यह बताता है कि किसी पिछली तारीख पर डेटासेट में क्या जानकारी मौजूद थी। बैकटेस्टिंग में, यह उन परिणामों को अलग करता है जिन्हें आप प्रमाणित कर सकते हैं, उन परिणामों से जो भविष्य के आंकड़ों का उपयोग करके प्राप्त किए गए हैं। h5i-db एक नया ओपन-सोर्स टाइम-सीरीज डेटाबेस है। इसे Rust में लिखा गया है और इसमें Python API उपलब्ध है। यह प्रत्येक राइट (write) को एक क्रमांकित संस्करण के रूप में संग्रहीत करता है और किसी भी रीड (read) को पिछले संस्करण को पिन (pin) करने की अनुमति देता है। नीचे वह डेटा लीकेज दी गई है जिसे पिनिंग (pinning) रोकता है। इसे वास्तविक फाइलिंग डेटा पर मापा गया है। इसके बाद एक ऐसा परिदृश्य है जिसे आप अपने लैपटॉप पर चला सकते हैं।
डेटा लीकेज का प्रभाव
जब आप बैकटेस्टिंग करते हैं, तो भविष्य के डेटा का अनजाने में उपयोग करना सबसे बड़ी त्रुटि है। यदि आपका मॉडल आज की फाइलिंग का उपयोग करके कल के रिटर्न की गणना करता है, तो परिणाम भ्रामक होंगे। h5i-db का उपयोग करके, आप यह सुनिश्चित कर सकते हैं कि आपका मॉडल केवल उसी डेटा को देखता है जो उस समय उपलब्ध था। यह प्रक्रिया डेटा लीकेज को पूरी तरह समाप्त कर देती है।
लैपटॉप पर परीक्षण का परिदृश्य
यहाँ क्लिक करके आप एक सरल Python स्क्रिप्ट डाउनलोड कर सकते हैं। यह स्क्रिप्ट h5i-db का उपयोग करके एक छोटे डेटासेट पर बैकटेस्ट चलाती है। आप देखेंगे कि कैसे पिनिंग (pinning) का उपयोग करने से परिणामों में सटीकता आती है और भविष्य के डेटा का प्रभाव समाप्त हो जाता है।
यह तकनीक वित्तीय विश्लेषण में पारदर्शिता और विश्वसनीयता सुनिश्चित करने के लिए आवश्यक है। डेटा की अखंडता बनाए रखने के लिए पॉइंट-इन-टाइम एक्सेस एक मानक अभ्यास होना चाहिए।
निष्कर्ष
###
h5i-db जैसे उपकरण डेवलपर्स को डेटा के साथ अधिक सुरक्षित रूप से काम करने में मदद करते हैं। संस्करण नियंत्रण (version control) के माध्यम से, आप अपने विश्लेषण को किसी भी ऐतिहासिक बिंदु पर फिर से बना सकते हैं। यह न केवल त्रुटियों को कम करता है, बल्कि आपके शोध की गुणवत्ता को भी बढ़ाता है।
बैकटेस्ट में पॉइंट-इन-टाइम डेटा क्या है?
बाजार की प्रत्येक जानकारी के दो टाइमस्टैम्प होते हैं। इवेंट टाइम वह समय है जब घटना घटी। अराइवल टाइम वह समय है जब यह जानकारी उस फर्म के बाहर किसी के लिए भी जानने योग्य बनी जिसने इसे रिपोर्ट किया था। तिमाही होल्डिंग्स रिपोर्ट एक तिमाही के अंतिम दिन रखी गई पोजीशन का विवरण देती है और जनता तक हफ्तों बाद पहुंचती है, इसलिए जो मॉडल केवल इवेंट टाइम के आधार पर डेटा जोड़ता है, उसे ऐसी जानकारी मिल जाती है जो उस समय किसी के पास उपलब्ध नहीं थी।
यह अंतर मापने योग्य है। संस्थागत प्रबंधक प्रत्येक तिमाही के समापन के बाद Form 13F फाइल करते हैं, और नीचे दिया गया पैनल उस तिमाही और उसे फाइल करने के दिन के बीच के दिनों की संख्या को मापता है जिसका विवरण फाइलिंग में दिया गया है।
हर आंकड़े के पीछे का पूरा SQL
WITH per_filing AS
(
SELECT
accession_number,
any(toDate(parseDateTimeBestEffortOrNull(toString(period)))) AS period_end,
any(toDate(filing_date)) AS filed_on
FROM global_markets.stocks_13f_filings
WHERE filing_date >= '2023-01-01'
GROUP BY accession_number
)
SELECT
toString(period_end) AS period_end_date,
countDistinct(accession_number) AS filings_count,
round(avg(dateDiff('day', period_end, filed_on)), 1) AS avg_days_to_public
FROM per_filing
WHERE period_end IS NOT NULL
AND filed_on >= period_end
AND filed_on <= period_end + 400
GROUP BY period_end
HAVING filings_count >= 100
ORDER BY period_end2026-06-30 को समाप्त होने वाली तिमाही के लिए, 10688 फाइलिंग उस अवधि के औसत 34.1 दिनों बाद पहुंची जिसे वे कवर करती हैं। यह पैनल 16 तिमाहियों में उस माप को दोहराता है। एक प्रबंधक फाइलिंग के लंबे समय बाद भी रिपोर्ट में संशोधन कर सकता है, इसलिए किसी पिछली तारीख का विवरण देने वाला रिकॉर्ड उस तारीख के बीत जाने के बाद भी बदलता रहता है।
Look-ahead bias एक स्टोरेज समस्या है
हमारा backtesting में look-ahead bias पर गाइड लीकेज को एक अनुशासन के रूप में देखता है: हर फीचर को लैग (lag) करें और प्रकाशन तिथियों का सम्मान करें। अनुशासन तब तक बना रहता है जब तक कोई इसे भूल न जाए, और यह विफलता चुपचाप होती है। एक लीक्ड बैकटेस्ट बेहतर Sharpe ratio दिखाता है और इसमें कोई त्रुटि नहीं होती।
Point-in-time स्टोरेज इस गारंटी को एक स्तर नीचे ले जाता है। जब किसी रणनीति को दिया गया डेटा फ्रेम किसी ऐसे रीड से आता है जो एक वर्जन पर पिन किया गया है, तो उस वर्जन के बाद लिखी गई कोई भी पंक्ति उसमें दिखाई नहीं दे सकती, चाहे रणनीति का कोड आगे कुछ भी करे। यह जांच कोड समीक्षा के बजाय रीड की एक विशेषता बन जाती है।
लाभांश (dividends) इस समय के अंतराल को दूसरी तरफ से दिखाते हैं। नकद लाभांश पहले घोषित किया जाता है और बाद में ex-date आती है, और आज लोड की गई तालिका में हर भुगतान के लिए दोनों तिथियां होती हैं, जिनमें वे भी शामिल हैं जिनकी घोषणा सिम्युलेट की जा रही तारीख पर नहीं हुई थी।
हर आंकड़े के पीछे का पूरा SQL
SELECT
toString(toStartOfMonth(ex_dividend_date)) AS month,
round(avg(dateDiff('day', declaration_date, ex_dividend_date)), 1) AS avg_days_announced_ahead,
countDistinct(ticker) AS payers_count
FROM global_markets.stocks_dividends
WHERE ex_dividend_date >= toStartOfMonth(today() - 730)
AND ex_dividend_date < toStartOfMonth(today())
AND declaration_date >= '1990-01-01'
AND declaration_date <= ex_dividend_date
GROUP BY month
ORDER BY month2026-07-01 से शुरू होने वाले महीने में, घोषणाएं ex-dividend date से औसतन 88.2 दिन पहले आईं, और पैनल उसी माप के 24 महीनों को कवर करता है। उस अंतराल के भीतर एक सिम्युलेटेड तारीख पर एक आधुनिक लाभांश तालिका को पढ़ें, तो भुगतान वहां पहले से ही मौजूद होता है, घोषणा के अस्तित्व में आने से कई सप्ताह पहले।
स्टोर्ड हिस्ट्री का पुनर्लेखन
देर से आगमन एक विफलता का प्रकार है। पुनर्कथन (restatement) दूसरा प्रकार है। कॉर्पोरेट कार्रवाइयां उन कीमतों को फिर से लिखती हैं जो पहले ही प्रिंट हो चुकी हैं: चार-के-बदले-एक (4-for-1) स्प्लिट के बाद, एडजस्ट की गई श्रृंखला में प्रत्येक पिछली कीमत को चार से विभाजित किया जाता है, और आज डाउनलोड की गई श्रृंखला उस टेप से मेल नहीं खाती जिसे ट्रेडर ने देखा था। स्प्लिट-एडजस्टेड प्राइस हिस्ट्री पर हमारा नोट इसके गणित को स्पष्ट करता है। यहाँ महत्वपूर्ण बात इसकी आवृत्ति है।
हर आंकड़े के पीछे का पूरा SQL
SELECT
toString(toStartOfQuarter(execution_date)) AS quarter_start_date,
countDistinctIf(id, split_to > split_from) AS forward_splits,
countDistinctIf(id, split_to < split_from) AS reverse_splits
FROM global_markets.stocks_splits
WHERE execution_date >= toStartOfQuarter(today() - 1460)
AND execution_date < toStartOfQuarter(today())
AND split_from > 0
AND split_to > 0
GROUP BY quarter_start_date
ORDER BY quarter_start_date2026-04-01 से शुरू होने वाली तिमाही में, 131 फॉरवर्ड स्प्लिट और 303 रिवर्स स्प्लिट प्रभावी हुए। प्रत्येक स्प्लिट उस मूल्य इतिहास को फिर से लिखता है जिसे रिसर्च पाइपलाइन ने पहले ही कैश (cache) कर लिया हो सकता है। वर्शन्ड स्टोरेज (versioned storage) पुनर्कथन को रोकता नहीं है। यह नई स्थिति को एक नए संस्करण के रूप में रिकॉर्ड करता है और पुरानी स्थिति को पढ़ने योग्य बनाए रखता है, जो एक पुराने परिणाम को पुनरुत्पादनीय (reproducible) परिणाम में बदल देता है।
न्यूज़ टाइमस्टैम्प में भी यही समस्या छोटे पैमाने पर मौजूद होती है।
हर आंकड़े के पीछे का पूरा SQL
SELECT
formatDateTime(toTimeZone(published_utc, 'America/New_York'), '%H:00') AS et_hour,
countDistinct(id) AS articles
FROM global_markets.stocks_news
WHERE published_utc >= today() - 90
GROUP BY et_hour
ORDER BY et_hourन्यू यॉर्क के दिन के सभी 24 घंटों में हेडलाइंस लगातार प्रिंट होती रहती हैं। पिछले नब्बे दिनों में 09:00 बजे के समय में 790 लेख आए, और 20:00 बजे के समय में 404 लेख आए। किसी शाम की हेडलाइन को उस दिन के शाम चार बजे के क्लोज़ (close) के साथ जोड़ना, क्लोज़ पर ट्रेड करने वाली रणनीति को कई घंटों का 'हिंडसाइट' (hindsight) लाभ दे देता है।
एक पॉइंट-इन-टाइम परिदृश्य जिसे आप चला सकते हैं
यहाँ सब कुछ Python पैकेज पर आधारित है। यह प्रोजेक्ट एक Rust कमांड लाइन टूल के साथ भी आता है, जो एक अलग इंस्टॉलेशन है और इस वॉकथ्रू के लिए इसकी आवश्यकता नहीं है। नमूना डेटा स्थानीय रूप से उत्पन्न होता है, किसी डाउनलोड की आवश्यकता नहीं है।
- पिन किया गया रिलीज़ इंस्टॉल करें:
pip install 'h5i-db==0.1.6', जिसे 4 अगस्त 2026 को प्रकाशित किया गया था। इसके लिए Python 3.9 या उससे नया संस्करण चाहिए और यहpyarrow>=14लाता है। प्रीबिल्ट व्हील्स Linux (x86-64 और arm64), Apple सिलिकॉन macOS, और Windows (x86-64) को कवर करते हैं। - डेटा का विवरण एक बार दें,
import pyarrow as paऔरimport pyarrow.parquet as pqके बाद:schema = pa.schema([('ts', pa.timestamp('us', tz='UTC')), ('symbol', pa.string()), ('price', pa.float64())])। pq.write_table(pa.table({'ts': [d1, d2], 'symbol': ['ACME', 'ACME'], 'price': [10.0, 10.5]}, schema=schema), 'day1.parquet')के साथ एक स्थानीय फ़ाइल में दो काल्पनिक पंक्तियाँ लिखें, जहाँd1औरd2टाइमज़ोन-अवेयर डेटटाइम हैं।- डेटाबेस और टेबल बनाएँ, समय कॉलम का नाम रखें:
db = h5i_db.Database('pit.db', create=True)फिरdb.create_table('prices', schema, time_column='ts')। - फ़ाइल को एक की (key) के अंतर्गत इनजेस्ट करें:
db.append('prices', pq.read_table('day1.parquet'), idempotency_key='load-day1')। यह कॉल उस कमिट को लौटाती है जो उसने बनाया है। - उसी लाइन को दोबारा चलाएँ। प्रोजेक्ट दस्तावेज़ बताते हैं कि समान की (key) के साथ दोहराव होने पर यह उस कमिट को ढूँढ लेता है जिसे उसने पहले ही बनाया था और इसे
"segments_added": 0के साथ लौटाता है, बजाय पंक्तियों को दूसरी बार लिखने के। रिट्राई के दोनों ओरdb.versions('prices')प्रिंट करें और देखें कि वर्ज़न सूची स्थिर रहती है। idempotency_key='load-day2'के अंतर्गत दूसरे दिन का डेटा इनजेस्ट करें, फिर दोनों के बीच क्वेरी करें:db.sql('SELECT symbol, count(*) AS n, avg(price) AS px FROM prices GROUP BY symbol').to_pandas()।- टेबल को उस स्थिति में पढ़ें जैसी वह दूसरे दिन के आने से पहले थी:
db.read('prices', version=1)। यही विधि समान कार्य के लिएas_of=औरsnapshot=तर्क लेती है।
छठा चरण ध्यान देने योग्य है। एक डुप्लिकेट अपेंड कोई त्रुटि नहीं देता है। यह उस क्षण से टेबल को गलत छोड़ देता है, और उसके बाद के प्रत्येक रन में वह क्षति चुपचाप बनी रहती है। आठवां चरण इसका परिणाम है: मार्च में गणना की गई संख्या को अगस्त में उसी पिन किए गए वर्ज़न से पुनर्गणना की जा सकती है, जो कि reproducible backtest के हमारे लेख में फ्रेमवर्क स्तर पर तर्क दिया गया गुण है।
प्रोजेक्ट के दावे और हमारी जांच
README की शुरुआत एक बेंचमार्क के साथ होती है:
20 मिलियन पंक्तियों के OHLCV+VWAP रोलअप पर DuckDB और Polars से 4.5 गुना से अधिक तेज़
यह आंकड़ा प्रोजेक्ट का अपना मापन है, जिसे अगस्त 2026 में लिए गए h5i-db README से उद्धृत किया गया है। हमने इसका परीक्षण नहीं किया है, और ऊपर दी गई कोई भी जानकारी इस पर निर्भर नहीं है।
यहाँ गति से अधिक परिपक्वता (maturity) मायने रखती है। लेखन के समय रिपॉजिटरी में 29 स्टार थे और यह Apache-2.0 लाइसेंस के तहत संस्करण 0.1.6 पर है। इस संयोजन का अर्थ है कि इसे बनाए रखने वालों की संख्या कम है और इसका API अभी भी पॉइंट रिलीज़ के बीच बदल सकता है। लोड के तहत इंजन के चलने का कोई लंबा सार्वजनिक रिकॉर्ड उपलब्ध नहीं है। सटीक संस्करण को पिन करना और डेटाबेस को फीड करने वाली parquet फाइलों को सुरक्षित रखना एक रास्ता देता है, यदि कोई रिलीज़ व्यवहार में बदलाव लाती है। अपने पास कच्चा डेटा (raw copy) रखना वही आदत है जो आपको वेंडर द्वारा डेटा में बदलाव करने से बचाती है, और यही विषय स्टॉक डेटा में सर्वाइवरशिप बायस में भी प्रमुखता से आता है।
अक्सर पूछे जाने वाले प्रश्न
पॉइंट-इन-टाइम डेटा क्या है?
पॉइंट-इन-टाइम डेटा एक ऐसा डेटासेट है जिसे उन टाइमस्टैम्प के साथ संग्रहीत किया जाता है जिन पर प्रत्येक तथ्य ज्ञात हुआ था, ताकि कोई क्वेरी यह पुनर्निर्माण कर सके कि किसी भी पिछली तारीख पर क्या दिखाई दे रहा था। एक सामान्य "नवीनतम मान" (latest value) तालिका ऐसा नहीं कर सकती, क्योंकि यह आज के संशोधित आंकड़ों के साथ अतीत को ओवरराइट कर देती है।
क्या वर्शन्ड स्टोरेज लुक-अहेड बायस को खत्म करता है?
नहीं। वर्शनिंग लीकेज के एक वर्ग को ठीक करती है, वह प्रकार जहाँ एक रन उन मानों को पढ़ता है जो सिम्युलेटेड निर्णय समय के बाद लिखे गए थे। फीचर निर्माण अभी भी अन्य तरीकों से लीक हो सकता है, जैसे कि किसी नमूने को उसके पूरे इतिहास पर गणना किए गए आंकड़ों द्वारा स्केल करना।
इनजेस्ट के दौरान इडेम्पोटेंसी की (idempotency key) क्या करती है?
यह एक राइट (write) को लेबल करती है ताकि रिट्राई को उसी राइट के रूप में पहचाना जा सके। इसके बाद एक लोडर क्रैश के बाद बिना उन्हीं पंक्तियों को दो बार जोड़े फिर से चल सकता है, जो कि वह विफलता है जो एक तालिका को चुपचाप गलत छोड़ देती है।
क्या h5i-db प्रोडक्शन के लिए तैयार है?
यह Apache-2.0 के तहत, लेखन के समय GitHub पर उनतीस स्टार के साथ संस्करण 0.1.6 है। उस आकार के शुरुआती सॉफ्टवेयर में API में बदलाव और सीमित सार्वजनिक ट्रैक रिकॉर्ड होता है, और एक वर्शन पिन के साथ-साथ सोर्स फाइलों की आपकी अपनी कॉपी ही वह चीज है जो मूल्यांकन को प्रतिवर्ती (reversible) रखती है।
ऊपर दिया गया प्रत्येक पैनल उस SQL के साथ आता है जिसने इसे तैयार किया है, इसलिए किसी एक को खोलें और पढ़ें कि संख्या की गणना कैसे की गई थी। Strasmore टर्मिनल पर सामान्य अंग्रेजी में भी यही प्रश्न पूछे जा सकते हैं।