11 Eylül 2026 · tekrarlanabilirlik

Aynı kod, aynı veri, iki farklı Sharpe: geriye dönük testinizin ihtiyaç duyduğu determinizm denetimi

Aynı kod, aynı veri, iki farklı Sharpe: geriye dönük testinizin ihtiyaç duyduğu determinizm denetimi

Aynı commit, aynı parquet dosyaları, aynı makine; aralarında bir saat olan iki çalıştırma. Sharpe 1.34 ve Sharpe 1.19. Toplam getiri %41.2 ve %37.8. İşlem sayısı 1,418 ve 1,421. İki özsermaye eğrisi ayrışmadan önce, art arda 11,205 bar boyunca yazdırılan her ondalık basamağa kadar aynıydı.

0.15aynı girdilerle Sharpe farkı
3 / 1,418farklılaşan işlem
11,205ayrışmaya kadar aynı olan bar

İlk üç sayı can sıkıcı. Sonuncusuysa ilginç; çünkü sorunun tüm çalıştırmaya yayılmış özensiz kayan nokta hesaplarından kaynaklanmadığını gösteriyor. Belirli bir barda ayrık bir şey oldu, sonrası da bileşik etkiydi. Bu yazıda o barı nasıl bulduğumuzu ve 0.15'lik farkın şimdiye kadar yaptığınız her parametre taramasını nasıl etkilediğini ele alıyoruz.

Strateji: Hacmi en yüksek 30 USDⓈ-M sürekli vadeli işlem sözleşmesinde kesitsel momentum; 4 saatlik yeniden dengeleme, 12 saatlik getiriye göre ilk beş varlıkta uzun, son beş varlıkta kısa pozisyon; 18 aylık geçmiş, her pozisyon ayağı için ücretler ve fonlama hesaba katılıyor.

Çalıştırmaların ayrıştığı barı bulmak

Bar başına özsermayeyi tam hassasiyetle kaydederseniz bu iş yaklaşık dört dakika sürer. Her iki çalıştırmanın çıktısını (bar_index, equity, open_positions_hash) içeren bir CSV'ye alın, ikisini de yükleyin ve farklılaştıkları ilk indeksi bulun. Bu betiği başka bir hata için zaten yazmıştık; yoksa sabahımı buna harcardım.

11,206. bar, 2025-03-14T08:00 UTC. Önceki barda özsermaye 13 anlamlı basamağa kadar aynı. 11,206. barda pozisyon karmaları farklı: A çalıştırmasında SOL uzun, B çalıştırmasında AVAX uzun. Yön aynı, nominal tutar aynı, sembol farklı. Sonrasında olan her şey bunun devamı.

Ben de o bardaki sıralama girdilerini iki çalıştırmada yazdırdım. Birebir aynıydılar. Bayt düzeyinde aynı; aynı 30 sembol, aynı 30 skor. Skorlardan ikisi 0.0. Tam olarak sıfırdı; çünkü her iki varlığın da geriye bakış penceresinde işlem yapılmayan 4 saatlik barı vardı. Böylece kapanışın önceki kapanışa oranı 1, logaritması da 0 çıktı. Yuvarlama sonucu sıfır değil. Sıfır.

İki sembol beşinci sırada eşit puan aldı. Seçimde ilk beş alınıyordu. Bu ikisinden hangisinin seçileceği, sıralamanın onları hangi sırada bıraktığına bağlıydı; sıralama da varsaydığım gibi davranmıyordu.

Bir eşitlik, kararlı olmayan bir sıralama ve iki yıldır göz ardı ettiğim şey

Sıralama gruplandırılmış bir toplulaştırmadan geçiyordu ve onu besleyen çerçeve, her barda eşzamansız bir veri çekimiyle yeniden oluşturulan sembol kümesi üzerinde çalışan bir dict comprehension'dan geliyordu. Kümenin dolaşım sırası hash seed'e göre değişiyor; sabitlemediğiniz sürece Python, süreç başına metin hash seed'ini rastgele belirliyor: PYTHONHASHSEED. Böylece sıralama öncesindeki satırların dizilişi çalıştırmalar arasında farklılaşıyor ve eşit anahtar üzerinde kararlı olmayan bir sıralama kullanıldığında eşitlik farklı şekilde çözülüyordu.

