सक्रिय समय के अनुसार PnL: वह मेट्रिक जो रणनीति रैंकिंग बदल देता है

अलग-अलग ट्रेडिंग समय वाली रणनीतियों की तुलना के लिए कच्चा वार्षिक PnL एक कमजोर मेट्रिक क्यों है। प्रभावी रिटर्न कैसे गणना करें, fill_efficiency की आवश्यकता क्यों है, और 27% PnL वाली रणनीति 300% वाली से बेहतर क्यों हो सकती है।

आपके पास दो रणनीतियाँ हैं। पहली: PnL +300%, 418 ट्रेड, पोजीशन 45% समय खुली रहती है। दूसरी: PnL +27%, 38 ट्रेड, पोजीशन 5% समय खुली रहती है। कौन सी बेहतर है?

अगर आपने पहली चुनी — तो आपका जवाब गलत है। यहाँ बताया गया है क्यों।

कच्चे PnL की समस्या

कच्चा PnL — पूरे बैकटेस्ट अवधि में कुल रिटर्न — इस बात का ध्यान नहीं रखता कि रणनीति कितने प्रतिशत समय पोजीशन में थी। +300% और 45% ट्रेडिंग समय वाली रणनीति आपकी पूंजी का उपयोग आधे से भी कम समय करती है। बाकी 55% समय, पूंजी निष्क्रिय पड़ी रहती है।

+27% और 5% ट्रेडिंग समय वाली रणनीति पूंजी का उपयोग केवल 5% समय करती है — लेकिन बाकी 95% समय अन्य रणनीतियों के लिए उपलब्ध होता है।

अगर आप एक ऑर्केस्ट्रेटर के जरिए रणनीतियों का पोर्टफोलियो चलाते हैं, तो एक रणनीति का निष्क्रिय समय दूसरी रणनीतियों द्वारा भर दिया जाता है। तब मुख्य मेट्रिक यह नहीं रह जाता कि एक रणनीति ने एक साल में कितना कमाया, बल्कि यह कि वह सक्रिय समय की प्रति इकाई कितना कमाती है।

प्रभावी रिटर्न फॉर्मूला

PnL per active day strategy ranking comparison

बुनियादी गणना

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}

जहाँ:

  • Active days — पोजीशन में बिताया गया कुल समय (दिनों में)
  • fill_efficiency — वह अनुपात जिसे ऑर्केस्ट्रेटर सिग्नल्स से भर सकता है (0...1)
def pnl_per_active_time(
    total_pnl: float,        # total PnL, %
    test_period_days: int,    # backtest length, days
    trading_time_pct: float,  # fraction of active time, 0..1
    fill_efficiency: float = 0.80,  # slot fill efficiency
) -> 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,
    }

वास्तविक रणनीतियों की पुनर्गणना

अवधि: 750 दिन (25 महीने), fill_efficiency = 0.80:

रणनीतिPnLट्रेडिंग समयActive daysPnL/दिनवार्षिक (x0.8)
रणनीति C+300%45%337.50.89%/d259%
रणनीति B+27%5%37.50.72%/d210%
रणनीति A+58%15%112.50.51%/d150%

कच्चे PnL के अनुसार: रणनीति C (300%) >> रणनीति A (58%) >> रणनीति B (27%)। प्रभावी रिटर्न के अनुसार: रणनीति C (259%) > रणनीति B (210%) > रणनीति A (150%)।

27% PnL वाली रणनीति B, 300% PnL वाली रणनीति C के बराबर निकलती है — क्योंकि वह 9 गुना कम सक्रिय समय में उतना ही पैसा कमाती है। बाकी 95% समय को अन्य रणनीतियों से भरा जा सकता है।

रैखिक बनाम चक्रवृद्धि एक्सट्रापोलेशन

ऊपर दिया गया फॉर्मूला रैखिक (linear) है। यह सरल और अधिक रूढ़िवादी है। चक्रवृद्धि (compound) वेरिएंट मुनाफे के पुनर्निवेश को ध्यान में रखता है:

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

