Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Jinsi ya kujenga confidence bands za backtest

Mkondo mmoja wa equity ni sampuli moja tu. Confidence interval ya Sharpe ratio mara nyingi hujumuisha sifuri. Jifunze mbinu ya bootstrapping na namna ya kuisoma.

Kipindi cha imani cha backtest

Kipindi cha imani (confidence interval) cha backtest hujibu swali moja: ni kiasi gani cha mkondo wa usawa (equity curve) kinatokana na mkakati, na ni kiasi gani kinatokana na kipindi mahususi cha historia kilichotumika. Mbinu ya bootstrapping katika backtest hujenga kipindi hicho kwa kuchagua sampuli upya kutoka kwenye mfululizo wa mapato mara nyingi, kuhesabu upya takwimu katika kila sampuli, na kusoma asilimia (percentiles) ya matokeo yanayopatikana. Sharpe ratio ya 1.4 iliyopimwa kwa mwaka mmoja wa uchunguzi wa kila siku hubeba kipindi cha asilimia tisini na tano (95 percent) ambacho ni kipana kiasi cha kujumuisha sifuri, na paneli zilizo hapa chini hupima sababu za hali hiyo.

Kwa nini mkondo mmoja wa hisa ni sampuli moja

Backtest hukupa namba moja kwa kila takwimu: Sharpe ratio moja, faida ya mwaka moja, drawdown mbaya zaidi moja, na hit rate moja. Hakuna hata moja kati ya hizo inayowakilisha thamani halisi ya mkakati. Kila moja ni makadirio yaliyojengwa kutokana na idadi ndogo ya siku za biashara, na kipindi kingine cha urefu uleule kingetoa matokeo tofauti.

Jopo hapa chini linaondoa mkakati kwenye swali hili kabisa. Linapima nafasi rahisi zaidi iwezekanavyo, yaani kushikilia SPY, kwa mwaka mmoja wa kalenda kwa wakati mmoja, kulingana na mapato ya bei ya close-to-close.

UlizaNafasi moja, mwaka mmoja: Uwiano wa Sharpe wa SPY kwa mwaka wa kalenda
SQL halisi nyuma ya kila namba
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2011-12-01'
      AND date <  '2026-01-01'
)
SELECT
    toString(toYear(date))                            AS year,
    count()                                           AS obs_count,
    round(avg(ret) * 252 * 100, 2)                    AS ann_return_pct,
    round(stddevSamp(ret) * sqrt(252) * 100, 2)       AS ann_vol_pct,
    round(avg(ret) / stddevSamp(ret) * sqrt(252), 2)  AS sharpe_ratio
FROM daily
WHERE date >= '2012-01-01'
  AND ret IS NOT NULL
GROUP BY year
ORDER BY year
Run this yourself

Kila mstari unashughulikia takriban 250 vipindi, karibu na mwaka wa kawaida wa biashara. Hakuna kilichobadilika kuhusu nafasi hiyo katika vipindi vyote. Katika 2012, Sharpe ratio ya mwaka ilipimwa kuwa 1.06; katika 2025 ilipimwa kuwa 0.88, huku 14 ya miaka ikiwa imechorwa kwenye chati. Mwaka mmoja wa kushikilia fahirisi pana hubadilisha namba kuu kwa kiasi kikubwa kuliko ambacho wasomaji wengi wangekiweka kwenye kundi la kelele (noise), na backtest ya mkakati wa urefu uleule hurithi angalau kiasi hicho cha tofauti. Mkataba wa kuhesabu mapato ya mwaka (annualization) nyuma ya safu hii unapatikana katika mwongozo wa Sharpe ratio, na mbinu za mapato ya kipindi katika jinsi mapato ya kila mwezi yanavyopimwa.

Upeo wa bendi kuzunguka Sharpe ya backtest ni upana kiasi gani?

Hitilafu ya kawaida (standard error) ya makadirio ya Sharpe hupungua takribani kwa kufuata mzizi wa mraba wa idadi ya vipimo. Badala ya kutegemea fomula hiyo, pima upeo huo moja kwa moja. Gawanya miaka ishirini ya vipindi vya biashara katika madirisha yasiyoingiliana yenye urefu maalum, hesabu Sharpe ya kila mwaka ndani ya kila dirisha, na uangalie umbali uliopo kati ya namba hizo.