Bunun gibi eşitlikler sıra dışı birer olay değil. Yapısal olarak ortaya çıkarlar. Bir özellik doyuma ulaştığında ya da kırpıldığında kesin eşitlikler üretirsiniz: sıfır hacimli barlarda getiriler tam olarak sıfır olur, kırpılmış bir z-skoru ±3.0 değerine sabitlenir, az sayıda farklı değer içeren bir sıralama dönüşümü onlarca eşitlik üretir, boole filtresi geçen her şeye 1.0 puan verir. Bu çalıştırmada, 4 saatlik barlarla 18 ay boyunca seçim sınırında eşitlik bulunan 47 bar vardı. Bunlardan üçü seçilen varlık sepetini değiştirdi. Diğerlerinde eşit kalan iki sembol de ya zaten sepetteydi ya da ikisi de dışarıda kaldı.

Yaklaşık bir gün boyunca veri yükleyicinin deterministik olmadığından emindim; çünkü en heyecan verici açıklama buydu. Değilmiş. Hiçbir zaman öyle olmuyor. Sorun bir küme, bir eşitlik ve kimsenin kayda geçirmediği bir sıralama kararlılığı varsayımı.

Üç işlem Sharpe'ı neden 0.15 değiştiriyor

İnsanların itiraz ettiği nokta burası. Cevap şu: özsermaye oranına göre pozisyon büyüklüğü belirlenen bir geriye dönük test, izlenen yola bağlı bir sistemdir. Her pozisyon ayağında mevcut özsermayenin %8'i kadar pozisyon açıyorsanız, n barındaki özsermaye farkı, n barından itibaren tüm nominal tutarların farklı olması demektir.

İlk ayrışmanın tek başına maliyeti düşüktü. B çalıştırmasının AVAX pozisyonu dokuz saatte %2.1 kaybetti; A çalıştırmasının SOL pozisyonu %0.4 kazandı. Bu işlemden sonraki özsermaye farkı: %0.21. Önemsiz. Ama o andan itibaren iki çalıştırma artık aynı strateji değildi. Pozisyon büyüklükleri biraz farklılaştı; dolayısıyla fonlama tahakkukları da biraz farklı oldu. Ayrıca besleyici skorlar elde tutulan pozisyonlardaki küçük farklardan hesaplandığı için, daha sonraki sınır eşitliklerinden ikisi daha farklı çözüldü. Bunlardan biri 2025-03-27'de, toplam K&Z'nin yaklaşık üçte birini getiren altı günlük yükseliş trendinden bir gün önce gerçekleşti. A çalıştırması hareketin tamamında pozisyondaydı; B çalıştırması bir yeniden dengeleme geç girdi.

Getiri farkı: 3.4 puan. Sharpe farkı, getiri farkının düşündürdüğünden daha büyük; çünkü B çalıştırmasındaki yeri değişen işlemler daha oynak bir döneme denk geldi. Böylece pay düşerken payda yükseldi. Küçük bir neden, iki büyütücü etken.

Pozisyon büyüklükleriniz sabitse ve girişleriniz mevcut pozisyonlara bağlı değilse bu etkiden çok daha az etkilenirsiniz. İlginç stratejilerin çoğu bu iki koşulu da sağlamaz.

Sorunun gerçekten ortaya çıktığı beş yer

