Strasmore Research
Jifunze Matt ConnorNa Matt Connor · data as of August 16, 2026 · refreshed weekly

h5i-db: Data ya point-in-time kwa ajili ya backtest

Data ya point-in-time inazuia upendeleo wa look-ahead katika backtest kwenye safu ya hifadhi. Jinsi h5i-db inavyotunza matoleo na athari za filing lag kwenye data.

Data ya point-in-time na kuzuia uvujaji wa taarifa

Data ya point-in-time ni rekodi ya kile ambacho seti ya data ilikuwa nacho katika tarehe iliyopita. Katika backtest, data hii hutenganisha matokeo unayoweza kutetea na yale yaliyosoma namba za kesho kimyakimya. h5i-db ni hifadhidata ya time-series ya chanzo huria (open-source) iliyo changa, iliyoandikwa kwa lugha ya Rust ikiwa na Python API. Hifadhidata hii huhifadhi kila uandishi kama toleo lenye namba na humruhusu mtumiaji yeyote anayesoma data kubainisha (pin) toleo la awali. Chini ni uvujaji ambao uwezo wa kubainisha (pinning) huzuia, uliopimwa kwa kutumia data halisi ya filings, ikifuatiwa na hali unayoweza kuijaribu kwenye kompyuta yako ya mkononi.

Data ya point-in-time katika backtest ni nini?

Kila ukweli wa soko una mihuri miwili ya muda. Muda wa tukio (event time) ni wakati jambo lilipotokea. Muda wa kuwasili (arrival time) ni wakati ambapo jambo hilo liliwezekana kujulikana na mtu yeyote nje ya kampuni iliyoliripoti. Ripoti ya robo mwaka ya umiliki inaelezea nafasi zilizoshikiliwa katika siku ya mwisho ya robo mwaka na huwafikia umma wiki kadhaa baadaye, kwa hivyo mfano unaounganisha data kwa kutumia muda wa tukio pekee hupokea taarifa ambayo hakuna mtu aliyekuwa nayo wakati huo.

Umbali huu unaweza kupimwa. Wasimamizi wa taasisi huwasilisha Form 13F baada ya kufungwa kwa kila robo mwaka, na paneli iliyo hapa chini inapima siku zilizopita kati ya robo mwaka inayoelezewa na ripoti na siku iliyowasilishwa.

UlizaMuda wa uwasilishaji wa 13F kufikia umma
SQL halisi nyuma ya kila namba
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

Kwa robo mwaka iliyoishia 2026-06-30, mawasilisho ya 10688 yaliwasili wastani wa siku 34.1 baada ya kipindi yanachokihusu. Paneli hii inarudia kipimo hicho katika robo mwaka 16. Meneja anaweza pia kurekebisha ripoti muda mrefu baada ya kuiwasilisha, kwa hivyo rekodi inayoelezea tarehe ya nyuma huendelea kubadilika hata baada ya tarehe hiyo kupita.

Upendeleo wa kutazama mbele ni tatizo la uhifadhi

Mwongozo wetu kuhusu upendeleo wa kutazama mbele katika backtesting unachukulia uvujaji wa data kama suala la nidhamu: chelewesha kila kigezo na uzingatie tarehe za uchapishaji. Nidhamu hudumu hadi mtu asahau, na hitilafu hiyo hutokea kimya kimya. Backtest iliyovuja data hutoa Sharpe ratio bora zaidi bila kuonyesha hitilafu yoyote.

Uhifadhi wa point-in-time huhamisha dhamana hiyo kwenye ngazi ya chini. Wakati data inayopewa mkakati inatoka kwenye usomaji uliowekwa kwenye toleo maalum, safu iliyoandikwa baada ya toleo hilo haiwezi kuonekana ndani yake, hata mkakati ufanye nini. Ukaguzi huacha kuwa wa msimbo na kuwa sifa ya usomaji wenyewe.

Dividends huonyesha pengo la muda kwa upande mwingine. Dividend ya pesa taslimu hutangazwa kwanza na kwenda ex-dividend baadaye, na jedwali linalopakiwa leo hubeba tarehe zote mbili kwa kila malipo, ikiwemo yale ambayo hayakuwa yametangazwa katika tarehe inayofanyiwa simulation.

UlizaSiku kati ya tangazo la gawio na ex-date, kwa mwezi
SQL halisi nyuma ya kila namba
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

Katika mwezi unaoanza 2026-07-01, matangazo yaliwasili wastani wa siku 88.2 kabla ya ex-dividend date, na paneli inashughulikia miezi 24 ya kipimo hicho hicho. Soma jedwali la kisasa la dividend katika tarehe ya simulation iliyo ndani ya pengo hilo na malipo yatakuwa tayari yapo, wiki kadhaa kabla ya tangazo kuwepo.

