Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Bootstrap betrouwbaarheidsintervallen voor backtests

Eén equity curve is slechts één steekproef. Een bootstrap betrouwbaarheidsinterval op een Sharpe ratio is vaak breed genoeg om nul te bevatten. Leer hoe u dit bouwt en leest.

Betrouwbaarheidsintervallen bij backtesting

Een betrouwbaarheidsinterval bij een backtest beantwoordt één vraag: in hoeverre is de equity curve het resultaat van de strategie, en in hoeverre van de specifieke historische periode waarop deze is getest. Bij bootstrapping van een backtest wordt dit interval geconstrueerd door de reeks rendementen vele malen opnieuw te samplen, de statistiek voor elke nieuwe sample opnieuw te berekenen en vervolgens de percentielen van de uitkomsten af te lezen. Een Sharpe ratio van één komma vier, gemeten over één jaar aan dagelijkse observaties, kent een vijfennegentig procent betrouwbaarheidsinterval dat breed genoeg is om nul te bevatten. De onderstaande panelen laten zien waarom dit zo is.

Waarom één equity curve slechts één steekproef is

Een backtest levert u per statistiek één getal op: één Sharpe ratio, één geannualiseerd rendement, één maximale drawdown, één hit rate. Geen van deze getallen vertegenwoordigt de werkelijke waarde van de strategie. Elk getal is een schatting op basis van een eindige reeks handelsdagen, en een andere periode van dezelfde lengte zou tot een ander resultaat hebben geleid.

Onderstaand overzicht laat de strategie volledig buiten beschouwing. Het meet de meest eenvoudige positie: het aanhouden van SPY, per kalenderjaar, op basis van close-to-close koersrendementen.

QueryEén positie, per jaar: geannualiseerde Sharpe van SPY per kalenderjaar
De exacte SQL achter elk getal
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

Elke rij beslaat ongeveer 250 sessies, wat dicht bij het standaard handelsjaar ligt. Er is in geen van de gevallen iets veranderd aan de positie. In 2012 bedroeg de geannualiseerde Sharpe 1.06; in 2025 was dit 0.88, waarbij 14 jaar in de grafiek is opgenomen. Eén jaar aanhouden van een brede index zorgt voor een grotere beweging in het hoofdcijfer dan de meeste lezers als ruis zouden bestempelen, en een backtest van een strategie met dezelfde lengte erft ten minste diezelfde mate van onzekerheid. De annualiseringsconventie achter de kolom vindt u in onze gids over de Sharpe ratio, en de mechanica van perioderendementen in hoe maandrendementen worden gemeten.

Hoe breed is de bandbreedte rond een Sharpe-ratio uit een backtest?

De standaardfout van een Sharpe-schatting daalt ruwweg met de vierkantswortel van het aantal observaties. In plaats van op die formule te vertrouwen, kunt u de spread direct meten. Verdeel twintig jaar aan handelssessies in niet-overlappende vensters van een vaste lengte, bereken de geannualiseerde Sharpe binnen elk venster en kijk hoe ver die getallen uiteenlopen.

QueryGemeten Sharpe-dispersie over niet-overlappende SPY-vensters, 2006 tot 2025
De exacte SQL achter elk getal
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

Bij 21 sessions per venster loopt het vijfde tot vijfennegentigste percentiel van de gemeten Sharpe van -3.37 tot 6.97, een spread van 10.34 Sharpe-punten over 238 vensters. Verleng elk venster tot 504 sessions en de spread daalt tot 1.67 punten, nu gemeten over slechts 8 vensters. Twee zaken veranderen tegelijkertijd. De schatting wordt nauwkeuriger naarmate de steekproefomvang toeneemt, en het aantal onafhankelijke samples dat de historie kan leveren, neemt af. Die spanning is het volledige onderwerp.

Hoe u een betrouwbaarheidsinterval voor een backtest bootstrapt

De procedure is kort genoeg om volledig uit te schrijven.

  1. Begin met de gerealiseerde rendementsreeks van de strategie, één cijfer per periode, in totaal N stuks.
  2. Trek willekeurig N rendementen uit die lijst, met teruglegging. Sommige verschijnen twee keer, andere helemaal niet.
  3. Herbereken de statistiek op de nieuwe steekproef.
  4. Herhaal dit enkele duizenden keren en bewaar elke waarde.
  5. Sorteer de opgeslagen waarden en lees het 2,5e en 97,5e percentiel af voor een vijfennegentig procent interval.