UlizaMtawanyiko wa Sharpe uliopimwa katika vipindi visivyopishana vya SPY, 2006 hadi 2025
SQL halisi nyuma ya kila namba
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2005-12-01'
      AND date <  '2026-01-01'
),
indexed AS
(
    SELECT
        ret,
        row_number() OVER (ORDER BY date) AS i
    FROM daily
    WHERE date >= '2006-01-01'
      AND ret IS NOT NULL
),
blocks AS
(
    SELECT
        w,
        intDiv(i, w)                                            AS blk,
        count()                                                 AS n,
        avg(ret) / nullIf(stddevSamp(ret), 0) * sqrt(252)        AS sharpe
    FROM indexed
    CROSS JOIN (SELECT arrayJoin([21, 63, 126, 252, 504]) AS w) AS ws
    GROUP BY w, blk
    HAVING n = w
)
SELECT
    concat(toString(w), ' sessions')                          AS horizon,
    count()                                                   AS block_count,
    round(quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_p05,
    round(quantileDeterministic(0.50)(sharpe, blk), 2)        AS sharpe_p50,
    round(quantileDeterministic(0.95)(sharpe, blk), 2)        AS sharpe_p95,
    round(quantileDeterministic(0.95)(sharpe, blk)
        - quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_band_width
FROM blocks
WHERE sharpe IS NOT NULL
GROUP BY w
ORDER BY w
Run this yourself

Kwa 21 sessions kwa kila dirisha, asilimia ya tano hadi ya tisini na tano ya Sharpe iliyopimwa huanzia -3.37 hadi 6.97, upeo wa 10.34 Sharpe points katika madirisha 238. Panua kila dirisha hadi 504 sessions na upeo huo hupungua hadi 1.67 points, sasa ikipimwa katika madirisha 8 pekee. Mambo mawili hutokea kwa wakati mmoja. Makadirio huwa sahihi zaidi kadiri ukubwa wa sampuli unavyoongezeka, na idadi ya sampuli huru ambazo historia inaweza kutoa hupungua. Mvutano huo ndio mada nzima.

Jinsi ya kuunda kipindi cha imani (confidence interval) cha backtest kupitia bootstrap

Utaratibu huu ni mfupi kiasi cha kuweza kuandikwa kikamilifu.

  1. Anza na mfululizo wa mapato halisi ya mkakati, takwimu moja kwa kila kipindi, jumla ya N.
  2. Chagua mapato N kutoka kwenye orodha hiyo kwa kubahatisha, huku ukirudisha kila chaguo kwenye orodha (with replacement). Baadhi ya mapato yatajitokeza mara mbili, mengine hayatatokea kabisa.
  3. Kokotoa upya takwimu husika kwenye sampuli hiyo mpya.
  4. Rudia mchakato huu mara elfu kadhaa, huku ukihifadhi kila thamani inayopatikana.
  5. Panga thamani zilizohifadhiwa na usome asilimia mbili nukta tano (2.5th) na tisini na saba nukta tano (97.5th) ili kupata kipindi cha asilimia tisini na tano (95 percent interval).

Weka mbegu (seed) ya jenereta ya namba za kubahatisha kabla ya hatua ya pili, na uandike mbegu hiyo kando ya kipindi ulichochapisha. Bootstrap ni makadirio ya Monte Carlo: majaribio mawili yasiyo na mbegu sawa yatatofautiana kwenye tarakimu za mwisho, na mkaguzi asiyeweza kurudia kipindi chako hatakuwa na njia ya kukihakiki. Hiyo ni nidhamu ileile iliyoelezwa katika usanidi wa backtest unaoweza kurudiwa.

Mapato ya wastani (mean return) na Sharpe hupita hatua ya pili bila matatizo, kwa sababu zote mbili husoma orodha ya mapato kama seti. Maximum drawdown haifanyi hivyo. Drawdown husoma mfululizo wa matukio kwa mpangilio wake. Sampuli inayopanga upya mapato hutoa drawdown mbaya zaidi ambayo haikutokana na mpangilio wowote wa mfululizo halisi wa biashara. Bado inafaa kufanya bootstrapping, mradi matokeo yaitwe kwa jina lake sahihi: usambazaji wa drawdown katika historia zilizopangwa upya, si utabiri wa mfululizo ujao. Ufafanuzi wake uko katika maximum drawdown.

Kwa nini mbinu ya IID bootstrap si sahihi kwa mapato ya soko

Hatua ya pili inadhani kuwa kila mapato ni huru na yamesambazwa kwa njia sawa, yaani dhana ya IID. Mapato ya kila siku huvunja dhana hii kwa njia inayobadilisha upana wa muda. Mapato yenye alama hubeba kumbukumbu hafifu ya siku moja pekee. Hata hivyo, ukubwa wake hujikusanya: mabadiliko makubwa hufuatana na mabadiliko makubwa, na siku tulivu huja kwa mfululizo.

UlizaAutocorrelation ya lag-one: mapato yenye alama dhidi ya mapato kamili, 2016 hadi 2025
SQL halisi nyuma ya kila namba
WITH daily AS
(
    SELECT
        ticker,
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('SPY', 'MSFT', 'KO', 'XOM', 'JNJ', 'PG')
      AND date >= '2015-11-01'
      AND date <  '2026-01-01'
),
lagged AS
(
    SELECT
        ticker,
        date,
        ret,
        lagInFrame(ret, 1) OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS ret_prev
    FROM daily
    WHERE ret IS NOT NULL
      AND abs(ret) < 0.35
)
SELECT
    ticker,
    count()                                AS obs_count,
    round(corr(ret, ret_prev), 3)          AS return_autocorr,
    round(corr(abs(ret), abs(ret_prev)), 3) AS abs_return_autocorr
FROM lagged
WHERE date >= '2016-01-01'
  AND ret_prev IS NOT NULL
GROUP BY ticker
ORDER BY abs_return_autocorr DESC
Run this yourself

Kwa SPY, autocorrelation ya lag-one ya mapato kamili ya kila siku ilipimwa 0.366 katika vipindi vya 2514, dhidi ya -0.133 kwa mapato yenye alama katika siku hizo hizo. Linganisha pau mbili kwenye jina lolote katika chati. Kuchanganya mfululizo wa mapato huharibu mkusanyiko huo, na mbinu ya IID bootstrap inayotumika kwenye data hii hutoa muda mwembamba zaidi kuliko ule unaoungwa mkono na sampuli. Hitilafu hii huelekea upande usiofaa zaidi: huipendelea mkakati husika kupita kiasi.

Bootstrap ya block inayotembea, na kuchagua urefu wa block

Suluhisho ni kufanya resampling ya block zinazofuatana badala ya returns za mtu mmoja mmoja. Chagua urefu wa block L, chota block za L returns zinazofuatana kwa kubahatisha na kwa kurudishia, kisha ziunganishe mwisho hadi mwisho hadi mfululizo wa sintetiki uwe na urefu wa N. Utegemezi ndani ya block hubaki vilevile: returns hizo hubaki katika mpangilio wake wa asili. Viungo kati ya block ndivyo pekee vilivyo vya kutengenezwa.

Urefu wa block hupambanisha makosa mawili, na hakuna mpangilio unaoweza kuepuka yote mawili. Block fupi hufanya kazi kama IID bootstrap na kupunguza ukubwa wa interval, jambo ambalo ni bias. Block ndefu huhifadhi utegemezi zaidi lakini huacha block chache tofauti za kuchota, hivyo kila resample hurudia sehemu kubwa za historia ileile na interval yenyewe huwa na kelele, jambo ambalo ni variance. Kanuni za jumla zilizochapishwa hupima urefu kwa kutumia N kwa nguvu ya moja ya tatu. Zichukulie kama sehemu za kuanzia.

Uchunguzi wa bei nafuu zaidi ni kuangalia jinsi variance inavyopimika kulingana na muda. Chini ya hali ya kutokuwa na utegemezi, variance ya jumla ya return ya siku k ni sawa na k mara variance ya siku moja, na uwiano wa hizo mbili husimama kwenye 1.

UlizaUwiano wa variance kulingana na urefu wa block: je, variance ya SPY hufuata mkondo wa michoro huru?
SQL halisi nyuma ya kila namba
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2005-12-01'
      AND date <  '2026-01-01'
),
indexed AS
(
    SELECT
        ret,
        row_number() OVER (ORDER BY date) AS i
    FROM daily
    WHERE date >= '2006-01-01'
      AND ret IS NOT NULL
),
base AS
(
    SELECT varSamp(ret) AS var_1d FROM indexed
),
blocks AS
(
    SELECT
        k,
        intDiv(i, k)  AS blk,
        count()       AS n,
        sum(ret)      AS block_ret
    FROM indexed
    CROSS JOIN (SELECT arrayJoin([2, 3, 5, 10, 21, 42, 63]) AS k) AS ks
    GROUP BY k, blk
    HAVING n = k
)
SELECT
    concat(toString(k), ' sessions')                          AS block_length,
    count()                                                   AS block_count,
    round(varSamp(block_ret) / (k * any(var_1d)), 3)          AS variance_ratio
FROM blocks
CROSS JOIN base
GROUP BY k
ORDER BY k
Run this yourself

Katika 2 sessions uwiano ulipimwa kuwa 0.844; katika 63 sessions ulipimwa kuwa 0.584, uliokokotolewa juu ya 78 block zisizopishana. Thamani zilizo karibu na 1 humaanisha kuwa jumla hupimika kwa njia ileile ambayo chomo za kubahatisha (independent draws) zingefanya katika muda huo. Thamani zilizo mbali na 1 huashiria muda ambapo utegemezi bado unafanya kazi, na hiyo ndiyo safu ambayo urefu wa block lazima uifunike. Chagua block fupi zaidi inayovuka safu hiyo, kisha angalia idadi ya block imeshuka kufikia kiasi gani.

Uchambuzi wa sampuli kwa vikundi (bucketed resampling) na idadi ya sampuli kando ya kila muda

Kufanya resampling ndani ya vikundi, kulingana na hali ya volatility au mwezi wa kalenda, hudumisha masharti yaliyofanya swali hilo kuwa la kuvutia. Ikiwa madai ni kwamba mkakati fulani hupata Sharpe ratio yake katika vipindi vya volatility ya juu, basi muda ulioundwa kwa kuchagua siku za volatility ya juu pekee ndio unaopaswa kutumika kupima madai hayo. Gharama yake ni ya kihesabu. Kugawanya observation mia mbili hamsini katika vikundi vinne huacha takribani sitini kwa kila kikundi, na bootstrap ya observation sitini hutoa muda (interval) ambao ni mpana mara mbili ya ule wa sampuli nzima.

Kwa hiyo, ripoti idadi ya sampuli ya kila kikundi kando ya kila muda, kila wakati. Safu ya block_count katika paneli zilizo hapo juu ni zoea hilo likionekana wazi: asilimia tano (5th percentile) inayotokana na madirisha tisa ni kitu tofauti na ile inayotokana na madirisha mia mbili, hata pale ambapo yote mawili yanapoonyeshwa kwa desimali mbili.

Mambo ambayo bootstrap haiwezi kurekebisha

Bootstrap hupima jambo moja tu, yaani kelele za sampuli (sampling noise) katika takwimu iliyohesabiwa kutoka kwa data uliyonayo. Haisemi lolote kuhusu kama data hiyo ilikuwa inawezekana kupatikana sokoni.

Backtest iliyochafuliwa na look-ahead bias huzalisha mfululizo wa mapato ambao haukuwahi kufanyiwa biashara, na bootstrap yake hutoa safu finyu na ya kujiamini inayozunguka hadithi ya kubuni. Ulimwengu wa hisa uliokusanywa kutoka kwa wanachama wa fahirisi wa leo hubeba survivorship bias, na kila sampuli mpya ya ulimwengu huo hurithi upendeleo huo. Pengo la tatu ni athari ya uteuzi: endesha lahaja mia mbili, chagua iliyo bora zaidi, na muda wa bootstrap yake utaelezea kelele za sampuli za lahaja hiyo moja huku ukipuuza zile mia moja tisini na tisa zilizotumika kuichagua. Safu za uaminifu ni muhimu. Haziwezi kuchukua nafasi ya data ya nje ya sampuli (out-of-sample data).

Vidokezo vya data na mbinu
  • Mapato ni mapato ya bei ya close-to-close kutoka kwa baa za kila siku. Dividends hazijajumuishwa, jambo ambalo hupunguza kiwango cha kila mapato na takwimu ya Sharpe kwa takribani kiasi cha dividend yield. Mtawanyiko (dispersion) ambao chapisho hili unahusu haujaathirika kwa kiasi kikubwa.
  • Madirisha na vizuizi havipishani, kwa hivyo kila takwimu iliyopimwa hutumia kipande cha historia kisichohusiana na kingine. Madirisha yanayopishana yangeongeza idadi ya sampuli inayoonekana.
  • Quantiles hutumia kikadirio cha kitaratibu (deterministic estimator), kwa hivyo kuendesha paneli upya hurejesha asilimia ileile badala ya makadirio mapya.
  • Paneli ya autocorrelation hutumia majina sita makubwa ambayo hayana mgawanyo wa hisa (share split) ndani ya dirisha hilo, na huondoa siku yoyote yenye mabadiliko ya zaidi ya asilimia thelathini na tano, jambo ambalo huondoa mabaki ya marekebisho ya bei (price-adjustment artifacts) kwenye uwiano.

Maswali Yanayoulizwa Mara Kwa Mara

Bootstrap confidence interval inakuambia nini kuhusu backtest?

Inatoa masafa ya thamani ambazo takwimu inaweza kuchukua kwa njia inayokubalika ikiwa mchakato uleule utachunguzwa tena kwa idadi ileile ya vipindi. Masafa ya asilimia tisini na tano yanayojumuisha sifuri yanamaanisha kuwa sampuli ni fupi mno kutofautisha mkakati huo na kutokuwepo kwa faida yoyote ya kibiashara.

Ni resamples ngapi za bootstrap zinatosha?

Kwa masafa ya asilimia tisini na tano, resamples elfu chache kwa kawaida huwa thabiti, na elfu kumi ni chaguo-msingi la kawaida ambalo haligharimu sana. Quantiles za mkia wa kina zinahitaji zaidi: asilimia ya kwanza ya michoro elfu moja inasomwa kutoka kwa thamani kumi hivi.

Ni urefu gani wa block ambao moving block bootstrap inapaswa kutumia?

Hakuna urefu sahihi kwa wote. Kanuni za jumla huupima kwa ukubwa wa sampuli kwa nguvu ya theluthi moja, na kipimo cha kivitendo ni upeo ambapo variance huacha kupanda kwa mstari, jambo ambalo jopo la variance ratio hapo juu hupima. Chapisha urefu uliotumia kando ya masafa hayo.

Je, unaweza kufanya bootstrap ya maximum drawdown?

Ndiyo, kwa tahadhari ya mpangilio. Drawdown inategemea mfuatano wa mapato, kwa hivyo resample iliyojengwa kwa kuchanganya inaripoti drawdown ya historia iliyopangwa upya. Block bootstrap huweka mfuatano mfupi ukiwa mzima na ndiyo zana bora zaidi kwa takwimu za njia.

Je, bootstrap husahihisha overfitting?

Hapana. Inapima kelele za sampuli ndani ya mfululizo mmoja wa mapato. Upendeleo wa kutazama mbele (look-ahead bias) na athari ya uteuzi kutokana na kupima lahaja nyingi zote ziko nje ya kile inachoweza kuona.


Kila jopo hapa lina SQL iliyolizalisha. Badilisha ticker au urefu wa dirisha na uendeshe mwenyewe kwenye terminal ya Strasmore.

#backtesting#bootstrap#statistics#confidence intervals#sharpe ratio