Annualizedcompound=(1+Daily return)365×fill_eff−1\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

# Strategy B: 27% over 37.5 active days
b_compound = compound_annualized(27, 37.5)
# daily_return = 1.27^(1/37.5) - 1 = 0.636%
# annualized = (1.00636)^(292) - 1 = 540%

# Strategy C: 300% over 337.5 active days
c_compound = compound_annualized(300, 337.5)
# daily_return = 4.0^(1/337.5) - 1 = 0.412%
# annualized = (1.00412)^(292) - 1 = 231%

चक्रवृद्धि एक्सट्रापोलेशन के साथ, रणनीति B रणनीति C को पीछे छोड़ देती है: 540% बनाम 231%। रैंकिंग उलट जाती है।

सिफारिश: रैंकिंग के लिए रैखिक एक्सट्रापोलेशन का उपयोग करें। यह अधिक रूढ़िवादी है और ट्रेडों की छोटी संख्या पर ओवरफिटिंग को पुरस्कृत करने की संभावना कम रखता है।

जाल: ट्रेडों की छोटी संख्या

38 ट्रेड और PnL/दिन = 0.72% वाली रणनीति B आकर्षक दिखती है। लेकिन 38 ट्रेड सांख्यिकीय रूप से एक कमजोर नमूना है। उच्च PnL/दिन एक भाग्यशाली संयोग का परिणाम हो सकता है।

विश्वास-समायोजित स्कोरिंग

हम छोटे नमूनों को दंडित करने के लिए t-डिस्ट्रीब्यूशन का उपयोग करते हैं:

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

जहाँ rˉ\bar{r} प्रति ट्रेड औसत रिटर्न है, ss मानक विचलन है, nn ट्रेडों की संख्या है, tα/2,n−1t_{\alpha/2, n-1} 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)

    # Lower bound of confidence interval
    alpha = 1 - confidence
    t_crit = st.t.ppf(1 - alpha / 2, df=n - 1)
    ci_lower = mean_ret - t_crit * se

    # Confidence factor: 0..1
    # If CI includes 0 -> factor ~ 0
    # If CI is far from 0 -> factor -> 1
    if mean_ret <= 0:
        confidence_factor = 0
    else:
        confidence_factor = max(0, ci_lower / mean_ret)

    # Active time calculation
    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

    # MaxLev
    # ... equity curve -> max drawdown -> MaxLev

    score = annualized * max_leverage * confidence_factor

    return {
        "score": score,
        "annualized": annualized,
        "confidence_factor": confidence_factor,
        "ci_lower": ci_lower,
        "n_trades": n,
    }

विश्वास समायोजन का प्रभाव

रणनीतिट्रेडऔसत रिटर्नSECI निचलाविश्वास कारकसमायोजित स्कोर
रणनीति B380.71%0.28%0.14%0.20210% x 0.20 = 42%
रणनीति C4180.72%0.05%0.62%0.86259% x 0.86 = 223%
रणनीति A4910.12%0.02%0.08%0.67150% x 0.67 = 100%

विश्वास समायोजन के बाद, रणनीति C आत्मविश्वास से आगे निकल जाती है: 418 ट्रेड एक संकीर्ण CI और उच्च विश्वास कारक देते हैं। 38 ट्रेड वाली रणनीति B को दंडित किया जाता है — उसका "शानदार" प्रदर्शन वैरिएंस का परिणाम हो सकता है।

fill_efficiency: इसे कहाँ से प्राप्त करें

Fill efficiency and orchestrator slot allocation

fill_efficiency पैरामीटर इस सवाल का जवाब देता है: "ऑर्केस्ट्रेटर कितने प्रतिशत समय पूंजी को काम पर रख सकता है?"

विकल्प 1: निश्चित स्थिरांक

