Белсенді уақыт бойынша 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%/к259%
Стратегия B+27%5%37.50.72%/к210%
Стратегия A+58%15%112.50.51%/к150%

Жалпы PnL бойынша: Стратегия C (300%) >> Стратегия A (58%) >> Стратегия B (27%). Тиімді табыстылық бойынша: Стратегия C (259%) > Стратегия B (210%) > Стратегия A (150%).

27% PnL бар Стратегия B 300% PnL бар Стратегия C-ге пара-пар болып шығады — себебі ол сол ақшаны 9 есе аз белсенді уақытта табады. Қалған 95% уақытты басқа стратегиялармен толтыруға болады.

Сызықтық және құрама экстраполяция

Жоғарыдағы формула сызықтық. Ол қарапайым әрі консервативті. Құрама нұсқа пайданы қайта инвестициялауды есепке алады:

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%. Рейтинг керісінше өзгереді.

Ұсыныс: рейтингтеу үшін сызықтық экстраполяцияны қолданыңыз. Ол консервативті әрі мәмілелер саны аз болғанда overfitting-ті марапаттауға аз бейім.

Тұзақ: мәмілелер саны аз

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\%.}
}