Aktif Zamana Göre PnL: Strateji Sıralamalarını Değiştiren Metrik
İki stratejiniz var. Birincisi: PnL +%300, 418 işlem, zamanın %45'inde pozisyon açık. İkincisi: PnL +%27, 38 işlem, zamanın %5'inde pozisyon açık. Hangisi daha iyi?
Birincisini seçtiyseniz — yanlış cevap verdiniz. İşte nedeni.
Ham PnL'nin Sorunu
Ham PnL — tüm backtest dönemi boyunca toplam getiri — stratejinin zamanın ne kadarında pozisyonda olduğunu hesaba katmaz. +%300 ve %45 işlem süresine sahip bir strateji, sermayenizi zamanın yarısından azında kullanır. Kalan %55'lik sürede sermaye atıl kalır.
+%27 ve %5 işlem süresine sahip bir strateji, sermayeyi yalnızca zamanın %5'inde kullanır — ancak kalan %95 diğer stratejiler için kullanılabilir durumdadır.
Bir orkestratör aracılığıyla bir strateji portföyü yürütüyorsanız, bir stratejinin boş zamanı diğerleri tarafından doldurulur. O zaman temel metrik, bir stratejinin yıl boyunca ne kadar kazandığı değil, aktif zamanın birimi başına ne kadar kazandığı olur.
Efektif Getiri Formülü