Historia iliyohifadhiwa hurekebishwa

Kuchelewa kufika ni aina moja ya hitilafu. Kurekebishwa kwa data (restatement) ndiyo nyingine. Hatua za shirika (corporate actions) hubadilisha bei ambazo tayari zimechapishwa: baada ya split ya nne-kwa-moja, kila bei ya awali katika mfululizo uliorekebishwa hugawanywa kwa nne, na mfululizo unaopakuliwa leo haulingani tena na rekodi ya soko (tape) ambayo mfanyabiashara aliitazama. Ufafanuzi wetu kuhusu historia ya bei iliyorekebishwa kwa split unaelezea hesabu hiyo. Kinachozingatiwa hapa ni marudio ya matukio hayo.

UlizaGawio la hisa (splits) linalotekelezwa kila robo mwaka, forward na reverse
SQL halisi nyuma ya kila namba
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

Katika robo inayoanza 2026-04-01, 131 forward splits na 303 reverse splits zilianza kutumika. Kila moja hurekebisha historia ya bei ambayo mfumo wa utafiti unaweza kuwa uliihifadhi kwenye cache. Hifadhi ya matoleo (versioned storage) haizuii marekebisho hayo. Inarekodi hali mpya kama toleo jipya na kuweka toleo la zamani likiwa linaweza kusomeka, jambo ambalo hubadilisha matokeo yaliyopitwa na wakati kuwa matokeo yanayoweza kurudiwa.

Timestamp za habari hubeba mtego uleule kwa kiwango kidogo.

UlizaWakati vichwa vya habari vya soko vinapochapishwa, kwa saa za New York
SQL halisi nyuma ya kila namba
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

Vichwa vya habari huchapishwa saa ishirini na nne kwa siku, katika saa zote 24 za siku ya New York. Saa tisa (09:00) ilibeba makala 790 katika kipindi cha siku tisini zilizopita, na saa ishirini (20:00) ilibeba 404. Kuweka timestamp ya kichwa cha habari cha jioni kwenye bei ya kufunga ya saa kumi (4:00 p.m.) ya siku hiyo huipa mkakati unaofanya biashara wakati wa kufunga soko faida ya kuona yaliyopita ya saa kadhaa.

Tukio la point-in-time unaloweza kuliendesha

Kila kitu hapa kinabaki kwenye kifurushi cha Python. Mradi huu pia unasafirisha zana ya mstari wa amri ya Rust, usakinishaji tofauti ambao mwongozo huu hauhitaji. Data ya sampuli huzalishwa ndani ya mfumo, bila kupakua.

  1. Sakinisha toleo lililofungwa: pip install 'h5i-db==0.1.6', lililochapishwa tarehe 4 Agosti 2026. Linahitaji Python 3.9 au mpya zaidi na huleta pyarrow>=14. Wheels zilizojengwa awali zinashughulikia Linux kwenye x86-64 na arm64, pamoja na Apple silicon macOS na Windows kwenye x86-64.
  2. Eleza data mara moja, baada ya import pyarrow as pa na import pyarrow.parquet as pq: schema = pa.schema([('ts', pa.timestamp('us', tz='UTC')), ('symbol', pa.string()), ('price', pa.float64())]).
  3. Andika safu mbili zilizobuniwa kwenye faili ya ndani kwa kutumia pq.write_table(pa.table({'ts': [d1, d2], 'symbol': ['ACME', 'ACME'], 'price': [10.0, 10.5]}, schema=schema), 'day1.parquet'), ambapo d1 na d2 ni tarehe na saa zinazotambua ukanda wa saa.
  4. Unda hifadhidata na jedwali, ukipa jina safu ya muda: db = h5i_db.Database('pit.db', create=True) kisha db.create_table('prices', schema, time_column='ts').
  5. Ingiza faili chini ya ufunguo: db.append('prices', pq.read_table('day1.parquet'), idempotency_key='load-day1'). Wito huo unarudisha commit iliyofanya.
  6. Endesha mstari huo hasa tena. Mradi unaandika kuwa marudio yanayobeba ufunguo uleule yanapata commit iliyokwisha zalishwa na kuirudisha ikiwa na "segments_added": 0 badala ya kuandika safu hizo kwa mara ya pili. Chapisha db.versions('prices') kila upande wa jaribio hilo na uone orodha ya toleo ikibaki vilevile.
  7. Ingiza siku ya pili chini ya idempotency_key='load-day2', kisha fanya query katika zote mbili: db.sql('SELECT symbol, count(*) AS n, avg(price) AS px FROM prices GROUP BY symbol').to_pandas().
  8. Soma jedwali kama lilivyokuwa kabla ya siku ya pili kuingia: db.read('prices', version=1). Njia hiyo hiyo inachukua hoja za as_of= na snapshot= kwa kazi hiyo hiyo.

