Backtest için bootstrapping güven aralığı oluşturma
Tek bir hisse senedi eğrisi tek bir örneklem sunar. Backtest Sharpe oranı için bootstrapping ile oluşturulan güven aralıkları genellikle sıfırı içerir. Bu yöntemi inceleyin.
Geriye dönük test güven aralığı tek bir soruya yanıt verir: Hisse senedi eğrisinin ne kadarı stratejiden, ne kadarı ise üzerinde çalıştığı tarihsel kesitten kaynaklanmaktadır? Bir geriye dönük testin "bootstrapping" yöntemiyle analizi, getiri serisinin birçok kez yeniden örneklenmesi, her örneklemde istatistiğin yeniden hesaplanması ve elde edilen sonuçların yüzdelik dilimlerinin okunmasıyla bu aralığı oluşturur. Günlük gözlemlerle tek bir yıl üzerinden ölçülen bir Sharpe oranı, sıfırı içerecek kadar geniş bir yüzde doksan beşlik güven aralığı taşır; aşağıdaki paneller bunun nedenini ölçmektedir.
Neden bir öz sermaye eğrisi tek bir örneklemdir
Bir geriye dönük test (backtest), istatistik başına size tek bir sayı sunar: tek bir Sharpe oranı, tek bir yıllık getiri, tek bir en kötü düşüş (drawdown), tek bir başarı oranı. Bunların hiçbiri stratejinin gerçek değerini yansıtmaz. Her biri, sınırlı sayıda işlem gününden elde edilen bir tahmindir; aynı uzunluktaki farklı bir zaman dilimi seçilseydi, farklı sonuçlar ortaya çıkardı.
Aşağıdaki panel, stratejiyi tamamen denklemin dışına çıkarır. En basit pozisyonu, yani SPY tutma stratejisini, takvim yılı bazında ve kapanıştan kapanışa fiyat getirileri üzerinden ölçer.
Her verinin arkasındaki tam SQL
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 yearHer satır, standart bir işlem yılına yakın olan yaklaşık 250 seansı kapsar. Pozisyonun yapısında bu süre zarfında hiçbir değişiklik yapılmamıştır. 2012 döneminde yıllıklandırılmış Sharpe oranı 1.06 olarak ölçülmüştür; 2025 döneminde ise bu değer 0.88 olmuş ve grafikte 14 yıllık veri kullanılmıştır. Geniş bir endeksi bir yıl boyunca tutmak, ana göstergeyi çoğu okuyucunun gürültü olarak sınıflandıracağından daha fazla hareket ettirir; aynı uzunluktaki bir strateji backtesti de en az bu kadar değişkenliği miras alır. Sütunun arkasındaki yıllıklandırma kuralı Sharpe oranı rehberimizde, dönem getirisi mekaniği ise aylık getirilerin nasıl ölçüldüğü kısmında yer almaktadır.
Geriye dönük test Sharpe oranındaki bant ne kadar geniştir?
Sharpe tahmini için standart hata, gözlem sayısının karekökü ile kabaca ters orantılı olarak azalır. Bu formüle güvenmek yerine, spread değerini doğrudan ölçmek daha sağlıklıdır. Yirmi yıllık işlem seanslarını sabit uzunlukta birbirini örtüşmeyen pencerelere bölün, her pencere içindeki yıllıklandırılmış Sharpe oranını hesaplayın ve bu rakamların birbirinden ne kadar uzaklaştığını inceleyin.
Her verinin arkasındaki tam SQL
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 wPencere başına 21 sessions değerinde, ölçülen Sharpe oranının yüzde beş ile yüzde doksan beş aralığı -3.37 ile 6.97 arasında seyretmekte olup, 238 pencere genelinde 10.34 Sharpe puanlık bir spread oluşmaktadır. Her pencereyi 504 sessions seviyesine genişlettiğinizde spread 1.67 puana düşer ve bu kez sadece 8 pencere üzerinden ölçüm yapılır. İki değişken aynı anda hareket eder. Tahmin örneklem büyüklüğü ile daha hassas hale gelirken, geçmiş verilerin sağlayabileceği bağımsız örneklem sayısı azalır. Tüm bu konu, söz konusu gerilim üzerine kuruludur.
Backtest güven aralığı nasıl oluşturulur
İzlenecek prosedür, tamamını yazmaya yetecek kadar kısadır.
- Stratejinin gerçekleşmiş getiri serisi ile başlayın; her dönem için bir adet olmak üzere toplam N adet veri kullanın.
- Bu listeden yerine koymalı (with replacement) yöntemle rastgele N adet getiri çekin. Bazı veriler iki kez gelebilir, bazıları ise hiç gelmeyebilir.
- Yeniden örneklenen veri seti üzerinde istatistiği tekrar hesaplayın.
- Bu işlemi birkaç bin kez tekrarlayın ve her bir değeri kaydedin.
- Kaydedilen değerleri sıralayın ve yüzde doksan beşlik bir aralık için iki buçukuncu ve doksan yedi buçukuncu yüzdelik dilimleri okuyun.
İkinci adımdan önce rastgele sayı üretecini başlatın (seed) ve bu başlangıç değerini yayınlanan aralığın yanına not edin. Bootstrap bir Monte Carlo tahminidir: Başlangıç değeri atanmamış iki çalışma son hanelerde farklılık gösterir ve sizin aralığınızı tekrar hesaplayamayan bir denetçinin bunu doğrulama imkanı yoktur. Bu, tekrarlanabilir bir backtest kurulumu başlığında açıklanan disiplinle aynıdır.
Ortalama getiri ve Sharpe oranı, ikinci adımdan sorunsuz geçer çünkü her ikisi de getiri listesini bir küme olarak okur. Maksimum drawdown (maksimum kayıp) ise böyle değildir. Drawdown, getiri yolunu sırasıyla okur. Getirilerin sırasını değiştiren bir yeniden örnekleme, gerçek işlem dizisinin hiçbir sıralamasında oluşmayan bir en kötü drawdown değeri üretir. Yine de, çıktı ne olduğu açıkça belirtildiği sürece bootstrap yapmak değerlidir: Bu, bir sonraki dönemin tahmini değil, yeniden karıştırılmış geçmiş veriler üzerindeki drawdown dağılımıdır. Tanımı maksimum drawdown başlığında yer almaktadır.
Why the IID bootstrap is wrong for market returns
Step 2 assumes every return is independent and identically distributed, the IID assumption. Daily returns break it in a way that changes interval width. Signed returns carry only faint one-day memory. Their magnitudes cluster: large moves sit next to large moves, and quiet days arrive in runs.
Her verinin arkasındaki tam SQL
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 DESCFor SPY, the lag-one autocorrelation of absolute daily returns measured 0.366 across 2514 sessions, against -0.133 for the signed returns on the same days. Compare the two bars on any name in the chart. Shuffling a return series destroys that clustering, and an IID bootstrap run on this data reports a narrower interval than the sample supports. The error runs in the least helpful direction: it flatters the strategy.
Hareketli blok bootstrap yöntemi ve blok uzunluğunun seçimi
Çözüm, tekil getiriler yerine birbirini izleyen blokların yeniden örneklenmesidir. L kadar bir blok uzunluğu seçilir, L adet ardışık getiriden oluşan bloklar yerine koymalı olarak rastgele çekilir ve sentetik seri N uzunluğuna ulaşana kadar uç uca eklenir. Blok içindeki bağımlılık yapısı bozulmadan korunur: bu getiriler orijinal sıralarını muhafaza eder. Yalnızca blokların birleştiği noktalar yapaydır.
Blok uzunluğu seçimi iki hata türü arasında bir denge gerektirir ve hiçbir ayar her ikisinden de kaçınamaz. Kısa bloklar IID bootstrap gibi davranır ve aralığı olduğundan düşük gösterir; bu bir yanlılıktır. Uzun bloklar daha fazla bağımlılığı korur ancak çekim yapılabilecek farklı blok sayısını azaltır; bu durumda her yeniden örnekleme aynı geçmişin büyük parçalarını tekrarlar ve aralığın kendisi gürültülü hale gelir; bu ise varyanstır. Yayınlanmış pratik kurallar, uzunluğu N'in üçte bir kuvveti ile ölçeklendirir. Bunları başlangıç noktası olarak kabul etmek gerekir.
Daha düşük maliyetli bir teşhis yöntemi, varyansın vade ile nasıl ölçeklendiğini kontrol etmektir. Bağımsızlık varsayımı altında, k-günlük getiri toplamının varyansı, bir-günlük varyansın k katına eşittir ve bu ikisinin oranı 1 seviyesinde seyreder.
Her verinin arkasındaki tam SQL
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 k2 sessions tarihinde oran 0.844 olarak ölçülmüştür; 63 sessions tarihinde ise örtüşmeyen 78 blok üzerinden hesaplanan değer 0.584 olmuştur. 1'e yakın değerler, toplamın o vadede bağımsız çekimlerle aynı şekilde ölçeklendiği anlamına gelir. 1'den uzak değerler, bağımlılığın hala etkili olduğu vadeleri işaret eder ve blok uzunluğunun kapsaması gereken aralık budur. Bu aralığı temizleyen en kısa bloğu seçin, ardından blok sayısının ne kadar düştüğünü inceleyin.
Kategorize edilmiş yeniden örnekleme ve her aralığın yanındaki sayım
Kategoriler dahilinde yeniden örnekleme yapmak; volatilite rejimine veya takvim ayına göre, soruyu ilginç kılan koşullandırmayı korur. Eğer bir stratejinin Sharpe oranını yüksek volatilite rejimlerinde kazandığı iddia ediliyorsa, yalnızca yüksek volatilite günlerinden çekilerek oluşturulan bir aralık, bu iddiayı test eden aralıktır. Bunun maliyeti aritmetiktir. İki yüz elli gözlemi dört kategoriye ayırmak, kategori başına yaklaşık altmış gözlem bırakır ve altmış gözlemlik bir bootstrap, tam örneklem versiyonuna kıyasla yaklaşık iki kat daha geniş bir aralık verir.
Bu nedenle, her seferinde her aralığın yanındaki kategori sayısını raporlayın. Yukarıdaki panellerde yer alan block_count sütunu, bu alışkanlığın görünür kılınmış halidir: dokuz pencereden okunan bir beşinci yüzdelik dilim, her ikisi de iki ondalık basamakla yazdırılsa bile, iki yüzden okunan bir değerden farklı bir nesnedir.
Bootstrap yönteminin düzeltemeyeceği hatalar
Bootstrap yöntemi tek bir şeyi, yani eldeki verilerden hesaplanan bir istatistiğin örnekleme gürültüsünü nicelleştirir. Söz konusu verilerin gerçekleşebilir olup olmadığı konusunda ise hiçbir bilgi sunmaz.
İleriye dönük önyargı (look-ahead bias) ile kirlenmiş bir geriye dönük test (backtest), asla alınıp satılamayacak bir getiri serisi üretir; bu seriye uygulanan bootstrap ise kurgusal bir verinin etrafında dar ve güven verici bir bant oluşturur. Günümüz endeks üyelerinden oluşturulan bir evren hayatta kalma önyargısı (survivorship bias) taşır ve bu evrenden alınan her yeniden örnekleme (resample) aynı önyargıyı devralır. Üçüncü bir eksiklik ise seçim etkisidir: iki yüz farklı varyant çalıştırıp en iyisini seçtiğinizde, bootstrap aralığı sadece o seçilen varyantın örnekleme gürültüsünü tanımlar; ancak onu seçmek için elenen yüz doksan dokuz denemeyi görmezden gelir. Dürüst hesaplanmış bantlar faydalıdır. Ancak bunlar, örneklem dışı (out-of-sample) verilerin yerini tutamaz.
Veri notları ve yöntem
- Getiriler, günlük barlardan elde edilen kapanıştan kapanışa fiyat getirileridir. Temettüler hariç tutulmuştur; bu durum her getiri ve Sharpe oranını yaklaşık temettü verimi kadar düşük göstermektedir. Bu yazının konusu olan dağılım ise bu durumdan neredeyse hiç etkilenmemektedir.
- Pencereler ve bloklar örtüşmemektedir; dolayısıyla ölçülen her istatistik, geçmişin birbirinden bağımsız bir dilimini kullanır. Örtüşen pencereler, görünürdeki örneklem sayısını yapay olarak artırırdı.
- Kantiller deterministik bir tahminleyici kullanır; bu nedenle bir panelin yeniden çalıştırılması, yeni bir yaklaşım yerine aynı yüzdelik dilimi döndürür.
- Otokorelasyon paneli, pencere içinde hisse bölünmesi yaşamamış altı büyük ismi kullanır ve fiyat düzeltme kaynaklı yapay etkileri korelasyonun dışında tutmak için yüzde otuz beşin üzerinde hareket görülen günleri eler.
Sıkça Sorulan Sorular
Bootstrap güven aralığı bir backtest hakkında ne söyler?
Aynı süreç aynı sayıda dönem boyunca tekrar örneklenseydi, istatistiğin alabileceği makul değer aralığını gösterir. Sıfırı içeren yüzde doksan beşlik bir aralık, örneklemin stratejiyi herhangi bir getiri avantajından ayırt etmek için çok kısa olduğu anlamına gelir.
Kaç bootstrap yeniden örneklemesi yeterlidir?
Yüzde doksan beşlik bir aralık için birkaç bin yeniden örnekleme genellikle kararlıdır; on bin ise maliyeti düşük, yaygın bir varsayılan değerdir. Daha derin kuyruk yüzdelikleri daha fazlasını gerektirir: bin çekilişin birinci yüzdeliği yaklaşık on değer üzerinden okunur.
Hareketli blok bootstrap hangi blok uzunluğunu kullanmalıdır?
Evrensel olarak doğru bir uzunluk yoktur. Pratik kurallar bunu örneklem büyüklüğünün üçte bir kuvvetiyle ölçeklendirir; pratik bir kontrol yöntemi ise yukarıdaki varyans oranı panelinin ölçtüğü, varyansın doğrusal ölçeklenmeyi bıraktığı ufuk çizgisidir. Kullandığınız uzunluğu aralıkla birlikte yayınlayın.
Maksimum drawdown bootstrap edilebilir mi?
Evet, ancak bir sıralama uyarısıyla. Drawdown getiri dizisine bağlıdır; bu nedenle karıştırılarak oluşturulan bir yeniden örnekleme, yeniden sıralanmış bir geçmişin drawdown değerini raporlar. Blok bootstrap kısa serileri bozulmadan tutar ve yol istatistikleri için daha iyi bir araçtır.
Bootstrap aşırı uyumu (overfitting) düzeltir mi?
Hayır. Sadece bir getiri serisi içindeki örnekleme gürültüsünü ölçer. İleriye dönük önyargı (look-ahead bias) ve birçok varyantın test edilmesinden kaynaklanan seçim etkisi, bootstrap'in görebildiği alanın dışındadır.
Buradaki her panel, kendisini üreten SQL kodunu içerir. Ticker sembolünü veya pencere uzunluğunu değiştirin ve Strasmore terminalinde kendiniz çalıştırın.