← Kembali ke artikel
March 18, 2026
5 menit baca

PnL berdasarkan Waktu Aktif: Metrik yang Mengubah Peringkat Strategi

PnL berdasarkan Waktu Aktif: Metrik yang Mengubah Peringkat Strategi
#algotrading
#backtest
#metrik
#PnL
#orkestrasi
#portofolio
#manajemen risiko

Anda memiliki dua strategi. Yang pertama: PnL +300%, 418 trade, posisi terbuka 45% dari waktu. Yang kedua: PnL +27%, 38 trade, posisi terbuka 5% dari waktu. Mana yang lebih baik?

Jika Anda memilih yang pertama — Anda menjawab dengan salah. Ini alasannya.

Masalah dengan PnL Mentah

PnL mentah — total return selama seluruh periode backtest — tidak memperhitungkan berapa fraksi waktu strategi berada dalam posisi. Strategi dengan +300% dan 45% waktu trading menggunakan modal Anda kurang dari setengah waktu. Sisa 55% waktu, modal tidak aktif.

Strategi dengan +27% dan 5% waktu trading menggunakan modal hanya 5% dari waktu — tetapi 95% sisanya tersedia untuk strategi lain.

Jika Anda menjalankan portofolio strategi melalui orkestrator, waktu idle satu strategi diisi oleh strategi lain. Metrik kunci kemudian bukan seberapa banyak suatu strategi menghasilkan dalam setahun, tetapi seberapa banyak yang dihasilkan per satuan waktu aktif.

Formula Return Efektif

Perbandingan peringkat strategi PnL per hari aktif

Perhitungan Dasar

PnLdaily=Total PnLActive days\text{PnL}_{daily} = \frac{\text{Total PnL}}{\text{Active days}}

Annualizedraw=PnLdaily×365\text{Annualized}_{raw} = \text{PnL}_{daily} \times 365

Annualizedeffective=Annualizedraw×fill_efficiency\text{Annualized}_{effective} = \text{Annualized}_{raw} \times \text{fill\_efficiency}

di mana:

  • Active days — total waktu dalam posisi (dalam hari)
  • fill_efficiency — fraksi waktu yang dapat diisi orkestrator dengan sinyal (0...1)
def pnl_per_active_time(
    total_pnl: float,        # total PnL, %
    test_period_days: int,    # panjang backtest, hari
    trading_time_pct: float,  # fraksi waktu aktif, 0..1
    fill_efficiency: float = 0.80,  # efisiensi pengisian slot
) -> dict:
    """
    Calculate effective return per active time.
    """
    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,
    }

Menghitung Ulang Strategi Nyata

Periode: 750 hari (25 bulan), fill_efficiency = 0.80:

Strategi PnL Waktu trading Hari aktif PnL/hari Disetahunkan (x0.8)
Strategi C +300% 45% 337.5 0.89%/h 259%
Strategi B +27% 5% 37.5 0.72%/h 210%
Strategi A +58% 15% 112.5 0.51%/h 150%

Berdasarkan PnL mentah: Strategi C (300%) >> Strategi A (58%) >> Strategi B (27%). Berdasarkan return efektif: Strategi C (259%) > Strategi B (210%) > Strategi A (150%).

Strategi B dengan PnL 27% ternyata sebanding dengan Strategi C dengan PnL 300% — karena menghasilkan uang yang sama dalam 9 kali lebih sedikit waktu aktif. Sisa 95% waktu dapat diisi dengan strategi lain.

Ekstrapolasi Linear vs Majemuk

Formula di atas bersifat linear. Lebih sederhana dan lebih konservatif. Varian majemuk memperhitungkan reinvestasi keuntungan:

Daily return (compound)=(1+Total PnL)1/Active days1\text{Daily return (compound)} = (1 + \text{Total PnL})^{1/\text{Active days}} - 1

Annualizedcompound=(1+Daily return)365×fill_eff1\text{Annualized}_{compound} = (1 + \text{Daily return})^{365 \times \text{fill\_eff}} - 1

import numpy as np

def compound_annualized(total_pnl_pct, active_days, fill_efficiency=0.80):
    """Compound extrapolation."""
    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)

Dengan ekstrapolasi majemuk, Strategi B melampaui Strategi C: 540% vs 231%. Peringkatnya terbalik.

Rekomendasi: gunakan ekstrapolasi linear untuk peringkat. Lebih konservatif dan kurang rentan terhadap penghargaan overfitting pada jumlah trade yang kecil.

Perangkap: Jumlah Trade yang Kecil