Temel Hesaplama
burada:
- Active days — pozisyonlardaki toplam süre (gün cinsinden)
- fill_efficiency — orkestratörün sinyallerle doldurabileceği zaman dilimi (0...1)
def pnl_per_active_time(
total_pnl: float, # toplam PnL, %
test_period_days: int, # backtest süresi, gün
trading_time_pct: float, # aktif zaman oranı, 0..1
fill_efficiency: float = 0.80, # slot doluluk verimliliği
) -> dict:
"""
Aktif zaman başına efektif getiriyi hesaplar.
"""
active_days = test_period_days * trading_time_pct
pnl_per_day = total_pnl / active_days
annualized_raw = pnl_per_day * 365
annualized_effective = annualized_raw * fill_efficiency
return {
"active_days": active_days,
"pnl_per_day": pnl_per_day,
"annualized_raw": annualized_raw,
"annualized_effective": annualized_effective,
}
Gerçek Stratejilerin Yeniden Hesaplanması
Dönem: 750 gün (25 ay), fill_efficiency = 0.80:
| Strateji | PnL | İşlem süresi | Aktif gün | PnL/gün | Yıllıklaştırılmış (x0.8) |
|---|---|---|---|---|---|
| Strateji C | +%300 | %45 | 337.5 | %0.89/g | %259 |
| Strateji B | +%27 | %5 | 37.5 | %0.72/g | %210 |
| Strateji A | +%58 | %15 | 112.5 | %0.51/g | %150 |
Ham PnL'ye göre: Strateji C (%300) >> Strateji A (%58) >> Strateji B (%27). Efektif getiriye göre: Strateji C (%259) > Strateji B (%210) > Strateji A (%150).
%27 PnL'li Strateji B, %300 PnL'li Strateji C ile karşılaştırılabilir hale geliyor — çünkü 9 kat daha az aktif sürede aynı parayı kazanıyor. Kalan %95'lik süre diğer stratejilerle doldurulabilir.
Doğrusal ve Bileşik Ekstrapolasyon
Yukarıdaki formül doğrusaldır. Daha basit ve daha tutucudur. Bileşik varyant, kâr yeniden yatırımını hesaba katar:
import numpy as np
def compound_annualized(total_pnl_pct, active_days, fill_efficiency=0.80):
"""Bileşik ekstrapolasyon."""
daily_return = (1 + total_pnl_pct / 100) ** (1 / active_days) - 1
annualized = (1 + daily_return) ** (365 * fill_efficiency) - 1
return annualized * 100
b_compound = compound_annualized(27, 37.5)
c_compound = compound_annualized(300, 337.5)
Bileşik ekstrapolasyonla Strateji B, Strateji C'yi geride bırakır: %540'a karşı %231. Sıralama tersine döner.
Öneri: sıralama için doğrusal ekstrapolasyon kullanın. Daha tutucudur ve az sayıda işlem üzerinde aşırı uydurmayı ödüllendirmeye daha az eğilimlidir.
Tuzak: Az Sayıda İşlem
38 işlem ve PnL/gün = %0.72 ile Strateji B cazip görünüyor. Ancak 38 işlem istatistiksel açıdan zayıf bir örnektir. Yüksek PnL/gün, şanslı bir tesadüfün sonucu olabilir.
Güven aralığı düzeltmeli puanlama
Küçük örnekleri cezalandırmak için t-dağılımını kullanıyoruz:
burada işlem başına ortalama getiri, standart sapma, işlem sayısı, t-dağılımı kantilidir.
import scipy.stats as st
import numpy as np
def confidence_adjusted_score(
trade_returns: list,
test_period_days: int,
fill_efficiency: float = 0.80,
min_trades: int = 30,
confidence: float = 0.95,
) -> dict:
"""
Örneklem büyüklüğü düzeltmeli strateji sıralaması.
"""
n = len(trade_returns)
if n < min_trades:
return {"score": 0, "reason": f"Çok az işlem ({n} < {min_trades})"}
returns = np.array(trade_returns)
mean_ret = np.mean(returns)
se = np.std(returns, ddof=1) / np.sqrt(n)
alpha = 1 - confidence
t_crit = st.t.ppf(1 - alpha / 2, df=n - 1)
ci_lower = mean_ret - t_crit * se
if mean_ret <= 0:
confidence_factor = 0
else:
confidence_factor = max(0, ci_lower / mean_ret)
total_pnl = np.sum(returns)
hold_times = [...] # her işlem için tutma saatleri
active_days = sum(hold_times) / 24
pnl_per_day = total_pnl / active_days if active_days > 0 else 0
annualized = pnl_per_day * 365 * fill_efficiency
score = annualized * max_leverage * confidence_factor
return {
"score": score,
"annualized": annualized,
"confidence_factor": confidence_factor,
"ci_lower": ci_lower,
"n_trades": n,
}
Güven düzeltmesinin etkisi
| Strateji | İşlem | Ort. getiri | SE | GA alt | Güven faktörü | Düzeltilmiş puan |
|---|---|---|---|---|---|---|
| Strateji B | 38 | %0.71 | %0.28 | %0.14 | 0.20 | %210 x 0.20 = %42 |
| Strateji C | 418 | %0.72 | %0.05 | %0.62 | 0.86 | %259 x 0.86 = %223 |
| Strateji A | 491 | %0.12 | %0.02 | %0.08 | 0.67 | %150 x 0.67 = %100 |
Güven düzeltmesinden sonra Strateji C güvenle önde: 418 işlem dar bir GA ve yüksek güven faktörü sağlıyor. 38 işlemli Strateji B cezalandırılıyor — "muhteşem" performansı varyansın sonucu olabilir.
fill_efficiency: Nereden Alınır

