Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Cara Membina Selang Keyakinan Bootstrapping Backtest

Satu keluk ekuiti hanyalah satu sampel. Selang keyakinan bootstrap pada Sharpe ratio backtest sering kali cukup lebar sehingga merangkumi sifar. Ketahui cara membina dan membacanya.

Selang keyakinan ujian semula (backtest) menjawab satu soalan: sejauh mana keluk ekuiti itu didorong oleh strategi, dan sejauh mana ia dipengaruhi oleh tempoh sejarah tertentu yang digunakan. Bootstrapping ujian semula membina selang tersebut dengan melakukan pensampelan semula siri pulangan berkali-kali, mengira semula statistik bagi setiap sampel, dan membaca persentil daripada hasil yang diperoleh. Nisbah Sharpe sebanyak satu perpuluhan empat yang diukur sepanjang satu tahun pemerhatian harian membawa selang sembilan puluh lima peratus yang cukup lebar sehingga merangkumi angka sifar, dan panel di bawah mengukur sebabnya.

Mengapa satu keluk ekuiti adalah satu sampel

Ujian ke belakang (backtest) memberikan anda satu angka bagi setiap statistik: satu Sharpe ratio, satu pulangan tahunan, satu penurunan nilai (drawdown) terburuk, dan satu kadar kejayaan. Tiada satu pun daripada angka ini merupakan nilai sebenar strategi tersebut. Setiap satunya adalah anggaran yang dibina daripada tempoh hari dagangan yang terhad, dan tempoh masa berbeza dengan panjang yang sama akan menghasilkan angka yang berlainan.

Panel di bawah mengetepikan strategi tersebut sepenuhnya daripada persoalan ini. Ia mengukur kedudukan paling mudah, iaitu memegang SPY, satu tahun kalendar pada satu masa, berdasarkan pulangan harga tutup-ke-tutup.

PertanyaanSatu posisi, satu tahun: Sharpe tahunan SPY mengikut tahun kalendar
SQL tepat di sebalik setiap nombor
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

Setiap baris meliputi kira-kira 250 sesi, menghampiri tahun dagangan standard. Tiada apa-apa mengenai kedudukan tersebut berubah sepanjang tempoh itu. Pada 2012, Sharpe ratio tahunan diukur pada 1.06; pada 2025, ia diukur pada 0.88, dengan 14 tahun dipaparkan pada carta. Tempoh pegangan indeks meluas selama satu tahun mengubah angka utama lebih daripada apa yang dianggap oleh kebanyakan pembaca sebagai gangguan (noise), dan ujian ke belakang strategi dengan tempoh yang sama mewarisi sekurang-kurangnya jumlah gangguan tersebut. Konvensyen tahunan di sebalik lajur ini terdapat dalam panduan Sharpe ratio kami, dan mekanik pulangan tempoh dalam cara pulangan bulanan diukur.

Sejauh manakah julat di sekitar Sharpe backtest?

Ralat piawai bagi anggaran Sharpe berkurangan secara kasarnya mengikut punca kuasa dua bilangan pemerhatian. Daripada bergantung pada rumus tersebut, ukur spread secara terus. Bahagikan dua puluh tahun sesi dagangan kepada tetingkap yang tidak bertindih dengan tempoh tetap, hitung Sharpe tahunan dalam setiap tetingkap, dan perhatikan sejauh mana perbezaan angka-angka tersebut.

PertanyaanSerakan Sharpe diukur merentas tetingkap SPY yang tidak bertindih, 2006 hingga 2025
SQL tepat di sebalik setiap nombor
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

Pada 21 sessions bagi setiap tetingkap, persentil ke-5 hingga ke-95 bagi Sharpe yang diukur adalah daripada -3.37 hingga 6.97, iaitu spread sebanyak 10.34 mata Sharpe merentasi 238 tetingkap. Panjangkan setiap tetingkap kepada 504 sessions dan spread tersebut jatuh kepada 1.67 mata, yang kini diukur hanya ke atas 8 tetingkap. Dua perkara berubah serentak. Anggaran menjadi lebih tepat dengan saiz sampel, dan bilangan sampel bebas yang boleh dibekalkan oleh sejarah akan merosot. Ketegangan itulah keseluruhan subjeknya.

Cara membina selang keyakinan backtest secara bootstrap

Prosedur ini cukup ringkas untuk ditulis sepenuhnya.

  1. Mulakan dengan siri pulangan sebenar strategi tersebut, satu angka bagi setiap tempoh, sebanyak N angka.
  2. Pilih N pulangan daripada senarai itu secara rawak, dengan penggantian. Sesetengah angka muncul dua kali, sesetengah tidak muncul langsung.
  3. Kira semula statistik tersebut berdasarkan sampel semula itu.
  4. Ulangi proses ini beberapa ribu kali, dan simpan setiap nilai.
  5. Susun nilai yang disimpan dan ambil persentil ke-2.5 dan ke-97.5 untuk mendapatkan selang sembilan puluh lima peratus.