सबसे सरल तरीका: सभी रणनीतियों के लिए fill_efficiency = 0.80। यह मानता है कि ऑर्केस्ट्रेटर निष्क्रिय समय का 80% अन्य रणनीतियों/जोड़ियों से भर देता है।

फायदा: सभी के लिए समान, तुलना करना आसान। नुकसान: रणनीतियों के बीच सहसंबंध को ध्यान में नहीं रखता।

विकल्प 2: विश्लेषणात्मक अनुमान

यदि आपके पास NN जोड़ियाँ हैं, प्रत्येक p%p\% समय सक्रिय, तो कम से कम एक के सक्रिय होने की संभावना:

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

लेकिन क्रिप्टोकरेंसी अत्यधिक सहसंबद्ध होती हैं — BTC, ETH, SOL और बाकी सबको साथ खींचता है। स्वतंत्र जोड़ियों की प्रभावी संख्या:

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 number of active pairs
    expected_active = effective_n * trading_time_pct
    utilization = min(expected_active, max_slots) / max_slots

    return min(p_at_least_one, utilization)

# Strategy B: 5% active, 10 pairs, correlation_factor=3
eff_b = estimate_fill_efficiency(0.05, 10, 3.0)
# effective_N = 3.3, P(>=1) = 1 - 0.95^3.3 = 15.6%

# Strategy C: 45% active, 10 pairs
eff_c = estimate_fill_efficiency(0.45, 10, 3.0)
# effective_N = 3.3, P(>=1) = 1 - 0.55^3.3 = 85.2%

5% सक्रियता और 10 सहसंबद्ध जोड़ियों वाली रणनीति B के लिए, fill_efficiency केवल ~16% है। यह प्रभावी रिटर्न को नाटकीय रूप से कम कर देता है।

विकल्प 3: डेटा से सिमुलेशन

