İleriye bakma yanlılığı kendini hiçbir şekilde belli etmez ve asıl sorun da bu. Yarının bilgisini sessizce kullanan bir backtest de kullanmayan da birbirine benzeyen getiri eğrileri çizer: eğri yükselir, arada zirveden düşüşler yaşanır, işlem sayısı makul görünür. Grafiğe bakarak fark edemezsiniz, hataya yol açan kod satırı da çoğu zaman gayet mantıklı durur.
Bilinen önlemler sanıldığı kadar güçlü değil. Kodu okuyarak ancak zaten şüphelendiğiniz hatayı bulursunuz, aklınıza gelmeyeni kaçırırsınız. Yüksek bir Sharpe en fazla bir işarettir, çünkü sızıntısız pek çok strateji de yüksek Sharpe verir, sızıntılı pek çoğu da vermez. "Kullandığım altyapı buna zaten izin vermiyor" demek ise altyapı hakkında bir şey söyler, sizin onun üstüne kurduğunuz strateji hakkında değil.
Oysa bunu doğrudan test etmenin bir yolu var. Bu yazıda o testi, testi uyguladığım stratejiyi ve testin gerçekten çalıştığından emin olmak için aynı stratejiyi bilerek bozduğumda neler olduğunu anlatıyorum.
İleriye bakma yanlılığı nedir
Backtest kararları sırayla simüle eder: her gün, o gün bilinebilecek bilgiyle karar verip bir sonraki güne geçer. Sonradan ortaya çıkan bir bilginin daha önceki bir karara karıştığı her durum ileriye bakma yanlılığıdır.
En bariz örneğinden kaçınmak kolay: sinyali günün kapanış fiyatından hesaplayıp aynı günün açılışında alım yapmak. Çoğu backtest motoru bunu zaten zorlaştırıyor.
Tehlikeli olanı ise fiyatlara hiç dokunmuyor. Simülasyon başlamadan önce cevapladığınız soruların içine gizleniyor:
- Hangi hisselerde işlem yapabilirim? Bu liste dönem sonundaki veriye bakılarak çıkarıldıysa simülasyondaki bütün kararlar geleceğe göre verilmiş olur.
- Hangi hisseler yeterince likit? Aynı sorun, bir alt seviyede.
- Hangi şirketler hâlâ ayakta? Bugün derlenmiş bir fiyat dosyasında yalnızca ayakta kalanlar vardır. Bu dosyayla 2020'ye dönüp işlem yaparsanız sıfırlanan hiçbir hisseyi portföye almamış olursunuz.
Bunlar sinyal hatası değil, kurulum kararları. Bir kez veriliyorlar, kodun stratejiden ayrı bir yerinde duruyorlar ve çoğu zaman aylar önce yazılmış oluyorlar. Stratejiyi ne kadar incelerseniz inceleyin onları bulamazsınız, çünkü hata stratejinin içinde değil.
Test edilen strateji
Testi gerçek bir strateji üzerinde çalıştırmak gerekiyor, yoksa sonuç gerçek stratejiler hakkında hiçbir şey kanıtlamaz. Yazının geri kalanındaki bütün koşular aynı stratejiyle yapıldı.
Kesitsel momentum. Hisseler son dönemde ne kadar yükseldiklerine göre sıralanıyor, en güçlüler portföye alınıyor ve sıralama belirli aralıklarla yenileniyor. Arkasındaki varsayım, göreli gücün birkaç ay boyunca sürdüğü.
Her yeniden dengeleme gününde sırasıyla:
- Hisse evreni belirleniyor. ABD'nin en likit 1,500 hissesi, ama bugüne göre değil, karar gününe göre kurulmuş hâliyle. Sonradan borsadan çıkan şirketler, işlem görmeyi bıraktıkları güne kadar evrende kalıyor.
- Her hisseye puan veriliyor: son altı ayın getirisi, en yakın ay hariç. Aradaki bu boşluğa atlama deniyor. Yeni fırlamış hisseler kazancın bir kısmını geri verme eğiliminde olduğu için son ay, sinyalin en az güvenilir kısmı.
- En güçlü 20 hisse portföye alınıyor ve oynaklığın tersiyle ağırlıklandırılıyor. İki kat oynak bir hisse yarı ağırlık alıyor.
- Satışta tampon bırakılıyor. Bir hisse ilk 20'den düştüğünde satılmıyor, ilk 40'ın dışına çıktığında satılıyor. Bu tampon olmasa sınırda gidip gelen bir hisse her ay alınıp satılır ve her seferinde alış satış makası ödenir.
- Portföy ayda bir yeniden dengeleniyor. Emirler ertesi günün açılışında gerçekleşiyor ve her işleme %0.25 maliyet yazılıyor.
Testin kapsadığı dönem 2024-01-02 ile 2026-07-31 arası, orta nokta ise 2025-06-30.
Bunların çoğu test açısından pek önemli değil. Önemli olan 1. adım, çünkü sızıntıyı oraya yerleştireceğim.
Hisse evreni nasıl seçiliyor, sızıntı nereden giriyor
"ABD'nin en likit 1,500 hissesi" sabit bir liste değil. Likiditeyi ölçmek gerekiyor, ölçmek için de bir zaman aralığı seçmek gerekiyor.
Doğru kurulmuş sürümde bu aralık testin başında sabitleniyor. Hangi hisselerin evrene gireceğine yalnızca o gün elde olan veriyle karar veriliyor ve sonradan ne kadar veri yüklenirse yüklensin aynı 1,500 hisse kullanılıyor.
Bozuk sürümde ise aralık, o an hangi veri yüklüyse ona göre kayıyor. Fiyatları 2026'ya kadar yüklerseniz likidite sıralaması da 2026'ya kadarki veriyle yapılıyor ve portföy, sonradan likit olduğu anlaşılan hisselerle işlem yapıyor.
Kısacası yapılan şu: sonradan çok işlem gördüğü ortaya çıkan hisseleri seç, sonra 2024'e dönüp bu hisselerle işlem yap. Bütün fark tek bir parametrede. Kodda son derece makul görünüyor ama kesitsel hisse çalışmalarında en sık rastlanan ileriye bakma hatası da bu.
Kesme testi
Backtest dönemi içinden bir tarih seçin, buna orta nokta diyelim.
- Fiyatları yalnızca orta noktaya kadar yükleyip stratejiyi başlangıçtan orta noktaya kadar çalıştırın.
- Bütün fiyatları yükleyip aynı stratejiyi aynı başlangıçtan verinin sonuna kadar çalıştırın.
- İki getiri eğrisini ortak oldukları günler üzerinden karşılaştırın.
İlk koşu orta noktadan sonraki hiçbir veriyi kullanamaz, çünkü o veri süreçte hiç yok. İkinci koşuda ise verinin tamamı var. Orta noktadan önceki herhangi bir karar geleceğe dokunduysa, iki eğri ortak dönemin bir yerinde ayrışır.
Kabaca şöyle, bütün test bundan ibaret:
MID = "2025-06-30"
# 1. a world that ends at the midpoint
prices_a = load_prices(start=START, end=MID)
curve_a = backtest(strategy, prices_a, start=START, end=MID)
# 2. a world that knows everything
prices_b = load_prices(start=START, end=TODAY)
curve_b = backtest(strategy, prices_b, start=START, end=TODAY)
# 3. the days they share must agree
shared = sorted(set(curve_a) & set(curve_b))
diffs = [d for d in shared if abs(curve_a[d] - curve_b[d]) > TOL]
assert not diffs, f"first divergence at {diffs[0]}"Testin kendisi son satırdaki assert. Hatanın ne olduğunu bilmenize, hatta bir hata olduğundan şüphelenmenize bile gerek yok. Zaten amaç da bu: kod incelemesinden sağ çıkan hatalar, kimsenin aramayı akıl etmediği hatalardır.
Temiz sürümün sonucu
İki koşunun ortak 374 işlem günü var ve bunların 4 tanesinde portföy değerleri bit düzeyinde birebir aynı değil.
Bunu başarısızlık sanmadan önce o dört farkın büyüklüğüne bakalım: yaklaşık $36,598 değerindeki bir portföyde her biri doların milyonda biri kadar, yani milyarda üç. Fark, kayan noktalı hesapların farklı sırayla toplanmasından geliyor, zamanda geriye giden bir bilgi yok. Geri kalan bütün günler birebir tutuyor ve ortak son günde iki koşu da kuruşu kuruşuna aynı değeri veriyor: $36,598.
Hisse evrenleri de aynı: 1500 hisse içinde farklı olan hisse sayısı 0.
Yani strateji testi geçiyor. Geçmişteki kararları geleceğin verisine bağlı değil.
Başarısız olamayan bir test işe yaramaz
Bu sonuç tek başına pek bir şey ifade etmiyor. Kesme testi ne olursa olsun "geçti" diyecek olsaydı, bana strateji hakkında hiçbir şey öğretmemiş, iyi habere ne kadar kolay inandığımı ise epey iyi göstermiş olurdu.
Ben de aynı stratejiyi yukarıda anlattığım şekilde bozdum ve başka hiçbir şeyi değiştirmeden aynı testi tekrar çalıştırdım.
Test sızıntıyı 2024-01-02 tarihinde, yani ilk günde yakalıyor ve iki eğri bir daha birleşmiyor. Aynı backtest aynı tarihler için iki kez çalıştırıldığında, ortak son günde koşulardan biri $37,008, diğeri $46,667 gösteriyor. Strateji de dönem de aynı ama aradaki fark $9,659. Değişen tek şey, diskte geleceğe ait ne kadar veri durduğu.
Orta noktaya gelindiğinde, gelecek veri de yüklenince sızıntılı backtest'in kendi geçmişi %26.1 daha yüksek çıkıyor. Arada fark en fazla %28.62 seviyesine kadar açılmış.
Evrene hangi hisseler giriyor
Mekanizma en net hisse evreninde görülüyor. Seçim sızdırdığında iki koşunun evreni arasında 220 hisselik fark oluyor, kabaca her yedi hisseden biri.
Bu hisseler rastgele değil. Yalnızca gelecek görünürken evrene girenler arasında AAOI, ACHR, ADT, ALM gibi isimler var. İşlem hacmi 2025 ve 2026'da patlayan şirketler bunlar. Ocak 2024'te ABD'nin en likit 1,500 hissesi arasında değillerdi ve o tarihte çalışan bir portföyün bunları elinde tutması mümkün değildi.
Evrenden çıkardıkları hisselerde ise dikkat çeken bir şey yok: ACAD, ADNT, AGL, AL. Bunlar 2024'te likit olan, sonra yavaş yavaş en likit 1,500 hissenin dışına kayan sıradan orta ölçekli şirketler.
Yani sızıntı küçük bir hesaplama kusuru değil. Portföy, fırsat kümesinin yedide birini sessizce, yakında gündeme oturacak hisselerle değiştirmiş.
Asıl endişe verici kısım
Şimdi bütün bunların manşet rakamlara etkisine bakalım.
| Eğitim | Test | |||||||
|---|---|---|---|---|---|---|---|---|
| CAGR | Maks. düşüş | MAR | İşlem sayısı | CAGR | Maks. düşüş | MAR | İşlem sayısı | |
| Frozenseçim başta sabitlendi | 29.9% | -35.3% | 0.85 | 248 | 44.1% | -35.3% | 1.25 | 410 |
| Leakyseçim veriyle birlikte kayıyor | 30.9% | -36.7% | 0.84 | 220 | 45.8% | -38.9% | 1.18 | 354 |
Bütün veriyle sonuna kadar çalıştırıldığında sızıntılı sürüm yıllık %45.78 getiri gösteriyor, temiz sürüm ise %44.13. Aradaki fark bir buçuk puan.
Bu sonucu inceleyen hiç kimse şüphelenmez. Ortada şüphe uyandıracak kadar pürüzsüz bir getiri eğrisi ya da imkânsız bir Sharpe yok. Zirveden düşüşü biraz daha derin, MAR'ı biraz daha düşük. Biraz farklı bir fikir test edilmiş gibi duruyor ve onu tehlikeli yapan da bu. Bir backtest'i %200 şişiren bir yanlılık hemen göze batar. Bu ise batmıyor ve son tabloya ne kadar bakarsanız bakın onu gerçek bir iyileşmeden ayırt edemezsiniz.
Kesme testi ise ikisini tek koşuda, neye bakacağı söylenmeden ayırıyor.
Sızıntılı sürümün sorununu daha kısa da anlatabiliriz: geçmişi değişiyor. Bu backtest'i Haziran 2025'te çalıştırsaydınız ilk on sekiz ay için yıllık %30.89 getiri görürdünüz. Aynı kodu hiç değiştirmeden bugün çalıştırdığınızda aynı on sekiz ay %26.1 daha fazla kazandırıyor. Geçmiş sonuçların değişmemesi gerekir. Değişiyorsa backtest görmemesi gereken bir veriye bakıyor demektir.
Testin yakalayamadıkları
Kesme testi bir denetim değil, tek bir test ve yalnızca bir şeyi kontrol ediyor: orta noktadan önceki kararların sonraki veriye bağlı olup olmadığını. Aşağıdaki üç hata testten rahatlıkla geçer.
- Verinin içine yerleşmiş yanlılık. Fiyat dosyasında borsadan çıkan şirketler zaten yoksa iki koşu da aynı şekilde yanlış olur ve birbirini kusursuz tutar. Kesme testi zamanda yolculuğu arar, kötü bir veri setini değil. Hayatta kalma yanlılığını ayrıca, batan şirketlerin izini sürerek kontrol etmek gerekir.
- Kısaltılmış dönemin içindeki ileriye bakma. İşlem yaptığı mumun kapanış fiyatını kullanan bir sinyal zaten hatalıdır, ama iki koşu da aynı şekilde hatalı olduğu için sonuçlar tutar. Orta noktayı kaydırmak farklı dönemleri yakalar, yine de emirlerin gerçekleşme zamanlamasını doğrudan test etmek daha iyi.
- İleriye bakma dışındaki her şey. Aşırı uyum, gerçekçi olmayan emir gerçekleşmeleri, fazla düşük tutulmuş işlem maliyetleri, o büyüklükte hiçbir zaman işlem yapamayacağınız bir hisse evreni. Bu test hiçbiri hakkında bilgi vermez.
Testi geçmek, "bu sınırın ötesinden hiçbir gelecek verisi sızmadı" demektir. Backtest'in doğru olduğu anlamına gelmez. İkisi ayrı şeyler ve ayrı tutulmaları gerekiyor.
Kendi backtest'lerinize uygulamak
Buradaki hiçbir şey belirli bir backtest motoruna bağlı değil. İki şeye ihtiyacınız var: motorun yüklediği veriyi sınırlayabilmek ve motordan gün gün portföy değeri serisi alabilmek.
Testi uygularken edindiğim üç pratik not var.
Orta noktayı stratejinin aktif olduğu bir döneme denk getirin. Portföyün boşta beklediği bir döneme düşen orta nokta hiçbir şeyi test etmez, çünkü sızıntının etkileyebileceği bir karar yoktur. Orta noktanın iki yanında da açık pozisyon olduğu sürece dönemin ortası makul bir seçim.
Veri büyükse iki yüklemeyi ayrı süreçlerde çalıştırın. İki tam fiyat seti belleğe birlikte sığmaz. O zaman test işe yarar bir sonuç vermek yerine süreç çöker.
Bir toleransla karşılaştırın, sonra toleransı aşan günlere bakın. Bit düzeyinde eşitlik aramak yanlış bir ölçüt. Onuncu ondalık basamaktaki farklar kayan nokta hesaplarının birikmesinden gelir, ilk günde ikinci ondalık basamakta çıkan bir fark ise hatadır. İkisi arasındaki mesafe o kadar büyük ki hangisine baktığınız konusunda kafa yormanız gerekmez.
Artık bir sonucu yayımlamadan önce bu testi her seferinde çalıştırıyorum. Ucuz ve mekanik bir kontrol ama bir backtest'e inanmakla onu gerçekten kontrol etmiş olmak arasındaki fark tam da bu.