Tetapkan benih (seed) penjana nombor rawak sebelum langkah dua, dan rekodkan benih tersebut bersebelahan dengan selang yang diterbitkan. Bootstrap ialah anggaran Monte Carlo: dua larian tanpa benih akan memberikan hasil yang berbeza pada digit terakhir, dan penyemak yang tidak dapat menjalankan semula selang anda tidak mempunyai cara untuk mengesahkannya. Ini adalah disiplin yang sama seperti yang diterangkan dalam persediaan backtest yang boleh dihasilkan semula.

Pulangan purata dan Sharpe melepasi langkah dua dengan lancar, kerana kedua-duanya membaca senarai pulangan sebagai satu set. Maximum drawdown tidak berbuat demikian. Drawdown membaca urutan laluan tersebut. Sampel semula yang menyusun semula pulangan akan menghasilkan drawdown terburuk yang tidak pernah dihasilkan oleh mana-mana urutan dagangan sebenar. Melakukan bootstrapping untuknya masih berbaloi, dengan syarat output tersebut dilabelkan dengan tepat: ia adalah taburan drawdown merentas sejarah yang disusun semula, bukan ramalan untuk sejarah seterusnya. Definisi ini terdapat dalam maximum drawdown.

Mengapa bootstrap IID tidak tepat untuk pulangan pasaran

Langkah dua mengandaikan setiap pulangan adalah bebas dan mempunyai taburan yang sama, iaitu andaian IID. Pulangan harian melanggar andaian ini dengan cara yang mengubah lebar selang. Pulangan bertanda hanya membawa memori satu hari yang lemah. Magnitudnya pula berkelompok: pergerakan besar sering diikuti oleh pergerakan besar, dan hari yang tenang berlaku secara berturutan.

PertanyaanAutokorelasi lag-satu: pulangan bertanda berbanding pulangan mutlak, 2016 hingga 2025
SQL tepat di sebalik setiap nombor
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

Bagi SPY, autokorelasi lag-satu bagi pulangan harian mutlak diukur pada 0.366 merentasi 2514 sesi, berbanding -0.133 bagi pulangan bertanda pada hari yang sama. Bandingkan kedua-dua bar pada mana-mana nama dalam carta tersebut. Menyusun semula siri pulangan akan memusnahkan pengelompokan itu, dan bootstrap IID yang dijalankan ke atas data ini melaporkan selang yang lebih sempit daripada yang disokong oleh sampel. Ralat ini berlaku dalam arah yang paling tidak membantu: ia memberikan gambaran yang terlalu optimistik terhadap strategi tersebut.

Bootstrap blok bergerak, dan pemilihan panjang blok

Pembaikannya adalah dengan mengambil sampel semula blok yang bersambungan dan bukannya pulangan tunggal. Pilih panjang blok L, ambil blok yang terdiri daripada L pulangan berturutan secara rawak dengan penggantian, dan cantumkannya dari hujung ke hujung sehingga siri sintetik mencapai panjang N. Kebergantungan di dalam blok kekal utuh: pulangan tersebut kekal dalam susunan asalnya. Hanya sambungan antara blok yang bersifat tiruan.

Panjang blok menyeimbangkan dua ralat antara satu sama lain, dan tiada tetapan yang dapat mengelakkan kedua-duanya. Blok pendek berkelakuan seperti bootstrap IID dan merendahkan anggaran selang, yang merupakan pincang (bias). Blok panjang mengekalkan lebih banyak kebergantungan tetapi meninggalkan lebih sedikit blok berbeza untuk dipilih, jadi setiap sampel semula mengulangi bahagian besar sejarah yang sama dan selang itu sendiri menjadi bising, yang merupakan varians. Peraturan praktikal yang diterbitkan menskalakan panjang dengan N kuasa satu pertiga. Anggap ia sebagai titik permulaan.

Diagnostik yang lebih murah adalah dengan memeriksa bagaimana varians berskala mengikut ufuk masa. Di bawah kebebasan (independence), varians bagi jumlah pulangan k-hari adalah sama dengan k kali varians satu hari, dan nisbah antara kedua-duanya berada pada 1.

PertanyaanNisbah varians mengikut panjang blok: adakah varians SPY berskala seperti cabutan bebas?
SQL tepat di sebalik setiap nombor
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

Pada 2 sessions nisbah tersebut mengukur 0.844; pada 63 sessions ia mengukur 0.584, dikira sepanjang 78 blok yang tidak bertindih. Nilai yang hampir dengan 1 bermakna jumlah tersebut berskala seperti cara cabutan bebas pada ufuk masa tersebut. Nilai yang jauh daripada 1 menandakan ufuk masa di mana kebergantungan masih memainkan peranan, dan itulah julat yang perlu diliputi oleh panjang blok. Pilih blok terpendek yang dapat melengkapinya, kemudian lihat sejauh mana kiraan blok telah jatuh.

Pensampelan semula berbaldi dan kiraan di sebelah setiap selang