सबसे सटीक तरीका है सभी रणनीतियों को सभी जोड़ियों पर चलाना और वास्तविक स्लॉट उपयोग की गणना करना:

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.
    """
    # Build timeline: for each minute — how many positions are active
    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

    # Utilization: min(active, max_slots) / max_slots
    capped = np.minimum(timeline, max_slots)
    fill_efficiency = np.mean(capped) / max_slots

    return fill_efficiency

अंतिम रैंकिंग फॉर्मूला

सभी घटकों को मिलाकर:

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])

    # 1. PnL per active day
    total_pnl = np.sum(returns)
    active_days = np.sum(hold_hours) / 24
    pnl_per_day = total_pnl / active_days

    # 2. MaxLev
    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)))

    # 3. Funding cost per active day
    funding_daily = funding_rate * 3 * max_lev * 100  # in %
    net_pnl_per_day = pnl_per_day - funding_daily

    # 4. Annualize with fill efficiency
    annualized = net_pnl_per_day * 365 * fill_efficiency

    # 5. Confidence adjustment
    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)

    # 6. Final score
    score = annualized * max_lev * conf_factor

    return score

श्रृंखला की अन्य मेट्रिक्स से संबंध

यह मेट्रिक पिछले लेखों के टूल्स की जगह नहीं लेता, बल्कि उन्हें पूरक बनाता है:

  • Loss-Profit Asymmetry: अधिकतम ड्रॉडाउन MaxLev निर्धारित करता है, जो स्कोर फॉर्मूला में जाता है। ड्रॉडाउन जितना गहरा होगा, स्कोर उतना कम होगा — गैर-रैखिक रूप से, रिकवरी असमानता के कारण।

  • Monte Carlo bootstrap: बूटस्ट्रैप से प्राप्त विश्वास अंतराल t-डिस्ट्रीब्यूशन की तुलना में विश्वास कारक का अधिक सटीक अनुमान देते हैं। आप t-डिस्ट्रीब्यूशन के CI को बूटस्ट्रैप के 5वें पर्सेंटाइल से बदल सकते हैं।

  • Funding rates: फंडिंग लागत को प्रति सक्रिय दिन PnL से घटाया जाता है। उच्च लीवरेज और कम PnL/दिन के साथ, फंडिंग नेट स्कोर को नकारात्मक बना सकती है — रणनीति वास्तव में एक सकारात्मक कच्चे PnL के बावजूद लाभहीन होती है।

यह ऑर्केस्ट्रेशन के लिए क्यों महत्वपूर्ण है

सक्रिय समय के अनुसार PnL ऑर्केस्ट्रेटर में रणनीतियों को रैंक करने के लिए मुख्य मेट्रिक है। जब कई रणनीतियाँ एक ही स्लॉट के लिए प्रतिस्पर्धा करती हैं, तो सबसे उच्च स्कोर (विश्वास समायोजन को ध्यान में रखते हुए) वाली रणनीति जीतती है।

व्यवहार में, यह आश्चर्यजनक निर्णयों की ओर ले जाता है: "मामूली" कच्चे PnL लेकिन पोजीशन में कम समय वाली रणनीतियों को अक्सर उच्च PnL लेकिन लंबी पोजीशन वाली "आकर्षक" रणनीतियों पर प्राथमिकता मिलती है। पहली वाली दर्जनों रणनीतियों के पोर्टफोलियो में पूंजी का अधिक कुशलता से उपयोग करती हैं।

मुख्य अंतर्दृष्टि: एकमात्र मेट्रिक जो स्केल करता है वह है प्रति सक्रिय दिन PnL। कच्चा PnL स्केल नहीं करता: आप एक ही रणनीति को दो बार नहीं चला सकते। लेकिन आप निष्क्रिय समय को अन्य रणनीतियों से भर सकते हैं — और प्रति सक्रिय दिन PnL सटीक रूप से भविष्यवाणी करता है कि आप एक पोर्टफोलियो में कितना कमाएंगे।

निष्कर्ष

कच्चा वार्षिक PnL एक सुविधाजनक लेकिन भ्रामक मेट्रिक है। यह ट्रेडर के सबसे महत्वपूर्ण संसाधन — उस समय को ध्यान में नहीं रखता जिसके दौरान पूंजी काम कर रही होती है।

तीन मुख्य निष्कर्ष:

  1. प्रति सक्रिय दिन PnL की गणना करें। पोजीशन में 38 दिनों में +27% वाली रणनीति = +0.72%/दिन। 338 दिनों में +300% वाली रणनीति = +0.89%/दिन। अंतर 11 गुना नहीं, बल्कि 1.2 गुना है।

  2. fill_efficiency को ध्यान में रखें। सहसंबद्ध क्रिप्टो जोड़ियों के पोर्टफोलियो में, fill_efficiency दिखने से कम होता है। 10 जोड़ियाँ 10 गुना विविधीकरण के बराबर नहीं होतीं। correlation_factor = 3 के साथ, जोड़ियों की प्रभावी संख्या केवल ~3 होती है।

  3. छोटे नमूनों को दंडित करें। +0.71% के औसत के साथ 38 ट्रेड +0.14% से +1.28% तक का CI देते हैं। +0.72% के साथ 418 ट्रेड +0.62% से +0.82% तक का CI देते हैं। दूसरी रणनीति अधिक विश्वसनीय है, भले ही औसत लगभग समान हों।

सक्रिय समय के अनुसार PnL मेट्रिक PnL@MaxLev की जगह नहीं लेता — यह पूंजी उपयोग दक्षता का आयाम जोड़कर इसे पूरक बनाता है। एक अकेली रणनीति के लिए, PnL@ML पर्याप्त है। रणनीतियों के पोर्टफोलियो के लिए, सक्रिय समय के अनुसार PnL आवश्यक है।


संदर्भ

  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

उद्धरण

@article{soloviov2026pnlactivetime,
  author = {Soloviov, Eugen},
  title = {PnL by Active Time: The Metric That Changes Strategy Rankings},
  year = {2026},
  url = {https://marketmaker.cc/ru/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\%.}
}