Strategi B dengan 38 trade dan PnL/hari = 0,72% terlihat menarik. Tetapi 38 trade adalah sampel yang lemah secara statistik. PnL/hari yang tinggi bisa jadi hasil kebetulan yang beruntung.

Penilaian yang disesuaikan dengan kepercayaan

Kami menggunakan distribusi-t untuk menghukum sampel kecil:

CIlower=rˉtα/2,n1×sn\text{CI}_{lower} = \bar{r} - t_{\alpha/2, n-1} \times \frac{s}{\sqrt{n}}

di mana rˉ\bar{r} adalah return rata-rata per trade, ss adalah standar deviasi, nn adalah jumlah trade, tα/2,n1t_{\alpha/2, n-1} adalah kuantil distribusi-t.

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:
    """
    Strategy ranking with sample size adjustment.
    """
    n = len(trade_returns)
    if n < min_trades:
        return {"score": 0, "reason": f"Too few trades ({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 = [...]  # holding hours for each trade
    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,
    }

Dampak penyesuaian kepercayaan

Strategi Trade Return rata-rata SE CI bawah Faktor kepercayaan Skor yang disesuaikan
Strategi B 38 0,71% 0,28% 0,14% 0,20 210% x 0,20 = 42%
Strategi C 418 0,72% 0,05% 0,62% 0,86 259% x 0,86 = 223%
Strategi A 491 0,12% 0,02% 0,08% 0,67 150% x 0,67 = 100%

Setelah penyesuaian kepercayaan, Strategi C memimpin dengan percaya diri: 418 trade memberikan CI yang sempit dan faktor kepercayaan yang tinggi. Strategi B dengan 38 trade dihukum — performanya yang "cemerlang" mungkin merupakan hasil dari varians.

fill_efficiency: Cara Mendapatkannya

Efisiensi pengisian dan alokasi slot orkestrator

Parameter fill_efficiency menjawab pertanyaan: "Berapa fraksi waktu yang dapat dijaga oleh orkestrator agar modal tetap bekerja?"

Opsi 1: Konstanta tetap

Pendekatan paling sederhana: fill_efficiency = 0,80 untuk semua strategi. Mengasumsikan orkestrator memanfaatkan 80% waktu idle dengan strategi/pasangan lain.

Pro: identik untuk semua, mudah dibandingkan. Kontra: tidak memperhitungkan korelasi antar strategi.

Opsi 2: Estimasi analitis

Jika Anda memiliki NN pasangan, masing-masing aktif p%p\% dari waktu, probabilitas bahwa setidaknya satu aktif:

P(1 active)=1(1p)NP(\geq 1\ \text{active}) = 1 - (1 - p)^N

Tetapi kripto sangat berkorelasi — BTC menarik ETH, SOL, dan yang lainnya bersama-sama. Jumlah pasangan independen yang efektif:

Neff=Ncorrelation factorN_{eff} = \frac{N}{\text{correlation factor}}

def estimate_fill_efficiency(
    trading_time_pct: float,
    n_pairs: int,
    correlation_factor: float = 3.0,  # crypto — high correlation
    max_slots: int = 10,
) -> float:
    """
    Analytical estimate of fill_efficiency.

    Args:
        trading_time_pct: fraction of active time for one strategy
        n_pairs: number of trading pairs
        correlation_factor: correlation coefficient (1=independent, 5=strong)
        max_slots: maximum number of simultaneous positions
    """
    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)

Untuk Strategi B dengan aktivitas 5% dan 10 pasangan yang berkorelasi, fill_efficiency hanya ~16%. Ini secara dramatis mengurangi return efektif.

Opsi 3: Simulasi dari data

Pendekatan paling akurat adalah menjalankan semua strategi pada semua pasangan dan menghitung utilisasi slot yang sebenarnya:

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:
    """
    Simulate real orchestrator slot utilization.
    """
    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

Formula Peringkat Akhir

Menggabungkan semua komponen:

def strategy_score(
    trades: list,
    test_period_days: int,
    fill_efficiency: float = 0.80,
    min_trades: int = 30,
    funding_rate: float = 0.0001,
) -> float:
    """
    Final score for strategy ranking.

    Accounts for:
    - PnL per active day (capital usage efficiency)
    - MaxLev (risk-adjusted scaling)
    - Confidence adjustment (penalty for small sample)
    - Funding costs (realistic costs at leverage)
    """
    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  # in %
    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

Hubungan dengan Metrik Lain dalam Seri