Pensampelan semula dalam baldi, mengikut rejim volatiliti atau mengikut bulan kalendar, mengekalkan penyelarasan yang menjadikan soalan tersebut menarik. Jika dakwaan menyatakan bahawa sesuatu strategi memperoleh Sharpe-nya dalam rejim volatiliti tinggi, selang yang dibina dengan hanya mengambil data daripada hari-hari volatiliti tinggi adalah selang yang menguji dakwaan tersebut. Kosnya adalah aritmetik. Membahagikan dua ratus lima puluh pemerhatian kepada empat baldi meninggalkan kira-kira enam puluh bagi setiap baldi, dan bootstrap enam puluh pemerhatian memberikan selang yang kira-kira dua kali lebih lebar daripada versi sampel penuh.

Oleh itu, laporkan kiraan baldi di sebelah setiap selang, setiap masa. Lajur block_count dalam panel di atas adalah tabiat itu yang dizahirkan: persentil kelima yang dibaca daripada sembilan tetingkap adalah objek yang berbeza daripada yang dibaca daripada dua ratus, walaupun kedua-duanya dicetak kepada dua titik perpuluhan.

Perkara yang tidak dapat diperbaiki oleh bootstrap

Bootstrap mengukur satu perkara, iaitu hingar pensampelan dalam statistik yang dikira daripada data yang anda miliki. Ia tidak memberikan sebarang petunjuk sama ada data tersebut boleh dicapai atau tidak.

Backtest yang tercemar dengan look-ahead bias menghasilkan siri pulangan yang tidak pernah boleh didagangkan, dan bootstrap ke atasnya hanya memberikan julat keyakinan yang sempit terhadap sesuatu yang tidak wujud. Alam semesta yang dibina daripada ahli indeks hari ini membawa survivorship bias, dan setiap pensampelan semula alam semesta itu mewarisi bias tersebut. Jurang ketiga ialah kesan pemilihan: jalankan dua ratus varian, simpan yang terbaik, dan selang bootstrapnya hanya menerangkan hingar pensampelan bagi varian tersebut sambil mengabaikan seratus sembilan puluh sembilan percubaan yang dilakukan untuk memilihnya. Julat yang jujur adalah berguna. Ia bukanlah pengganti kepada data luar sampel.

Nota data dan kaedah
  • Pulangan adalah pulangan harga tutup-ke-tutup daripada bar harian. Dividen dikecualikan, yang menyebabkan tahap setiap pulangan dan angka Sharpe menjadi lebih rendah daripada nilai sebenar sebanyak kira-kira hasil dividen. Serakan yang dibincangkan dalam catatan ini hampir tidak terjejas.
  • Tetingkap dan blok tidak bertindih, jadi setiap statistik yang diukur menggunakan bahagian sejarah yang berasingan. Tetingkap yang bertindih akan meningkatkan bilangan sampel yang kelihatan.
  • Kuantil menggunakan penganggar deterministik, jadi menjalankan semula panel akan memberikan persentil yang sama dan bukannya anggaran baharu.
  • Panel autokorelasi menggunakan enam nama besar tanpa pecahan saham dalam tetingkap tersebut, dan menggugurkan mana-mana hari dengan pergerakan

Soalan Lazim

Apakah yang diberitahu oleh selang keyakinan bootstrap mengenai backtest?

Ia memberikan julat nilai yang mungkin diambil oleh statistik jika proses yang sama disampel semula dalam tempoh yang sama. Selang sembilan puluh lima peratus yang mengandungi sifar bermakna sampel tersebut terlalu pendek untuk membezakan strategi itu daripada ketiadaan kelebihan langsung.

Berapakah bilangan sampel semula bootstrap yang mencukupi?

Untuk selang sembilan puluh lima peratus, beberapa ribu sampel semula biasanya stabil, dan sepuluh ribu adalah nilai lalai biasa yang tidak menelan kos tinggi. Kuantil ekor yang lebih dalam memerlukan lebih banyak: persentil pertama daripada seribu cabutan hanya dibaca daripada kira-kira sepuluh nilai.

Berapakah panjang blok yang perlu digunakan oleh moving block bootstrap?

Tiada panjang yang betul secara universal. Peraturan praktikal melaraskannya mengikut saiz sampel kepada kuasa satu pertiga, dan semakan praktikal ialah ufuk di mana varians berhenti berskala secara linear, yang diukur oleh panel nisbah varians di atas. Terbitkan panjang yang anda gunakan bersama-sama dengan selang tersebut.

Bolehkah anda melakukan bootstrap pada maximum drawdown?

Ya, dengan syarat penyusunan. Drawdown bergantung pada urutan pulangan, jadi sampel semula yang dibina dengan menyusun semula akan melaporkan drawdown bagi sejarah yang telah disusun semula. Block bootstrap mengekalkan siri pendek dan merupakan alat yang lebih baik untuk statistik laluan.

Adakah bootstrap membetulkan overfitting?

Tidak. Ia mengukur hingar pensampelan di dalam satu siri pulangan. Bias pandang ke hadapan dan kesan pemilihan daripada menguji banyak varian kedua-duanya berada di luar skop pemerhatiannya.


Setiap panel di sini membawa SQL yang menghasilkannya. Tukar ticker atau panjang tetingkap dan jalankan sendiri pada terminal Strasmore.

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