fill_efficiency parametresi şu soruyu yanıtlar: "Orkestratör sermayeyi zamanın ne kadarında çalıştırabilir?"
Seçenek 1: Sabit sabit
En basit yaklaşım: tüm stratejiler için fill_efficiency = 0.80. Orkestratörün boş zamanın %80'ini diğer stratejiler/çiftlerle doldurduğunu varsayar.
Avantaj: tümü için aynı, karşılaştırması kolay. Dezavantaj: stratejiler arasındaki korelasyonu hesaba katmaz.
Seçenek 2: Analitik tahmin
çiftiniz varsa, her biri zamanın 'inde aktifse, en az birinin aktif olma olasılığı:
Ancak kripto paralar yüksek korelasyona sahiptir — BTC, ETH, SOL ve geri kalanını da sürükler. Bağımsız çiftlerin efektif sayısı:
def estimate_fill_efficiency(
trading_time_pct: float,
n_pairs: int,
correlation_factor: float = 3.0, # kripto — yüksek korelasyon
max_slots: int = 10,
) -> float:
"""
fill_efficiency analitik tahmini.
Args:
trading_time_pct: bir strateji için aktif zaman oranı
n_pairs: işlem çifti sayısı
correlation_factor: korelasyon katsayısı (1=bağımsız, 5=güçlü)
max_slots: maksimum eşzamanlı pozisyon sayısı
"""
effective_n = n_pairs / correlation_factor
p_at_least_one = 1 - (1 - trading_time_pct) ** effective_n
expected_active = effective_n * trading_time_pct
utilization = min(expected_active, max_slots) / max_slots
return min(p_at_least_one, utilization)
eff_b = estimate_fill_efficiency(0.05, 10, 3.0)
eff_c = estimate_fill_efficiency(0.45, 10, 3.0)
%5 aktivite ve 10 korelasyonlu çiftle Strateji B için fill_efficiency yalnızca ~%16'dır. Bu, efektif getiriyi dramatik biçimde düşürür.
Seçenek 3: Verilerden simülasyon
En doğru yaklaşım, tüm stratejileri tüm çiftler üzerinde çalıştırmak ve gerçek slot kullanımını hesaplamaktır:
def simulate_fill_efficiency(
all_signals: dict, # {(strategy, pair): [(entry_time, exit_time), ...]}
max_slots: int = 10,
test_period_minutes: int = 750 * 24 * 60,
) -> float:
"""
Gerçek orkestratör slot kullanımını simüle eder.
"""
timeline = np.zeros(test_period_minutes)
for signals in all_signals.values():
for entry_min, exit_min in signals:
timeline[entry_min:exit_min] += 1
capped = np.minimum(timeline, max_slots)
fill_efficiency = np.mean(capped) / max_slots
return fill_efficiency
Nihai Sıralama Formülü
Tüm bileşenlerin birleştirilmesi:
def strategy_score(
trades: list,
test_period_days: int,
fill_efficiency: float = 0.80,
min_trades: int = 30,
funding_rate: float = 0.0001,
) -> float:
"""
Strateji sıralaması için nihai puan.
Şunları hesaba katar:
- Aktif gün başına PnL (sermaye kullanım verimliliği)
- MaxLev (riske göre düzeltilmiş ölçeklendirme)
- Güven düzeltmesi (küçük örneklem cezası)
- Fonlama maliyetleri (kaldıraçta gerçekçi maliyetler)
"""
n = len(trades)
if n < min_trades:
return 0
returns = np.array([t.pnl_pct for t in trades])
hold_hours = np.array([t.hold_hours for t in trades])
total_pnl = np.sum(returns)
active_days = np.sum(hold_hours) / 24
pnl_per_day = total_pnl / active_days
equity = np.cumprod(1 + returns / 100)
peak = np.maximum.accumulate(equity)
max_dd = ((equity - peak) / peak).min()
max_lev = max(1, int(50 / abs(max_dd * 100)))
funding_daily = funding_rate * 3 * max_lev * 100 # %cinsinden
net_pnl_per_day = pnl_per_day - funding_daily
annualized = net_pnl_per_day * 365 * fill_efficiency
se = np.std(returns, ddof=1) / np.sqrt(n)
mean_ret = np.mean(returns)
if mean_ret <= 0:
return 0
t_crit = st.t.ppf(0.975, df=n - 1)
ci_lower = mean_ret - t_crit * se
conf_factor = max(0, ci_lower / mean_ret)
score = annualized * max_lev * conf_factor
return score
Serideki Diğer Metriklerle Bağlantı
Bu metrik, önceki makalelerdeki araçların yerini almaz, tamamlar:
-
Kayıp-Kâr Asimetrisi: maksimum düşüş, puan formülüne giren MaxLev'i belirler. Düşüş ne kadar derin olursa, kurtarma asimetrisi nedeniyle doğrusal olmayan biçimde puan o kadar düşük olur.
-
Monte Carlo bootstrap: bootstrap'tan gelen güven aralıkları, güven faktörünün t-dağılımından daha doğru bir tahminini sağlar. t-dağılımından gelen GA'yı bootstrap'ın 5. persantiliyle değiştirebilirsiniz.
-
Fonlama oranları: fonlama maliyetleri aktif gün başına PnL'den çıkarılır. Yüksek kaldıraç ve düşük PnL/gün ile fonlama, net puanı negatif yapabilir — strateji, pozitif ham PnL'ye rağmen gerçekte kârsızdır.
Bu Neden Orkestrasyon için Önemli
Aktif zaman başına PnL, bir orkestratördeki stratejileri sıralamak için birincil metriktir. Birden fazla strateji aynı slot için rekabet ettiğinde, en yüksek puana (güven düzeltmesi hesaba katılarak) sahip olan kazanır.
Pratikte bu, şaşırtıcı kararlara yol açar: "mütevazı" ham PnL'ye sahip ancak pozisyonda kısa süre kalan stratejiler, genellikle yüksek PnL'ye sahip ancak uzun pozisyonlu "gösterişli" stratejilere öncelik verilir. Eski stratejiler, onlarca stratejilik bir portföyde sermayeyi daha verimli kullanır.
Temel içgörü: ölçeklenen tek metrik aktif gün başına PnL'dir. Ham PnL ölçeklenmez: aynı stratejiyi iki kez çalıştıramazsınız. Ancak boş zamanı diğer stratejilerle doldurabilirsiniz — ve aktif gün başına PnL, bir portföyde ne kadar kazanacağınızı doğru şekilde tahmin eder.
Sonuç
Ham yıllık PnL, kullanışlı ama yanıltıcı bir metriktir. Yatırımcının en önemli kaynağını hesaba katmaz — sermayenin çalıştığı zamanı.
Üç çıkarım:
-
Aktif gün başına PnL'yi hesaplayın. 38 günlük pozisyonda +%27'lik bir strateji = +%0.72/gün. 338 günlük pozisyonda +%300'lük bir strateji = +%0.89/gün. Fark 11 kat değil, 1.2 kattır.
-
fill_efficiency'yi hesaba katın. Korelasyonlu kripto çiftlerinden oluşan bir portföyde fill_efficiency göründüğünden düşüktür. 10 çift, 10 kat çeşitlendirme anlamına gelmez. correlation_factor = 3 ile efektif çift sayısı yalnızca ~3'tür.
-
Küçük örnekleri cezalandırın. Ortalama +%0.71 olan 38 işlem, +%0.14 ile +%1.28 arasında bir GA verir. Ortalama +%0.72 olan 418 işlem, +%0.62 ile +%0.82 arasında bir GA verir. Ortalamaların neredeyse aynı olmasına rağmen ikinci strateji daha güvenilirdir.
Aktif zamana göre PnL metriği, PnL@MaxLev'in yerini almaz — sermaye kullanım verimliliği boyutunu ekleyerek onu tamamlar. Tek bir strateji için PnL@ML yeterlidir. Strateji portföyü için aktif zamana göre PnL zorunludur.
Kaynaklar
- Lopez de Prado — Advances in Financial Machine Learning: The Sharpe Ratio
- Pardo, R. — The Evaluation and Optimization of Trading Strategies
- Bailey, D.H. & Lopez de Prado — The Deflated Sharpe Ratio
- Kelly, J.L. — A New Interpretation of Information Rate (1956)
- Quantopian — Lecture on Strategy Evaluation Metrics
- Ernest Chan — Algorithmic Trading: Portfolio Management
Atıf
@article{soloviov2026pnlactivetime,
author = {Soloviov, Eugen},
title = {PnL by Active Time: The Metric That Changes Strategy Rankings},
year = {2026},
url = {https://marketmaker.cc/tr/blog/post/pnl-active-time-metric},
version = {0.1.0},
description = {Ham yıllık PnL'nin neden farklı işlem sürelerine sahip stratejileri karşılaştırmak için zayıf bir metrik olduğu. Efektif getiri nasıl hesaplanır, fill\_efficiency neden gereklidir ve neden \%27 PnL'li bir strateji \%300'lü birini geçebilir.}
}
Yazarlar
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.