Strasmore Research
Belajar Matt ConnorOleh Matt Connor · data as of August 16, 2026 · refreshed weekly

h5i-db: Data Point-in-Time untuk Backtest

Data point-in-time menghalang bias look-ahead dalam backtest pada lapisan storan. Ketahui bagaimana h5i-db menguruskan versi data dan kesan lag pemfailan terhadap sejarah.

Data point-in-time dan integriti backtest

Data point-in-time ialah rekod mengenai kandungan set data pada tarikh yang lalu. Dalam backtest, ia memisahkan hasil yang boleh dipertahankan daripada hasil yang secara senyap menggunakan data masa depan. h5i-db ialah pangkalan data siri masa sumber terbuka yang baharu, ditulis dalam Rust dengan API Python, yang menyimpan setiap penulisan sebagai versi bernombor dan membolehkan sebarang bacaan ditetapkan pada versi terdahulu. Di bawah ialah kebocoran yang disekat oleh fungsi pinning, diukur berdasarkan data pemfailan sebenar, diikuti dengan senario yang boleh anda jalankan pada komputer riba.

2026-07-01 Kebocoran data dalam backtest

Kebocoran data berlaku apabila maklumat daripada masa depan secara tidak sengaja dimasukkan ke dalam model latihan atau ujian. Dalam analisis kewangan, ini sering berlaku apabila set data tidak mencerminkan apa yang diketahui oleh pasaran pada masa sebenar transaksi dilakukan. Sebagai contoh, jika anda menggunakan data penyata kewangan yang disemak semula (restated) untuk menguji strategi yang sepatutnya dilaksanakan pada tahun dua ribu dua puluh, anda sebenarnya menggunakan maklumat yang belum wujud pada tarikh tersebut.

Mekanisme pinning h5i-db

h5i-db menangani isu ini dengan membenarkan pengguna menetapkan "pin" pada versi pangkalan data tertentu. Apabila anda melakukan pertanyaan (query) dengan pin pada tarikh atau nombor versi yang spesifik, pangkalan data akan mengabaikan semua kemas kini yang berlaku selepas titik masa tersebut. Ini memastikan bahawa backtest anda hanya melihat data yang tersedia secara sah pada masa itu, sekali gus menghapuskan risiko bias pandangan ke hadapan (look-ahead bias).

Senario ujian pada komputer riba

Anda boleh mensimulasikan persekitaran ini dengan langkah-langkah berikut:

  • Muat turun pustaka h5i-db melalui pengurus pakej Python anda.
  • Lakukan beberapa siri penulisan data harga saham dengan cap masa yang berbeza.
  • Lakukan pertanyaan (query) tanpa pin untuk melihat data terkini.
  • Lakukan pertanyaan (query) dengan pin pada tarikh yang lebih awal untuk melihat bagaimana pangkalan data mengembalikan keadaan data pada masa itu.

Muat turun kod contoh di sini

Apakah data point-in-time dalam backtest?

Setiap fakta pasaran membawa dua cap masa. Masa peristiwa ialah waktu sesuatu perkara itu berlaku. Masa ketibaan ialah waktu maklumat tersebut boleh diketahui oleh sesiapa sahaja di luar firma yang melaporkannya. Laporan pegangan suku tahunan menerangkan kedudukan yang dipegang pada hari terakhir sesuatu suku tahun dan sampai kepada orang awam beberapa minggu kemudian, jadi model yang menggabungkan data berdasarkan masa peristiwa sahaja akan menerima maklumat yang tidak dimiliki oleh sesiapa pun pada waktu itu.

Jarak tersebut boleh diukur. Pengurus institusi memfailkan Borang 13F selepas penutupan setiap suku tahun, dan panel di bawah mengukur bilangan hari antara suku tahun yang diterangkan dalam pemfailan dengan hari ia difailkan.

PertanyaanTempoh masa pemfailan pegangan 13F sampai kepada orang awam
SQL tepat di sebalik setiap nombor
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_end
Run this yourself

Bagi suku tahun yang berakhir pada 2026-06-30, pemfailan 10688 tiba secara purata 34.1 hari selepas tempoh yang diliputi. Panel tersebut mengulangi pengukuran itu merentasi 16 suku tahun. Seorang pengurus juga boleh meminda laporan lama selepas memfailkannya, jadi rekod yang menerangkan tarikh yang lalu terus berubah sebaik sahaja tarikh tersebut berlalu.

Bias pandang ke hadapan merupakan masalah penyimpanan

Panduan kami mengenai bias pandang ke hadapan dalam ujian ke belakang menganggap kebocoran data sebagai satu disiplin: lambatkan setiap ciri dan patuhi tarikh penerbitan. Disiplin ini bertahan sehingga seseorang terlupa, dan kegagalan tersebut berlaku secara senyap. Ujian ke belakang yang mengalami kebocoran akan mencatatkan Sharpe ratio yang lebih baik tanpa sebarang ralat.