Hatua ya sita ndiyo ya kuzingatia. Kuongeza data iliyorudiwa hakusababishi kosa. Kunaacha jedwali likiwa si sahihi kuanzia wakati huo, na kila uendeshaji baada ya hapo unarithi uharibifu huo kimya kimya. Hatua ya nane ndiyo faida: namba iliyohesabiwa mwezi Machi inaweza kuhesabiwa upya mwezi Agosti kutoka toleo lilelile lililofungwa, sifa ambayo maandishi yetu ya reproducible backtest yanatetea katika ngazi ya mfumo.

Madai ya mradi, na yale tuliyoyahakiki

README inaongoza kwa kigezo cha utendaji:

zaidi ya mara nne na nusu (4.5×) kwa kasi zaidi kuliko DuckDB na Polars katika OHLCV+VWAP rollups kwa safu milioni ishirini

Takwimu hiyo ni kipimo cha mradi wenyewe, kilichonukuliwa kutoka h5i-db README kama kilivyopatikana mnamo Agosti 2026. Hatukufanya majaribio hayo, na hakuna chochote hapo juu kinachotegemea takwimu hiyo.

Ukomavu ni muhimu zaidi kuliko kasi katika muktadha huu. Hazina hiyo ilikuwa na nyota ishirini na tisa wakati wa kuandika makala haya na iko katika toleo la 0.1.6 chini ya leseni ya Apache-2.0. Mchanganyiko huo unamaanisha kundi dogo la watunzaji na API ambayo bado inaweza kubadilika kati ya matoleo madogo. Hakuna rekodi ndefu ya hadhara ya injini hiyo ikifanya kazi chini ya mzigo mkubwa. Kuweka toleo kamili na kuhifadhi faili za parquet zilizolisha hifadhidata hiyo kunatoa njia ya kurudi nyuma ikiwa toleo jipya litabadilisha utendaji. Kuhifadhi nakala yako mbichi ni tabia ileile inayokulinda dhidi ya mtoa huduma kubadilisha historia chini yako, mada inayoendelea kupitia upendeleo wa manusura katika data ya hisa.

Maswali Yanayoulizwa Mara kwa Mara

Data ya point-in-time ni nini?

Data ya point-in-time ni mkusanyiko wa data uliotunzwa pamoja na mihuri ya muda (timestamps) ambayo kila ukweli uliweza kujulikana, ili hoja (query) iweze kujenga upya kile kilichokuwa kinaonekana katika tarehe yoyote ya nyuma. Jedwali la kawaida la "thamani ya hivi karibuni" haliwezi kufanya hivyo, kwa sababu hufuta yaliyopita na kuweka namba zilizosahihishwa za leo.

Je, hifadhi ya matoleo (versioned storage) huondoa upendeleo wa kuangalia mbele (look-ahead bias)?

Hapana. Uwekaji matoleo husuluhisha aina moja ya uvujaji, ile ambapo mfumo husoma thamani zilizoandikwa baada ya muda wa uamuzi uliolengwa. Uundaji wa vipengele (feature construction) bado unaweza kuvuja kwa njia nyingine, kama vile kupima sampuli kwa takwimu zilizokokotolewa katika historia yake yote.

Ufunguo wa idempotency hufanya nini wakati wa kuingiza data (ingest)?

Huweka lebo kwenye uandishi ili jaribio la kurudia litambulike kama uandishi uleule. Kipakiaji (loader) kinaweza kuanza upya baada ya hitilafu bila kuongeza safu zilezile mara mbili, ambayo ni hitilafu inayofanya jedwali kuwa na makosa bila mtumiaji kujua.

Je, h5i-db iko tayari kwa matumizi ya uzalishaji (production)?

Ni toleo la 0.1.6 lenye nyota ishirini na tisa kwenye GitHub wakati wa kuandika makala haya, chini ya leseni ya Apache-2.0. Programu changa ya ukubwa huo hubeba mabadiliko ya mara kwa mara ya API na rekodi ndogo ya hadhara, na uwekaji wa toleo maalum (version pin) pamoja na nakala yako ya faili za chanzo ndivyo vinavyofanya tathmini iweze kutenduliwa.


Kila paneli hapo juu inakuja na SQL iliyoizalisha, kwa hivyo fungua moja na usome jinsi namba ilivyokokotolewa. Maswali hayohayo yanaweza kuulizwa kwa Kiingereza rahisi kwenye terminal ya Strasmore.