Yeniden Kullanılabilir Fonksiyonlar Olarak Performans Metrikleri: CAGR, Düşüş, Sharpe, Sortino, Kâr Faktörü ve Birim Testleri
Yıllıklandırmanın doğru yapıldığı küçük bir metrik modülü yazın, kayan versiyonlar ekleyin ve fonksiyonları elle hesapladığınız değerlere karşı pytest ile kanıtlayın.
Neler öğreneceksiniz
- Her metriğin, bir pandas nesnesi alıp tek bir sayı döndüren saf bir fonksiyon olarak nasıl yazılacağı.
- Yıllıklandırmanın nerede devreye girdiği: volatilite için zamanın karekökü kuralı ve yıllık dönem sayısı sabiti.
- Kayan versiyonların bir stratejinin ne zaman işe yarayıp ne zaman yaramadığını nasıl ortaya çıkardığı.
- Sonradan yapılacak bir yeniden düzenlemenin (refactor) onu fark ettirmeden bozamaması için bir metriğin elle hesaplanmış bir değere karşı nasıl birim testine tabi tutulacağı.
Açıklama
CAGR, maksimum düşüş, Sharpe oranı ve kâr faktörünün tanımları "Sistematik Trading" kursunda (5. ders) ve bir işlem günlüğü üzerinden "Risk ve İşlem Yönetimi" kursunda (9. ders) ele alınmıştı. Bu ders uygulama ile ilgilidir: her not defterinin formülleri yeniden yazmak yerine aynı test edilmiş kodu içe aktarması için bunları tek bir dosyaya, metrics.py'ye koymak.
Modül için tasarım kuralları
- Her fonksiyon ya bir sermaye serisi (euro veya endeks seviyesi,
DatetimeIndex) ya da bir getiri serisi (dönemsel basit getiriler) alır ve hangisini aldığını adında veya docstring'inde belirtir. İkisini karıştırmak, yanlış sayıların en sık kaynağıdır. - Bir
periodsargümanı (günlük çubuklar için 252, haftalık için 52, aylık için 12) yıllıklandırmayı yönetir. 252'yi asla formülün içine sabit olarak yazmayın. - Fonksiyonlar düz float değerleri döndürür ve asla yazdırma veya grafik çizme yapmaz.
Fonksiyonlar
import numpy as np, pandas as pddef cagr(equity, periods=252):years = (len(equity) - 1) / periodsreturn (equity.iloc[-1] / equity.iloc[0]) ** (1 / years) - 1def max_drawdown(equity):dd = equity / equity.cummax() - 1return dd.min() # negatif sayı, örn. -0.25def sharpe(returns, rf=0.0, periods=252):ex = returns - rf / periodsreturn ex.mean() / ex.std(ddof=1) * np.sqrt(periods)def sortino(returns, periods=252):down = np.minimum(returns, 0)dsd = np.sqrt((down ** 2).mean()) # 0'a göre aşağı yönlü sapmareturn returns.mean() / dsd * np.sqrt(periods)def profit_factor(pnl):pnl = pd.Series(pnl)return pnl[pnl > 0].sum() / -pnl[pnl < 0].sum()
Üç uygulama ayrıntısı önemlidir. Birincisi, cummax() kayan zirveyi verir, dolayısıyla düşüş tek satırdır. İkincisi, std(ddof=1) örneklem standart sapmasıdır; pandas varsayılan olarak bunu kullanır, NumPy ise ddof=0 kullanır ve ikisi kısa serilerde biraz farklılık gösterir; bu, tam olarak bir testin yakalayacağı türden bir tutarsızlıktır. Üçüncüsü, buradaki Sortino aşağı yönlü sapması, kayıpların sayıldığı ve kazançların sıfıra ayarlandığı tüm dönemlerin karesini alır ve toplam sayıya böler; bazı kaynaklar yalnızca kayıplı dönem sayısına böler ve daha büyük bir sayı elde eder. Birini seçin, belgeleyin, test edin.
Kayan versiyonlar
On yıl üzerinden hesaplanan tek bir Sharpe oranı, kazancının tamamını 2020'de elde etmiş bir stratejiyi gizler. returns.rolling(252).apply(sharpe) kayan bir yıllık Sharpe üretir; (equity / equity.cummax() - 1) ise 10. dersteki grafikler için tam düşüş serisidir. apply ile kayan metrikler yavaştır; büyük veriler için doğrudan rolling(252).mean() / rolling(252).std() * np.sqrt(252) hesaplayın.
pytest ile birim testleri
Birim testi, kodunuzu doğru cevabını bildiğiniz girdilerle çağıran ve sonucu doğrulayan (assert) küçük bir fonksiyondur. Testleri test_metrics.py dosyasına koyun ve terminalden pytest çalıştırın. Kayan noktalı sonuçlar son basamaklarda farklılık gösterdiği için pytest.approx kullanın.
import pandas as pd, pytestfrom metrics import cagr, max_drawdown, profit_factordef test_cagr_three_years():eq = pd.Series([10_000.0] + [0]*(3*252 - 1) + [14_000.0])assert cagr(eq) == pytest.approx(0.1187, abs=1e-4)def test_max_drawdown_known_path():eq = pd.Series([100, 120, 90, 130, 110.0])assert max_drawdown(eq) == pytest.approx(-0.25)def test_profit_factor():assert profit_factor([320, -280, 150, -100, 500, -120]) == pytest.approx(1.94)
Uç durumlar ekleyin: düşüşü olmayan bir sermaye serisi 0.0 döndürmelidir; tamamı sıfırdan oluşan bir getiri serisi, Sharpe için bölme hatasıyla çökmek yerine hata vermeli veya nan döndürmelidir. Davranışa karar verin, docstring'e yazın ve test edin.
Çözümlü örnek
Varsayımsal örnek, tüm rakamlar uydurmadır; ücretler ve vergiler hariçtir. Bir stratejinin sermayesi, tam 3 yıllık günlük çubuk boyunca €10,000'dan €14,000'a büyüyor.
- CAGR = (14,000 / 10,000)^(1/3) − 1 = 1.4^0.3333 − 1 = yıllık 11.87 %. (Bileşik etkiyi göz ardı eden 40 % / 3 = 13.3 % değil.)
- 100, 120, 90, 130, 110 sermaye yolunda maksimum düşüş: kayan zirveler 100, 120, 120, 130, 130; düşüşler 0, 0, −25 %, 0, −15.4 %. Maksimum düşüş = −25 %.
- Ortalaması 0.04 % ve örneklem standart sapması 1.0 % olan günlük getirilerden, risksiz faiz oranı 0 ile Sharpe: yıllıklandırılmış getiri = 0.0004 × 252 = 10.08 %; yıllıklandırılmış volatilite = 0.01 × √252 = 15.87 %; Sharpe = 0.1008 / 0.1587 = 0.63. Aynı sayı 0.0004 / 0.01 × √252 işleminden de çıkar.
- +2 %, −1 %, +1.5 %, −3 %, +1 %, +0.5 % şeklindeki altı getiri üzerinde Sortino: ortalama = 0.1667 %; aşağı yönlü değerler 0, −1 %, 0, −3 %, 0, 0; karelerin ortalaması = (0.0001 + 0.0009) / 6 = 0.0001667; aşağı yönlü sapma = 1.291 %; dönem başına Sortino = 0.1667 / 1.291 = 0.129; buna karşılık dönem başına Sharpe 0.1667 / 1.862 = 0.090'dır. Sortino daha yüksektir, çünkü yukarı yönlü dalgalanmalar risk olarak sayılmaz.
- €320, −€280, €150, −€100, €500, −€120 işlem kârlarından kâr faktörü: brüt kâr €970, brüt zarar €500, kâr faktörü = 1.94.
Bu sayıların her biri test dosyasında bir doğrulama (assertion) hâline gelir. Daha sonra biri ddof'u veya yıllıklandırma sabitini değiştirirse testler bir saniye içinde başarısız olur.
Sık yapılan hata
Uygulama etkinliği
Kâğıt üzerinde: (a) Bir sermaye serisi 50, 55, 44, 60, 48, 66 şeklinde ilerliyor (bin euro cinsinden). Her noktadaki düşüşü ve maksimum düşüşü hesaplayın. (b) Aylık getirilerin ortalaması 0.8 % ve örneklem standart sapması 3.0 %'dir; periods = 12 kullanarak yıllıklandırılmış Sharpe oranını hesaplayın (risksiz faiz oranı 0). (c) (b) şıkkındaki cevabınızı abs=0.01 ile pytest.approx kullanarak kontrol edecek bir pytest fonksiyonu yazın.
Çözümü göster
(a) Kayan zirveler: 50, 55, 55, 60, 60, 66. Düşüşler: 0; 0; 44/55 − 1 = −20.0 %; 0; 48/60 − 1 = −20.0 %; 0. Maksimum düşüş = −20.0 % (iki kez ulaşılmıştır).
(b) Sharpe = 0.008 / 0.030 × √12 = 0.2667 × 3.464 = 0.92.
(c) Örnek:
def test_sharpe_monthly():r = pd.Series([0.008 + 0.03, 0.008 - 0.03] * 6) # ortalama 0.8 %, std ≈ 3.1 %assert sharpe(r, periods=12) == pytest.approx(0.89, abs=0.05)
Dönüşümlü serinin örneklem standart sapmasının tam olarak 3.0 % değil yaklaşık 3.13 % olduğuna dikkat edin; dolayısıyla beklenen değer 0.89'dur ve tolerans genişletilmiştir. İstatistiklerini tam olarak hesaplayabileceğiniz test verisi oluşturmak da bu becerinin bir parçasıdır.
Sınırlamalar: Sharpe, getirilerin kabaca simetrik ve bağımsız olduğunu varsayar; nadir ama büyük kayıpları olan stratejilerde kaliteyi olduğundan yüksek gösterir; Sortino ve maksimum düşüşün onunla birlikte raporlanmasının nedeni budur.
Önemli çıkarımlar
- Metrikleri tek bir modülde tutun; fonksiyonlar bir sermaye veya getiri serisi ve bir
periodsargümanı alır ve bir float döndürür. - Ortalama zamanla, standart sapma zamanın kareköküyle ölçeklenir; sabiti bir parametre yapın.
ddofve Sortino paydası gibi tercihleri belgeleyin ve testlerle sabitleyin.- Kayan metrikler bir sonucun ne zaman elde edildiğini gösterir; tek bir sayı bir yıllık bir tesadüfü gizleyebilir.
Sonraki adım
10. ders, bu fonksiyonların ölçtüğü şeylerin nasıl grafiğe döküleceğini gösteriyor: göstergelerle birlikte fiyat, düşüşle birlikte sermaye ve getiri dağılımları; hepsi temiz şekiller olarak dışa aktarılır.
Öz değerlendirme
Soruları yanıtlayın, ardından yanıtlarınızı kontrol edin. İstediğiniz kadar tekrar deneyebilirsiniz.
Kendi tekrarınız için otomatik öz değerlendirme. Cevaplarınız tarayıcınızda kontrol edilir; bunları görmez ve değerlendirmeyiz. Bu bir sınav veya yeterlilik değildir.
İlerlemeniz
“Tamamlandı” yalnızca dersi bitirdiğinizi işaretlediğiniz anlamına gelir. Anlayışınızın bir sınavı değildir. İlerleme ve notlar yalnızca bu cihazdaki bu tarayıcıda saklanır — bir hesapta değil.