Metrik ini tidak menggantikan tetapi melengkapi alat dari artikel sebelumnya:

  • Asimetri Kerugian-Keuntungan: drawdown maksimum menentukan MaxLev, yang dimasukkan ke dalam formula skor. Semakin dalam drawdown, semakin rendah skor — secara nonlinier, karena asimetri pemulihan.

  • Bootstrap Monte Carlo: interval kepercayaan dari bootstrap memberikan estimasi faktor kepercayaan yang lebih akurat daripada distribusi-t. Anda dapat mengganti CI dari distribusi-t dengan persentil ke-5 dari bootstrap.

  • Funding rate: biaya funding dikurangkan dari PnL per hari aktif. Dengan leverage tinggi dan PnL/hari rendah, funding dapat membuat skor bersih menjadi negatif — strategi tidak menguntungkan dalam kenyataan meskipun PnL mentah positif.

Mengapa Ini Penting untuk Orkestrasi

PnL per waktu aktif adalah metrik utama untuk peringkat strategi dalam orkestrator. Ketika beberapa strategi bersaing untuk slot yang sama, yang dengan skor tertinggi (dengan mempertimbangkan penyesuaian kepercayaan) menang.

Dalam praktiknya, ini mengarah pada keputusan yang mengejutkan: strategi dengan PnL mentah yang "sederhana" tetapi waktu singkat dalam posisi sering mendapat prioritas atas strategi yang "mencolok" dengan PnL tinggi tetapi posisi panjang. Yang pertama menggunakan modal lebih efisien dalam portofolio yang terdiri dari puluhan strategi.

Wawasan kunci: satu-satunya metrik yang dapat diskalakan adalah PnL per hari aktif. PnL mentah tidak dapat diskalakan: Anda tidak dapat menjalankan strategi yang sama dua kali. Tetapi Anda dapat mengisi waktu idle dengan strategi lain — dan PnL per hari aktif secara akurat memprediksi berapa banyak yang akan Anda hasilkan dalam sebuah portofolio.

Kesimpulan

PnL tahunan mentah adalah metrik yang nyaman tetapi menipu. Metrik ini tidak memperhitungkan sumber daya terpenting trader — waktu selama modal bekerja.

Tiga poin penting:

  1. Hitung PnL per hari aktif. Strategi dengan +27% selama 38 hari dalam posisi = +0,72%/hari. Strategi dengan +300% selama 338 hari = +0,89%/hari. Perbedaannya bukan 11x, tetapi 1,2x.

  2. Pertimbangkan fill_efficiency. Dalam portofolio pasangan kripto yang berkorelasi, fill_efficiency lebih rendah dari yang terlihat. 10 pasangan tidak sama dengan diversifikasi 10x. Dengan correlation_factor = 3, jumlah pasangan yang efektif hanya ~3.

  3. Hukum sampel kecil. 38 trade dengan rata-rata +0,71% memberikan CI dari +0,14% hingga +1,28%. 418 trade dengan +0,72% memberikan CI dari +0,62% hingga +0,82%. Strategi kedua lebih andal, meskipun rata-ratanya hampir identik.

Metrik PnL per waktu aktif tidak menggantikan PnL@MaxLev — metrik ini melengkapinya dengan menambahkan dimensi efisiensi penggunaan modal. Untuk strategi tunggal, PnL@ML sudah cukup. Untuk portofolio strategi, PnL per waktu aktif sangat penting.


Referensi

  1. Lopez de Prado — Advances in Financial Machine Learning: The Sharpe Ratio
  2. Pardo, R. — The Evaluation and Optimization of Trading Strategies
  3. Bailey, D.H. & Lopez de Prado — The Deflated Sharpe Ratio
  4. Kelly, J.L. — A New Interpretation of Information Rate (1956)
  5. Quantopian — Lecture on Strategy Evaluation Metrics
  6. Ernest Chan — Algorithmic Trading: Portfolio Management

Kutipan

@article{soloviov2026pnlactivetime,
  author = {Soloviov, Eugen},
  title = {PnL by Active Time: The Metric That Changes Strategy Rankings},
  year = {2026},
  url = {https://marketmaker.cc/id/blog/post/pnl-active-time-metric},
  version = {0.1.0},
  description = {Why raw annual PnL is a poor metric for comparing strategies with different trading time. How to calculate effective return, why you need fill\_efficiency, and why a strategy with 27\% PnL can outperform one with 300\%.}
}
Penafian: Informasi yang disediakan dalam artikel ini hanya untuk tujuan edukasi dan informasi serta tidak merupakan nasihat keuangan, investasi, atau trading. Trading mata uang kripto mengandung risiko kerugian yang signifikan.

Penulis

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Selangkah Lebih Maju dari Pasar

Berlangganan newsletter kami untuk wawasan AI trading eksklusif, analisis pasar, dan pembaruan platform.

Kami menghormati privasi Anda. Berhenti berlangganan kapan saja.