Initialiseer de generator voor toevalsgetallen vóór stap twee en noteer de seed naast het gepubliceerde interval. Een bootstrap is een Monte Carlo-schatting: twee runs zonder seed verschillen in de laatste cijfers, en een reviewer die uw interval niet kan herberekenen, heeft geen manier om dit te controleren. Dat is dezelfde discipline als beschreven in een reproduceerbare backtest-opzet.

Het gemiddeld rendement en de Sharpe-ratio doorstaan stap twee probleemloos, aangezien beide de rendementslijst als een verzameling lezen. De maximum drawdown doet dat niet. Drawdown leest het pad in de juiste volgorde. Een nieuwe steekproef die rendementen herschikt, levert een worst drawdown op die geen enkele volgorde van de werkelijke handelsreeks heeft geproduceerd. Het bootstrappen hiervan is nog steeds zinvol, mits de output wordt gelabeld voor wat het is: de verdeling van drawdown over herschikte historie, niet een prognose van de volgende. De definitie staat in maximum drawdown.

Waarom de IID-bootstrap onjuist is voor marktrendementen

Stap twee gaat uit van de aanname dat elk rendement onafhankelijk en identiek verdeeld is, de IID-aanname. Dagrendementen schenden deze aanname op een wijze die de intervalbreedte beïnvloedt. Rendementen met een teken vertonen slechts een zwak geheugen van één dag. De omvang ervan vertoont echter clustering: grote bewegingen volgen op grote bewegingen en rustige dagen komen in reeksen voor.

QueryLag-one autocorrelatie: gesigneerde returns versus absolute returns, 2016 tot 2025
De exacte SQL achter elk getal
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

Voor SPY bedraagt de lag-one autocorrelatie van absolute dagrendementen 0.366 over 2514 sessies, tegenover -0.133 voor de rendementen met teken op dezelfde dagen. Vergelijk de twee staven voor elk willekeurig instrument in de grafiek. Het husselen van een reeks rendementen vernietigt deze clustering, en een IID-bootstrap die op deze data wordt uitgevoerd, rapporteert een nauwer interval dan de steekproef rechtvaardigt. De fout werkt in de minst gunstige richting: zij flatteert de strategie.

De moving block bootstrap en het kiezen van een bloklengte

De oplossing is om aaneengesloten blokken te resamplen in plaats van individuele rendementen. Kies een bloklengte L, trek willekeurig blokken van L opeenvolgende rendementen met teruglegging en plak deze achter elkaar totdat de synthetische reeks een lengte van N heeft. De afhankelijkheid binnen een blok blijft intact: deze rendementen behouden hun oorspronkelijke volgorde. Alleen de overgangen tussen de blokken zijn kunstmatig.

De bloklengte vormt een afweging tussen twee fouten, en geen enkele instelling ontkomt aan beide. Korte blokken gedragen zich als de IID bootstrap en onderschatten het interval, wat leidt tot bias. Lange blokken behouden meer afhankelijkheid, maar laten minder afzonderlijke blokken over om uit te putten, waardoor elke resample grote delen van dezelfde historie herhaalt en het interval zelf onrustig wordt, wat leidt tot variantie. Gepubliceerde vuistregels schalen de lengte met N tot de macht één derde. Beschouw deze als uitgangspunten.

Een goedkopere diagnostische methode is om te controleren hoe de variantie schaalt met de horizon. Bij onafhankelijkheid is de variantie van een som van rendementen over k dagen gelijk aan k maal de variantie van één dag, en de verhouding tussen beide is 1.

QueryVariance ratio per bloklengte: schaalt de SPY-variantie als onafhankelijke observaties?
De exacte SQL achter elk getal
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

Op 2 sessions bedroeg de ratio 0.844; op 63 sessions bedroeg deze 0.584, berekend over 78 niet-overlappende blokken. Waarden nabij 1 betekenen dat de som schaalt zoals onafhankelijke trekkingen dat op die horizon zouden doen. Waarden die afwijken van 1 markeren de horizons waar afhankelijkheid nog steeds een rol speelt, en dat is het bereik dat een bloklengte moet dekken. Kies het kortste blok dat dit bereik dekt en kijk vervolgens tot welk niveau het aantal blokken is gedaald.

Gegroepeerde resampling en het aantal waarnemingen naast elk interval

Resampling binnen groepen, op basis van volatiliteitsregimes of kalendermaanden, behoudt de conditionering die de vraagstelling relevant maakt. Als de stelling luidt dat een strategie haar Sharpe-ratio behaalt in regimes met een hoge volatiliteit, dan is een interval dat uitsluitend is opgebouwd uit dagen met een hoge volatiliteit de juiste toetsing voor die claim. De prijs hiervan is rekenkundig. Het verdelen van tweehonderdvijftig waarnemingen over vier groepen resulteert in ongeveer zestig waarnemingen per groep, en een bootstrap van zestig waarnemingen levert een interval op dat ongeveer twee keer zo breed is als de versie op basis van de volledige steekproef.