Penyimpanan point-in-time memindahkan jaminan tersebut ke lapisan yang lebih bawah. Apabila kerangka data yang diberikan kepada sesuatu strategi datang daripada bacaan yang dikunci pada satu versi, baris yang ditulis selepas versi tersebut tidak akan muncul di dalamnya, tidak kira apa jua tindakan kod strategi seterusnya. Pemeriksaan itu tidak lagi menjadi semakan kod, sebaliknya menjadi sifat bagi bacaan tersebut.

Dividen menunjukkan jurang masa daripada sudut yang berbeza. Dividen tunai diisytiharkan terlebih dahulu dan tarikh ex-dividend berlaku kemudian, manakala jadual yang dimuatkan hari ini membawa kedua-dua tarikh bagi setiap pembayaran, termasuk pembayaran yang belum diumumkan pada tarikh yang sedang disimulasikan.

PertanyaanBilangan hari antara pengisytiharan dividen dan tarikh ex-date, mengikut bulan
SQL tepat di sebalik setiap nombor
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 month
Run this yourself

Pada bulan yang bermula pada 2026-07-01, pengisytiharan dibuat secara purata 88.2 hari sebelum tarikh ex-dividend, dan panel tersebut meliputi 24 bulan bagi ukuran yang sama. Jika anda membaca jadual dividen moden pada tarikh simulasi di dalam jurang tersebut, pembayaran itu sudah pun tersenarai di sana, beberapa minggu sebelum pengumuman tersebut wujud.

Sejarah tersimpan ditulis semula

Ketibaan lewat merupakan satu mod kegagalan. Penyataan semula adalah satu lagi. Tindakan korporat menulis semula harga yang telah pun direkodkan: selepas pecahan saham 4-untuk-1, setiap harga terdahulu dalam siri terlaras dibahagikan dengan empat, dan siri yang dimuat turun hari ini tidak lagi sepadan dengan pita harga yang diperhatikan oleh pedagang. Nota kami mengenai sejarah harga terlaras pecahan menghuraikan pengiraan tersebut. Perkara yang penting di sini ialah kekerapan.

PertanyaanPecahan saham yang berkuat kuasa setiap suku tahun, forward dan reverse
SQL tepat di sebalik setiap nombor
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_date
Run this yourself

Dalam suku yang bermula pada 2026-04-01, 131 pecahan saham ke hadapan dan 303 pecahan saham terbalik telah berkuat kuasa. Setiap satunya menyatakan semula sejarah harga yang mungkin telah pun disimpan dalam cache oleh saluran penyelidikan. Storan berversi tidak menghalang penyataan semula tersebut. Ia merekodkan keadaan baharu sebagai versi baharu dan mengekalkan versi lama agar boleh dibaca, yang mana ia mengubah hasil yang lapuk menjadi hasil yang boleh dihasilkan semula.

Cap masa berita membawa perangkap yang sama dalam skala kecil.

PertanyaanWaktu tajuk berita pasaran diterbitkan, mengikut jam di New York
SQL tepat di sebalik setiap nombor
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
Run this yourself

Tajuk berita diterbitkan sepanjang masa, merentasi kesemua 24 jam hari dagangan New York. Jam 09:00 mencatatkan 790 artikel sepanjang 90 hari yang lalu, dan jam 20:00 mencatatkan 404. Meletakkan cap masa tajuk berita waktu malam pada harga penutup jam 4:00 petang hari tersebut memberikan strategi yang berdagang pada waktu penutup dengan kelebihan beberapa jam pengetahuan retrospektif.

Senario point-in-time yang boleh anda jalankan

Segala-galanya di sini kekal pada pakej Python. Projek ini juga membekalkan alat baris perintah Rust, satu pemasangan berasingan yang tidak diperlukan oleh panduan ini. Data sampel dijana secara setempat, tanpa sebarang muat turun.

  1. Pasang keluaran yang ditetapkan: pip install 'h5i-db==0.1.6', diterbitkan pada 4 Ogos 2026. Ia memerlukan Python 3.9 atau lebih baharu dan membawa pyarrow>=14. Wheel yang telah dibina merangkumi Linux pada x86-64 dan arm64, serta macOS Apple silicon dan Windows pada x86-64.
  2. Perihalkan data sekali, selepas import pyarrow as pa dan import pyarrow.parquet as pq: schema = pa.schema([('ts', pa.timestamp('us', tz='UTC')), ('symbol', pa.string()), ('price', pa.float64())]).
  3. Tulis dua baris ciptaan ke dalam fail setempat dengan pq.write_table(pa.table({'ts': [d1, d2], 'symbol': ['ACME', 'ACME'], 'price': [10.0, 10.5]}, schema=schema), 'day1.parquet'), di mana d1 dan d2 merupakan tarikh-masa yang peka zon waktu.
  4. Cipta pangkalan data dan jadual tersebut, dengan menamakan lajur masa: db = h5i_db.Database('pit.db', create=True) kemudian db.create_table('prices', schema, time_column='ts').
  5. Masukkan fail di bawah satu kunci: db.append('prices', pq.read_table('day1.parquet'), idempotency_key='load-day1'). Panggilan tersebut mengembalikan komit yang dibuatnya.
  6. Jalankan baris yang sama sekali lagi. Projek ini mendokumenkan bahawa ulangan yang membawa kunci yang sama akan menemui komit yang telah dihasilkannya dan mengembalikannya dengan "segments_added": 0 dan bukannya menulis baris tersebut buat kali kedua. Cetak db.versions('prices') pada kedua-dua belah percubaan semula dan perhatikan senarai versi kekal tidak berubah.
  7. Masukkan hari kedua di bawah idempotency_key='load-day2', kemudian buat pertanyaan merentas kedua-duanya: db.sql('SELECT symbol, count(*) AS n, avg(price) AS px FROM prices GROUP BY symbol').to_pandas().
  8. Baca jadual seperti keadaan sebelum hari kedua dimasukkan: db.read('prices', version=1). Kaedah yang sama mengambil argumen as_of= dan snapshot= untuk tugasan yang sama.