KaynakBelirtiÇözüm
Sabitlenmemiş PYTHONHASHSEED; küme/dict dolaşım sırası sıralamayı etkiliyorEşitlikler çalıştırmalar arasında farklı çözülüyor; ayrışma belirli bir barda başlıyorSeed'i sabitleyin; eşitlikleri deterministik çözmek için açık bir ikincil anahtara (sembol) göre sıralayın
Eşit anahtar üzerinde kararlı olmayan sıralama (quicksort NumPy/pandas varsayılanı)Yukarıdakiyle aynı; seed sabitlense de sorun sürerkind="stable" kullanın ya da anahtarı tümüyle sıralanabilir hâle getirin
Bootstrap'ta, eğitim/test karıştırmalarında veya sentetik gerçekleşme oynaklığında seed verilmemiş RNGTüm çalıştırmaya yayılan sapma; belirgin bir ayrışma noktası yokHer bileşen için açık bir seed kullanıp çalıştırma manifestine kaydedin
Paralel kayan nokta indirgemesi (iş parçacığı sayısına bağlı toplama sırası)Son birkaç bitte farklar oluşur; bir eşik karşılaştırmasını geçene kadar genellikle zararsızdırAraştırma çalıştırmalarında iş parçacığı sayılarını sabitleyin; karar sınırında kayan nokta değerlerini asla == ile karşılaştırmayın
Sabitlenmemiş kütüphane sürümleriBugün tekrarlanabilir, kasımda değilVeri anlık görüntüsü karmasının yanında manifestte kilit dosyası karmasını da tutun

Dördüncü satır, insanların korktuğu kadar sık sorun yaratmıyor; ilk satırsa sürekli karşımıza çıkıyor.

Bit düzeyinde tekrarlanabilirlik bir araçtır, erdem değil

Bir satırı değiştirdiğinizde özsermaye eğrisindeki farkın o satıra bağlanabilmesi için determinizme ihtiyacınız var. Tek sebep bu. Stratmill'deki her ajan çalıştırması artık veri anlık görüntüsü karmasını, kilit dosyası karmasını ve tüm seed'leri içeren bir manifest yazıyor. Önceki eğriyi bit düzeyinde yeniden üretemeyen bir yeniden çalıştırma ilginç bir bulgu değil, başarısız bir derlemedir.

Ama tekrarlanabilirliği sağladıktan sonra sistemi bilerek değişken hâle getirin. 64 farklı seed ile 64 kez çalıştırıp dağılıma bakın:

Oynaklık aralığı. Aynı strateji, aynı veri; eşitlik çözümü ve gerçekleşme sırası için seed'lenmiş 64 permütasyon. Sharpe p5 1.12, medyan 1.27, p95 1.41. Aralık genişliği 0.29.

Şimdi parametre taramasına dönün. En iyi yapılandırmanın skoru 1.46'ydı. 96 yapılandırma arasında 40. sıradaki yapılandırmanınki 1.31'di. Aralarındaki fark 0.15; yani aralığın yarısı. Tarama bu iki yapılandırmayı sıralamadı. Her birinin dağılımından birer örnek alıp bu örnekleri sıraladı.

Bu bakış açısı seçim yöntemimizi değiştirdi. Yapılandırmalar arasındaki farklar tek bir yapılandırmanın oynaklığını aşıyorsa, tarama sonucu bir sıralamadır. 1,400 işlem içeren, izlenen yola bağlı stratejilerde oynaklık genellikle sıralama tablosunun üst üçte birlik bölümünü eşitliğe çevirecek kadar büyüktür. Böyle olduğunda, oynaklık aralığının gizleyemeyeceği ölçütlere göre seçim yapın: daha düşük devir hızı, daha az parametre, kuşkucu birine savunabileceğiniz bir maliyet varsayımı, en az güvendiğiniz ileriye dönük test bölümünde daha iyi davranış. Bunlar gerçek eşitlik bozuculardır. 0.15 Sharpe üstünlüğü öyle değildir.

Herhangi bir sonuca güvenmeden önce yapmaya değer bir şey daha var. Geriye dönük testinizi şimdi iki kez çalıştırın, bar başına özsermayeyi karşılaştırın ve bit düzeyinde aynı sonuç veren grupta mı, yoksa 0.15 grubunda mı olduğunuzu görün. Bu 15 dakikalık deney, araştırma geçmişinizin ne kadarının stratejiyi, ne kadarınınsa bir hash seed'i ölçtüğünü gösterir.

determinizmgeriye dönük testveri mühendisliğiaşırı uyumpython
← Tüm yazılar