Rapporteer daarom bij elk interval, elke keer opnieuw, het aantal waarnemingen in de groep. De block_count-kolom in de bovenstaande panelen is de zichtbare uitkomst van die gewoonte: een vijfde percentiel afgeleid van negen vensters is een ander object dan een percentiel afgeleid van tweehonderd, zelfs wanneer beide op twee decimalen worden afgerond.

Wat een bootstrap niet kan herstellen

Een bootstrap kwantificeert één aspect: de steekproefruis in een statistiek die is berekend op basis van de beschikbare data. De methode zegt niets over de vraag of die data ooit verhandelbaar waren.

Een backtest die is besmet met look-ahead bias levert een rendementsreeks op die nooit verhandelbaar was; een bootstrap daarvan geeft slechts een nauwe, zelfverzekerde bandbreedte rondom een fictie. Een universum dat is samengesteld uit de huidige indexleden bevat survivorship bias, en elke hertrekking van dat universum neemt deze vertekening over. Een derde tekortkoming is het selectie-effect: wie tweehonderd varianten draait en de beste behoudt, krijgt een bootstrap-interval dat de steekproefruis van die ene variant beschrijft, terwijl de honderdnegenennegentig andere trekkingen die tot die keuze leidden, worden genegeerd. Eerlijke bandbreedtes zijn nuttig. Ze zijn echter geen vervanging voor out-of-sample data.

Datatoelichting en methode
  • Rendementen zijn close-to-close koersrendementen op basis van dagkoersen. Dividenden zijn uitgesloten, wat het niveau van elk rendement en elk Sharpe-cijfer met ongeveer de dividend yield onderschat. De spreiding waar dit artikel over gaat, blijft hierdoor vrijwel onaangetast.
  • Vensters en blokken overlappen niet, waardoor elke gemeten statistiek gebruikmaakt van een disjunct deel van de historie. Overlappende vensters zouden het schijnbare aantal steekproeven kunstmatig verhogen.
  • Kwantielen maken gebruik van een deterministische schatter, waardoor het opnieuw draaien van een panel hetzelfde percentiel oplevert in plaats van een nieuwe benadering.
  • Het autocorrelatie-panel gebruikt zes grote namen zonder aandelensplitsing binnen het venster, en sluit elke dag met een koersbeweging van meer dan vijfendertig procent uit om prijsaanpassingsartefacten buiten de correlatie te houden.

Veelgestelde vragen

Wat vertelt een bootstrap-betrouwbaarheidsinterval u over een backtest?

Het geeft het bereik aan waarden weer dat de statistiek aannemelijk zou aannemen als hetzelfde proces opnieuw zou worden bemonsterd over hetzelfde aantal perioden. Een interval van vijfennegentig procent dat nul bevat, betekent dat de steekproef te kort is om de strategie te onderscheiden van het ontbreken van enig voordeel.

Hoeveel bootstrap-resamples zijn voldoende?

Voor een interval van vijfennegentig procent is een paar duizend resamples meestal stabiel, en tienduizend is een gebruikelijke standaard die weinig extra rekenkracht kost. Voor extremere kwantielen in de staart is meer nodig: het eerste percentiel van duizend trekkingen wordt afgelezen van ongeveer tien waarden.

Welke bloklengte moet een moving block bootstrap gebruiken?

Er is geen universeel correcte lengte. Vuistregels schalen deze met de steekproefgrootte tot de macht één derde, en een praktische controle is de horizon waarop de variantie stopt met lineair schalen, wat het bovenstaande variantieratio-paneel meet. Vermeld de gebruikte lengte naast het interval.

Kunt u een maximum drawdown bootstrappen?

Ja, met een kanttekening wat betreft de volgorde. Drawdown hangt af van de reeks rendementen; een resample die is opgebouwd door te schudden, rapporteert de drawdown van een geherordende historie. Een block bootstrap houdt korte reeksen intact en is het betere instrument voor pad-statistieken.

Corrigeert een bootstrap voor overfitting?

Nee. Het meet steekproefruis binnen één rendementsreeks. Look-ahead bias en het selectie-effect van het testen van vele varianten vallen beide buiten wat het kan waarnemen.


Elk paneel hier bevat de SQL die het heeft gegenereerd. Wijzig de ticker of de vensterlengte en voer het zelf uit op de Strasmore-terminal.

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