Langkah 6 adalah langkah yang perlu diberi perhatian. Penambahan pendua tidak mencetuskan ralat. Ia menyebabkan jadual menjadi salah sejak saat itu, dan setiap larian selepasnya mewarisi kerosakan tersebut secara senyap. Langkah 8 adalah hasilnya: nombor yang dikira pada bulan Mac boleh dikira semula pada bulan Ogos daripada versi yang ditetapkan yang sama, iaitu ciri yang dihujahkan oleh penulisan kami mengenai backtest yang boleh dihasilkan semula pada peringkat rangka kerja.

Dakwaan projek dan perkara yang kami semak

README projek tersebut bermula dengan penanda aras:

lebih 4.5 kali lebih pantas daripada DuckDB dan Polars dalam pengiraan OHLCV+VWAP ke atas 20 juta baris data

Angka tersebut merupakan ukuran projek itu sendiri, dipetik daripada README h5i-db seperti yang diperoleh pada Ogos 2026. Kami tidak menjalankan ujian tersebut, dan tiada perkara di atas yang bergantung kepadanya.

Kematangan lebih penting daripada kelajuan dalam konteks ini. Repositori tersebut mempunyai 29 bintang pada masa penulisan dan berada pada versi 0.1.6 di bawah lesen Apache-2.0. Gabungan ini bermakna kumpulan penyelenggara yang kecil dan API yang masih boleh berubah antara keluaran titik. Tiada rekod awam yang panjang mengenai enjin ini yang beroperasi di bawah beban kerja. Menetapkan versi yang tepat dan menyimpan fail parquet yang membekalkan pangkalan data tersebut menyediakan jalan keluar jika sesuatu keluaran mengubah kelakuan sistem. Menyimpan salinan mentah anda sendiri adalah tabiat yang sama yang melindungi daripada vendor yang mengubah sejarah di belakang anda, tema yang berterusan melalui bias kemandirian dalam data saham.

Soalan Lazim

Apakah data point-in-time?

Data point-in-time ialah set data yang disimpan bersama cap masa bagi setiap fakta yang boleh diketahui, supaya pertanyaan boleh membina semula apa yang kelihatan pada mana-mana tarikh yang lalu. Jadual "nilai terkini" biasa tidak boleh melakukan perkara ini, kerana ia menimpa data masa lalu dengan nombor yang telah diperbetulkan pada hari ini.

Adakah storan berversi menghapuskan bias pandang ke hadapan (look-ahead bias)?

Tidak. Pemversian membetulkan satu kelas kebocoran, iaitu jenis di mana satu larian membaca nilai yang ditulis selepas masa keputusan disimulasikan. Pembinaan ciri masih boleh bocor melalui cara lain, seperti menskalakan sampel mengikut statistik yang dikira sepanjang sejarah penuhnya.

Apakah fungsi kunci idempotensi semasa proses ingest?

Ia melabelkan penulisan supaya percubaan semula dikenali sebagai penulisan yang sama. Pemuat (loader) kemudian boleh dijalankan semula selepas kegagalan sistem tanpa menambah baris yang sama dua kali, iaitu kegagalan yang menyebabkan jadual menjadi salah secara senyap.

Adakah h5i-db sedia untuk pengeluaran?

Ia adalah versi 0.1.6 dengan 29 bintang di GitHub pada masa penulisan, di bawah lesen Apache-2.0. Perisian awal dengan saiz sedemikian membawa perubahan API dan rekod prestasi awam yang tipis, dan penetapan versi (version pin) serta salinan fail sumber anda sendiri adalah perkara yang memastikan penilaian boleh diterbalikkan.


Setiap panel di atas dihantar bersama SQL yang menghasilkannya, jadi buka satu dan baca bagaimana nombor itu dikira. Soalan yang sama boleh ditanya dalam bahasa Inggeris biasa pada terminal Strasmore.