h5i-db ile point-in-time veri yönetimi ve backtest
h5i-db veritabanı ile backtest süreçlerinde look-ahead bias riskini önleyin. Her yazma işlemini sürümleyerek geçmiş veriyi sabitleyin ve bildirim gecikmelerini analiz edin.
Point-in-time verisi ve veri sızıntısı
Point-in-time verisi, bir veri setinin geçmişteki belirli bir tarihte neyi içerdiğinin kaydıdır. Geriye dönük testlerde (backtest), savunulabilir bir sonuç ile yarının verilerini önceden görmüş bir sonuç arasındaki farkı belirler. h5i-db, Rust ile yazılmış ve Python API’sine sahip, genç ve açık kaynaklı bir zaman serisi veritabanıdır. Her yazma işlemini numaralandırılmış bir sürüm olarak kaydeder ve herhangi bir okuma işleminin geçmişteki bir sürümü sabitlemesine (pin) olanak tanır. Aşağıda, gerçek bildirim verileri üzerinde ölçülen ve sabitleme işleminin engellediği veri sızıntısı ile dizüstü bilgisayarınızda çalıştırabileceğiniz bir senaryo yer almaktadır.
Veri sızıntısı nedir?
Veri sızıntısı, modelin eğitim aşamasında gelecekteki bilgileri kullanması durumudur. Finansal verilerde bu durum, genellikle bir şirketin mali tablolarının açıklandığı tarih ile veritabanına işlendiği tarih arasındaki uyumsuzluktan kaynaklanır. Eğer bir model, geçmişe dönük test sırasında bu zaman farkını dikkate almazsa, gerçekte mevcut olmayan bilgilere erişerek yanıltıcı derecede yüksek performans sonuçları üretir.
h5i-db ile sürüm yönetimi
h5i-db, veritabanı üzerindeki her değişikliği bir sürüm numarası ile takip eder. Bu yapı, araştırmacıların belirli bir tarih ve saatteki veritabanı durumuna dönmesini sağlar. Bu özellik sayesinde, geçmişteki bir tarihte piyasada hangi verilerin bilindiği kesin olarak belirlenebilir.
Uygulama senaryosu
Aşağıdaki örnek, bir dizüstü bilgisayarda h5i-db kullanılarak nasıl tutarlı bir test ortamı oluşturulacağını göstermektedir:
- Veritabanını başlatın ve verileri sürüm numaralarıyla yazın.
- Belirli bir tarih için
pinkomutunu kullanarak veritabanını o ana sabitleyin. - Modelinizi yalnızca bu sabitlenmiş veri kümesi üzerinde çalıştırın.
- Sonuçları, gelecekteki verilerin sızmadığından emin olarak analiz edin.
Sonuç
Point-in-time verisi, finansal modelleme ve strateji geliştirme süreçlerinde güvenilirliğin temel taşıdır. h5i-db gibi araçlar, veritabanı düzeyinde sürümleme yaparak bu karmaşık süreci yönetilebilir ve denetlenebilir hale getirir.
Backtestlerde point-in-time (zaman damgalı) veri nedir?
Her piyasa verisinin iki zaman damgası bulunur. Olay zamanı, olayın gerçekleştiği andır. Ulaşım zamanı ise, verinin raporlayan kurum dışındaki herkes tarafından öğrenilebilir hale geldiği andır. Bir çeyreklik portföy raporu, bir çeyreğin son günündeki pozisyonları tanımlar ve kamuoyuna haftalar sonra ulaşır; bu nedenle yalnızca olay zamanına göre eşleşme yapan bir model, o tarihte kimsenin sahip olmadığı bilgilere erişmiş olur.
Bu süre ölçülebilir niteliktedir. Kurumsal yöneticiler, her çeyreğin kapanışından sonra Form 13F bildirimi yaparlar ve aşağıdaki panel, bir bildirimin kapsadığı çeyrek ile bildirimin yapıldığı gün arasındaki gün sayısını ölçer.
Her verinin arkasındaki tam 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 tarihinde sona eren çeyrek için, 10688 bildirimleri kapsadıkları dönemden ortalama 34.1 gün sonra ulaşmıştır. Panel, bu ölçümü 16 çeyrek boyunca tekrarlar. Bir yönetici, bildirim yaptıktan uzun süre sonra da rapor üzerinde düzeltme yapabilir; bu nedenle geçmiş bir tarihi tanımlayan kayıt, o tarih geçtikten sonra dahi değişmeye devam eder.
İleriye dönük önyargı bir veri depolama sorunudur
Geriye dönük testlerde ileriye dönük önyargı rehberimiz, veri sızıntısını bir disiplin meselesi olarak ele alır: her değişkeni geciktirin ve yayın tarihlerine sadık kalın. Disiplin, birisi unutana kadar sürer ve hata sessizce gerçekleşir. Sızıntı içeren bir geriye dönük test, daha iyi bir Sharpe oranı üretir ve hiçbir hata mesajı vermez.
Point-in-time (zaman damgalı) depolama, bu güvenceyi bir alt katmana taşır. Bir stratejiye sunulan veri çerçevesi, belirli bir sürüme sabitlenmiş bir okumadan geldiğinde, o sürümden sonra yazılan bir satır, strateji kodu ne yaparsa yapsın çerçeve içinde görünemez. Kontrol, bir kod incelemesi olmaktan çıkıp okuma işleminin bir özelliği haline gelir.
Temettüler, zamanlama farkını diğer taraftan gösterir. Nakit temettü önce ilan edilir ve daha sonra ex-dividend (temettü hak edişi öncesi) tarihine girer. Bugün yüklenen bir tablo, simülasyonun yapıldığı tarihte henüz açıklanmamış olanlar da dahil olmak üzere, her ödeme için her iki tarihi de taşır.
Her verinin arkasındaki tam 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 tarihinde başlayan ayda, beyanlar ex-dividend tarihinden ortalama 88.2 gün önce gerçekleşti ve panel, aynı ölçümün 24 aylık dönemini kapsamaktadır. Bu boşluk içindeki simüle edilmiş bir tarihte güncel bir temettü tablosu okunduğunda, ödeme, duyuru henüz yapılmadan haftalar önce tabloda halihazırda yer almaktadır.
Geçmiş verilerin yeniden yazılması
Geç ulaşan veriler bir hata modudur. Yeniden düzenleme ise bir diğeridir. Kurumsal işlemler, daha önce gerçekleşmiş işlemleri (prints) yeniden yazar: dört-bölü-bir oranındaki bir hisse bölünmesinin ardından, düzeltilmiş serideki her bir önceki fiyat dörde bölünür ve bugün indirilen seri, bir yatırımcının geçmişte izlediği piyasa verileriyle artık eşleşmez. Bölünme düzeltmeli fiyat geçmişi üzerine hazırladığımız not, bu aritmetiği detaylandırmaktadır. Burada önemli olan husus sıklıktır.
Her verinin arkasındaki tam 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 çeyreğinde başlayan dönemde, 131 adet ileri yönlü bölünme ve 303 adet ters bölünme yürürlüğe girmiştir. Bunların her biri, bir araştırma hattının halihazırda önbelleğe almış olabileceği fiyat geçmişini yeniden düzenler. Sürümlü depolama, bu yeniden düzenlemeyi engellemez. Yeni durumu yeni bir sürüm olarak kaydeder ve eskisini okunabilir tutar; bayat bir sonucu tekrarlanabilir bir sonuca dönüştüren yöntem budur.
Haber zaman damgaları da aynı tuzağı küçük ölçekte barındırır.
Her verinin arkasındaki tam 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_hourManşetler, New York gününün tüm 24 saati boyunca günün her saati yayınlanır. Son doksan gün içinde 09:00 saati 790 makaleye, 20:00 saati ise 404 makaleye ev sahipliği yapmıştır. Bir akşam manşetini o günün 16:00 kapanışına damgalamak, kapanışta işlem yapan bir stratejiye birkaç saatlik geriye dönük bilgi avantajı sağlar.
Uygulayabileceğiniz bir geçmişe dönük senaryo
Buradaki her şey Python paketi üzerinde kalır. Proje ayrıca, bu izlenecek yolun ihtiyaç duymadığı, ayrı bir kurulum gerektiren bir Rust komut satırı aracıyla birlikte gelir. Örnek veriler yerel olarak oluşturulur, herhangi bir indirme işlemi yapılmaz.
- Sabitlenmiş sürümü kurun:
pip install 'h5i-db==0.1.6', 4 Ağustos 2026 tarihinde yayınlanmıştır. Python 3.9 veya daha yeni bir sürüm gerektirir vepyarrow>=14getirir. Önceden oluşturulmuş paketler (wheels), x86-64 ve arm64 mimarilerinde Linux'u, ayrıca Apple silicon macOS ve x86-64 mimarisinde Windows'u kapsar. - Veriyi bir kez,
import pyarrow as paveimport pyarrow.parquet as pqsonrasında tanımlayın: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')kullanarak yerel bir dosyaya iki hayali satır yazın; buradad1ved2zaman dilimi bilgisine sahip tarih-zaman ifadeleridir.- Veritabanını ve tabloyu oluşturun, zaman sütununu adlandırın:
db = h5i_db.Database('pit.db', create=True)ve ardındandb.create_table('prices', schema, time_column='ts'). - Dosyayı bir anahtar altında içeri aktarın:
db.append('prices', pq.read_table('day1.parquet'), idempotency_key='load-day1'). Çağrı, yaptığı commit işlemini döndürür. - Aynı satırı tekrar çalıştırın. Proje belgeleri, aynı anahtarı taşıyan bir tekrarın, halihazırda oluşturduğu commit işlemini bulduğunu ve satırları ikinci kez yazmak yerine
"segments_added": 0ile döndürdüğünü belirtir. Yeniden denemenin her iki tarafınadb.versions('prices')yazdırın ve sürüm listesinin sabit kaldığını gözlemleyin. idempotency_key='load-day2'altında ikinci bir günü içeri aktarın, ardından her ikisini de sorgulayın:db.sql('SELECT symbol, count(*) AS n, avg(price) AS px FROM prices GROUP BY symbol').to_pandas().- Tabloyu ikinci gün eklenmeden önceki haliyle okuyun:
db.read('prices', version=1). Aynı yöntem, aynı iş içinas_of=vesnapshot=argümanlarını alır.
- adım üzerinde durulması gereken adımdır. Yinelenen bir ekleme işlemi hata vermez. O andan itibaren tabloyu hatalı bırakır ve sonraki her çalıştırma bu hasarı sessizce devralır. 8. adım ise kazanımdır: Mart ayında hesaplanan bir sayı, aynı sabitlenmiş sürümden Ağustos ayında yeniden hesaplanabilir; bu, tekrarlanabilir geriye dönük test üzerine yazdığımız makalenin çerçeve düzeyinde savunduğu özelliktir.
Projenin iddiaları ve doğruladıklarımız
README dosyası bir kıyaslama verisiyle başlıyor:
20 milyon satırlık OHLCV+VWAP toplamlarında DuckDB ve Polars'tan 4,5 kat daha hızlı
Bu rakam, projenin kendi ölçümüdür ve Ağustos 2026'da alınan h5i-db README dosyasından alıntılanmıştır. Bu ölçümü biz gerçekleştirmedik ve yukarıdaki hiçbir analiz buna dayanmamaktadır.
Burada hızdan ziyade olgunluk önem taşımaktadır. Depo, bu metin yazıldığı sırada yirmi dokuz yıldıza sahipti ve Apache-2.0 lisansı altında 0.1.6 sürümünde bulunuyordu. Bu kombinasyon, küçük bir geliştirici havuzu ve ara sürümler arasında değişebilecek bir API anlamına gelmektedir. Motorun yük altında çalıştığına dair uzun süreli bir kamu kaydı bulunmamaktadır. Tam sürümü sabitlemek ve veritabanını besleyen parquet dosyalarını saklamak, bir sürümün davranış değiştirmesi durumunda geri dönüş yolu sağlar. Kendi ham kopyanızı tutmak, bir tedarikçinin geçmişi sizin bilginiz dışında yeniden şekillendirmesine karşı koruma sağlayan alışkanlıkla aynıdır; bu tema hisse senedi verilerinde hayatta kalma yanlılığı boyunca işlenen ana fikirdir.
Sıkça Sorulan Sorular
Point-in-time verisi nedir?
Point-in-time verisi, her bir bilginin öğrenilebilir hale geldiği zaman damgalarıyla saklanan bir veri setidir; böylece bir sorgu, geçmişteki herhangi bir tarihte nelerin görülebilir olduğunu yeniden oluşturabilir. Basit bir "en güncel değer" tablosu bunu yapamaz, çünkü geçmişi bugünün düzeltilmiş rakamlarıyla üzerine yazarak siler.
Versiyonlu depolama, ileriye dönük bakış (look-ahead) yanlılığını ortadan kaldırır mı?
Hayır. Versiyonlama, bir simülasyonun karar anından sonra yazılan değerleri okuması gibi bir veri sızıntısı türünü düzeltir. Özellik oluşturma süreci, tüm geçmişi üzerinden hesaplanan istatistiklerle bir örneği ölçeklendirmek gibi başka yollarla hala sızıntıya neden olabilir.
Bir idempotency key (eşdeğerlik anahtarı), veri girişi sırasında ne işe yarar?
Bir yazma işlemini etiketler, böylece yeniden deneme aynı yazma işlemi olarak tanınır. Bir yükleyici, bir çökme sonrasında aynı satırları iki kez eklemeden yeniden çalışabilir; bu, tablonun sessizce hatalı kalmasına yol açan bir başarısızlık türüdür.
h5i-db üretime hazır mı?
Bu metnin yazıldığı sırada Apache-2.0 lisansı altında, GitHub üzerinde yirmi dokuz yıldıza sahip sıfır nokta bir nokta altı versiyonundadır. Bu ölçekteki erken aşama yazılımlar API değişiklikleri ve kısıtlı bir kamu geçmişi taşır; bir versiyon sabitlemesi ve kaynak dosyalarınızın kendi kopyanız, bir değerlendirmeyi geri döndürülebilir kılan unsurlardır.
Yukarıdaki her panel, kendisini oluşturan SQL ile birlikte sunulmaktadır; bu nedenle birini açın ve sayının nasıl hesaplandığını okuyun. Aynı sorular, Strasmore terminali üzerinde yalın bir dille sorulabilir.