← Maqolalarga qaytish
March 18, 2026
5 daqiqa o'qish

Faol vaqt bo'yicha PnL: strategiyalar reytingini o'zgartiradigan ko'rsatkich

Faol vaqt bo'yicha PnL: strategiyalar reytingini o'zgartiradigan ko'rsatkich
#algotrading
#backtest
#metrics
#PnL
#orchestration
#portfolio
#risk management

Sizda ikkita strategiya bor. Birinchisi: PnL +300%, 418 bitim, pozitsiya vaqtning 45%-ida ochiq. Ikkinchisi: PnL +27%, 38 bitim, pozitsiya vaqtning 5%-ida ochiq. Qaysi biri yaxshiroq?

Agar siz birinchisini tanlagan bo'lsangiz — javobingiz noto'g'ri. Mana sababi.

Xom PnL bilan bog'liq muammo

Xom PnL — butun bektest davridagi umumiy daromad — strategiya vaqtning qancha qismida pozitsiyada bo'lganini hisobga olmaydi. +300% va 45% savdo vaqtiga ega strategiya kapitalingizdan yarmidan kamroq vaqt foydalanadi. Qolgan 55% vaqt davomida kapital bo'sh turadi.

+27% va 5% savdo vaqtiga ega strategiya kapitaldan atigi 5% vaqt foydalanadi — ammo qolgan 95% vaqt boshqa strategiyalar uchun mavjud bo'ladi.

Agar siz strategiyalar portfelini orkestrator orqali boshqarsangiz, bir strategiyaning bo'sh vaqti boshqalari tomonidan to'ldiriladi. Shunda asosiy ko'rsatkich strategiya bir yilda qancha topgani emas, balki faol vaqtning bir birligiga qancha topishi bo'ladi.

Samarali daromad formulasi

PnL per active day strategy ranking comparison

Asosiy hisoblash

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}

bu yerda:

  • Active days — pozitsiyalarda o'tkazilgan umumiy vaqt (kunlarda)
  • fill_efficiency — orkestrator signallar bilan to'ldira oladigan vaqt ulushi (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,
    }

Haqiqiy strategiyalarni qayta hisoblash

Davr: 750 kun (25 oy), fill_efficiency = 0.80:

Strategiya PnL Savdo vaqti Active days PnL/kun Yillik (x0.8)
Strategiya C +300% 45% 337.5 0.89%/k 259%
Strategiya B +27% 5% 37.5 0.72%/k 210%
Strategiya A +58% 15% 112.5 0.51%/k 150%

Xom PnL bo'yicha: Strategiya C (300%) >> Strategiya A (58%) >> Strategiya B (27%). Samarali daromad bo'yicha: Strategiya C (259%) > Strategiya B (210%) > Strategiya A (150%).

27% PnL bilan Strategiya B 300% PnL bilan Strategiya C bilan taqqoslanadigan bo'lib chiqadi — chunki u xuddi shu pulni 9 marta kamroq faol vaqtda topadi. Qolgan 95% vaqtni boshqa strategiyalar bilan to'ldirish mumkin.

Chiziqli va murakkab ekstrapolatsiya

Yuqoridagi formula chiziqli. U soddaroq va konservativroq. Murakkab varianti foydani qayta investitsiya qilishni hisobga oladi:

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)

Murakkab ekstrapolatsiya bilan Strategiya B Strategiya C-ni ortda qoldiradi: 540% ga qarshi 231%. Reyting teskari bo'lib qoladi.

Tavsiya: reyting uchun chiziqli ekstrapolatsiyadan foydalaning. U konservativroq va bitimlar soni kam bo'lganda overfitting-ni mukofotlashga kamroq moyil.

Tuzoq: bitimlar sonining kamligi

38 bitimi va PnL/kun = 0.72% bo'lgan Strategiya B jozibali ko'rinadi. Ammo 38 bitim statistik jihatdan zaif namuna hisoblanadi. Yuqori PnL/kun omadli tasodifning natijasi bo'lishi mumkin.

Ishonch darajasiga moslashtirilgan baholash

Kichik namunalarni jazolash uchun t-taqsimotdan foydalanamiz:

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

bu yerda rˉ\bar{r} — bitim boshiga o'rtacha daromad, ss — standart og'ish, nn — bitimlar soni, tα/2,n1t_{\alpha/2, n-1} — t-taqsimotning kvantili.

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,
    }

Ishonch darajasini moslashtirishning ta'siri

Strategiya Bitimlar O'rt. daromad SE CI quyi Ishonch koef. Moslashtirilgan ball
Strategiya B 38 0.71% 0.28% 0.14% 0.20 210% x 0.20 = 42%
Strategiya C 418 0.72% 0.05% 0.62% 0.86 259% x 0.86 = 223%
Strategiya A 491 0.12% 0.02% 0.08% 0.67 150% x 0.67 = 100%

Ishonch darajasini moslashtirgandan so'ng, Strategiya C ishonchli tarzda yetakchilik qiladi: 418 bitim tor CI va yuqori ishonch koeffitsientini beradi. 38 bitimga ega Strategiya B jazolanadi — uning "ajoyib" natijasi dispersiya natijasi bo'lishi mumkin.

fill_efficiency: uni qayerdan olish mumkin

Fill efficiency and orchestrator slot allocation

fill_efficiency parametri quyidagi savolga javob beradi: "Orkestrator kapitalni qancha vaqt ishlab turishini ta'minlay oladi?"

1-variant: qat'iy konstanta

Eng sodda yondashuv: barcha strategiyalar uchun fill_efficiency = 0.80. Orkestrator bo'sh vaqtning 80%-ini boshqa strategiyalar/juftliklar bilan to'ldiradi deb faraz qilinadi.

Afzalligi: barchasi uchun bir xil, solishtirish oson. Kamchiligi: strategiyalar orasidagi korrelyatsiyani hisobga olmaydi.

2-variant: analitik baholash

Agar sizda NN ta juftlik bo'lsa, har biri p%p\% vaqt faol bo'lsa, kamida bittasi faol bo'lish ehtimoli:

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

Ammo kriptovalyutalar yuqori darajada korrelyatsiyalangan — BTC ETH, SOL va qolganlarini o'zi bilan birga tortadi. Mustaqil juftliklarning samarali soni:

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)

5% faollik va 10 ta korrelyatsiyalangan juftlikka ega Strategiya B uchun fill_efficiency atigi ~16% ni tashkil qiladi. Bu samarali daromadni keskin kamaytiradi.

3-variant: ma'lumotlar asosida simulyatsiya

Eng aniq yondashuv — barcha strategiyalarni barcha juftliklarda ishga tushirish va haqiqiy slot ishlatilishini hisoblash:

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

Yakuniy reyting formulasi

Barcha komponentlarni birlashtirish:

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

Seriyaning boshqa ko'rsatkichlari bilan bog'liqligi

Bu ko'rsatkich oldingi maqolalardagi vositalarni almashtirmaydi, balki to'ldiradi:

  • Loss-Profit Asymmetry: maksimal drawdown MaxLev-ni belgilaydi, u ball formulasiga kiradi. Drawdown qanchalik chuqur bo'lsa, ball shunchalik past bo'ladi — tiklanish assimetriyasi tufayli chiziqli bo'lmagan tarzda.

  • Monte Carlo bootstrap: bootstrapdan olingan ishonch intervallari t-taqsimotga qaraganda ishonch koeffitsientining aniqroq bahosini beradi. t-taqsimotdagi CI-ni bootstrapning 5-protsentili bilan almashtirish mumkin.

  • Funding rates: funding xarajatlari faol kun boshiga PnL-dan chegiriladi. Yuqori leverage va past PnL/kun bilan funding sof ballni manfiy qilishi mumkin — strategiya musbat xom PnL-ga qaramay aslida foydasiz bo'ladi.

Bu orkestratsiya uchun nima uchun muhim

Faol vaqt bo'yicha PnL — orkestratorda strategiyalarni reytinglash uchun asosiy ko'rsatkich. Bir nechta strategiya bir xil slot uchun raqobatlashganda, eng yuqori ball (ishonch darajasini moslashtirishni hisobga olgan holda) g'olib chiqadi.

Amalda, bu kutilmagan qarorlarga olib keladi: "kamtarona" xom PnL-ga ega, lekin pozitsiyada qisqa vaqt bo'lgan strategiyalar ko'pincha yuqori PnL-ga, lekin uzoq pozitsiyalarga ega "jozibali" strategiyalardan ustunlikka ega bo'ladi. Birinchilari o'nlab strategiyalar portfelida kapitaldan samaraliroq foydalanadi.

Asosiy xulosa: masshtablanadigan yagona ko'rsatkich — faol kun boshiga PnL. Xom PnL masshtablanmaydi: siz bir xil strategiyani ikki marta ishga tushira olmaysiz. Ammo bo'sh vaqtni boshqa strategiyalar bilan to'ldirish mumkin — va faol kun boshiga PnL portfelda qancha topishingizni aniq bashorat qiladi.

Xulosa

Xom yillik PnL qulay, ammo aldamchi ko'rsatkich. U treyderning eng muhim resursi — kapital ishlaydigan vaqtni hisobga olmaydi.

Uchta asosiy xulosa:

  1. Faol kun boshiga PnL-ni hisoblang. Pozitsiyada 38 kun davomida +27% beradigan strategiya = +0.72%/kun. 338 kun davomida +300% beradigan strategiya = +0.89%/kun. Farq 11 marta emas, balki 1.2 marta.

  2. fill_efficiency-ni hisobga oling. Korrelyatsiyalangan kripto juftliklari portfelida fill_efficiency ko'rinishidan pastroq. 10 ta juftlik 10 martalik diversifikatsiyaga teng emas. correlation_factor = 3 bo'lganda, juftliklarning samarali soni atigi ~3 ni tashkil qiladi.

  3. Kichik namunalarni jazolang. O'rtacha +0.71% bilan 38 bitim +0.14% dan +1.28% gacha CI beradi. +0.72% bilan 418 bitim +0.62% dan +0.82% gacha CI beradi. O'rtacha qiymatlar deyarli bir xil bo'lsa-da, ikkinchi strategiya ishonchliroq.

Faol vaqt bo'yicha PnL ko'rsatkichi PnL@MaxLev-ni almashtirmaydi — u kapitaldan foydalanish samaradorligi o'lchamini qo'shish orqali uni to'ldiradi. Bitta strategiya uchun PnL@ML yetarli. Strategiyalar portfeli uchun faol vaqt bo'yicha PnL zarur.


Manbalar

  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

Iqtibos

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

Authors

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

Bozordan bir qadam oldinda bo'ling

Sun'iy intellekt savdo tahlillari, bozor tahlili va platforma yangiliklari uchun bizning xabarnomaga obuna bo'ling.

Biz sizning maxfiyligingizni hurmat qilamiz. Istalgan vaqtda obunadan chiqishingiz mumkin.