الربح والخسارة حسب الوقت النشط: المقياس الذي يغيّر ترتيب الاستراتيجيات
لديك استراتيجيتان. الأولى: PnL +300%، 418 صفقة، المركز مفتوح 45% من الوقت. الثانية: PnL +27%، 38 صفقة، المركز مفتوح 5% من الوقت. أيهما أفضل؟
إذا اخترت الأولى — فإجابتك خاطئة. إليك السبب.
مشكلة PnL الخام
PnL الخام — إجمالي العائد على كامل فترة الاختبار الرجعي — لا يأخذ في الاعتبار نسبة الوقت التي كانت الاستراتيجية فيها في مركز. استراتيجية بنسبة +300% ووقت تداول 45% تستخدم رأس مالك أقل من نصف الوقت. خلال الـ 55% المتبقية، يبقى رأس المال عاطلاً.
استراتيجية بنسبة +27% ووقت تداول 5% تستخدم رأس المال 5% فقط من الوقت — لكن الـ 95% المتبقية متاحة لاستراتيجيات أخرى.
إذا شغّلت محفظة استراتيجيات عبر مُنظِّم (orchestrator)، فإن وقت خمول استراتيجية يُملأ بأخرى. المقياس الرئيسي حينها ليس كم كسبت الاستراتيجية خلال سنة، بل كم تكسب لكل وحدة زمن نشطة.
معادلة العائد الفعلي

الحساب الأساسي
حيث:
- 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 | وقت التداول | الأيام النشطة | PnL/يوم | معدل سنوي (x0.8) |
|---|---|---|---|---|---|
| الاستراتيجية C | +300% | 45% | 337.5 | 0.89%/يوم | 259% |
| الاستراتيجية B | +27% | 5% | 37.5 | 0.72%/يوم | 210% |
| الاستراتيجية A | +58% | 15% | 112.5 | 0.51%/يوم | 150% |
حسب PnL الخام: الاستراتيجية C (300%) >> الاستراتيجية A (58%) >> الاستراتيجية B (27%). حسب العائد الفعلي: الاستراتيجية C (259%) > الاستراتيجية B (210%) > الاستراتيجية A (150%).
الاستراتيجية B بنسبة PnL 27% تبين أنها مماثلة للاستراتيجية C بنسبة PnL 300% — لأنها تكسب نفس المال في تُسع الوقت النشط. الـ 95% المتبقية يمكن ملؤها باستراتيجيات أخرى.
الاستقراء الخطي مقابل المركب
المعادلة أعلاه خطية. أبسط وأكثر تحفظاً. البديل المركب يأخذ في الاعتبار إعادة استثمار الأرباح:
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)
مع الاستقراء المركب، الاستراتيجية B تتجاوز الاستراتيجية C: 540% مقابل 231%. الترتيب ينعكس.
التوصية: استخدم الاستقراء الخطي للترتيب. أكثر تحفظاً وأقل عرضة لمكافأة التكيف المفرط على عدد قليل من الصفقات.
الفخ: عدد صفقات قليل
الاستراتيجية B بـ 38 صفقة وPnL/يوم = 0.72% تبدو جذابة. لكن 38 صفقة عينة ضعيفة إحصائياً. PnL/يوم المرتفع قد يكون نتيجة صدفة محظوظة.
التسجيل المعدّل بالثقة
نستخدم توزيع t لمعاقبة العينات الصغيرة:
حيث متوسط العائد لكل صفقة، الانحراف المعياري، عدد الصفقات، مئوية توزيع 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,
}
تأثير التعديل بالثقة
| الاستراتيجية | الصفقات | متوسط العائد | SE | الحد الأدنى CI | عامل الثقة | النتيجة المعدّلة |
|---|---|---|---|---|---|---|
| الاستراتيجية B | 38 | 0.71% | 0.28% | 0.14% | 0.20 | 210% x 0.20 = 42% |
| الاستراتيجية C | 418 | 0.72% | 0.05% | 0.62% | 0.86 | 259% x 0.86 = 223% |
| الاستراتيجية A | 491 | 0.12% | 0.02% | 0.08% | 0.67 | 150% x 0.67 = 100% |
بعد التعديل بالثقة، الاستراتيجية C تتصدر بثقة: 418 صفقة تعطي CI ضيق وعامل ثقة مرتفع. الاستراتيجية B بـ 38 صفقة تُعاقَب — أداؤها "الرائع" قد يكون نتيجة التباين.
fill_efficiency: من أين نحصل عليه

معامل fill_efficiency يجيب على السؤال: "ما نسبة الوقت التي يستطيع المُنظِّم إبقاء رأس المال فيها عاملاً؟"
الخيار 1: ثابت محدد
أبسط نهج: fill_efficiency = 0.80 لجميع الاستراتيجيات. يفترض أن المُنظِّم يستغل 80% من وقت الخمول باستراتيجيات/أزواج أخرى.
الميزة: متطابق للجميع، سهل المقارنة. العيب: لا يأخذ في الاعتبار الارتباط بين الاستراتيجيات.
الخيار 2: تقدير تحليلي
إذا كان لديك أزواج، كل منها نشط من الوقت، فإن احتمال أن يكون واحد على الأقل نشطاً:
لكن العملات المشفرة مترابطة بشدة — BTC يسحب ETH وSOL والباقي معه. العدد الفعلي للأزواج المستقلة:
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)
للاستراتيجية B بنشاط 5% و10 أزواج مترابطة، 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.
"""
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
معادلة الترتيب النهائية
الجمع بين كل المكونات:
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
الارتباط بمقاييس أخرى في السلسلة
هذا المقياس لا يحل محل بل يُكمّل أدوات المقالات السابقة:
-
عدم تماثل الخسارة-الربح: الحد الأقصى للتراجع يحدد MaxLev، الذي يدخل في معادلة النتيجة. كلما كان التراجع أعمق، كلما انخفضت النتيجة — بشكل غير خطي، بسبب عدم تماثل التعافي.
-
محاكاة مونتي كارلو بوتستراب: فترات الثقة من البوتستراب توفر تقديراً أكثر دقة لعامل الثقة من توزيع t. يمكنك استبدال CI من توزيع t بالمئوية الخامسة من البوتستراب.
-
معدلات التمويل: تكاليف التمويل تُطرح من PnL لكل يوم نشط. مع رافعة مالية عالية وPnL/يوم منخفض، يمكن للتمويل أن يجعل النتيجة الصافية سلبية — الاستراتيجية غير مربحة فعلياً رغم PnL خام إيجابي.
لماذا هذا مهم للتنظيم
PnL لكل وقت نشط هو المقياس الرئيسي لترتيب الاستراتيجيات في المُنظِّم. عندما تتنافس عدة استراتيجيات على نفس الفتحة، تفوز الاستراتيجية ذات أعلى نتيجة (مع مراعاة تعديل الثقة).
عملياً، هذا يؤدي إلى قرارات مفاجئة: استراتيجيات بـ PnL خام "متواضع" لكن وقت قصير في المركز غالباً ما تحصل على أولوية على استراتيجيات "لامعة" بـ PnL مرتفع لكن مراكز طويلة. الأولى تستخدم رأس المال بكفاءة أكبر في محفظة من عشرات الاستراتيجيات.
الرؤية الرئيسية: المقياس الوحيد القابل للتوسع هو PnL لكل يوم نشط. PnL الخام لا يتوسع: لا يمكنك تشغيل نفس الاستراتيجية مرتين. لكن يمكنك ملء وقت الخمول باستراتيجيات أخرى — وPnL لكل يوم نشط يتنبأ بدقة بكم ستكسب في محفظة.
الخاتمة
PnL السنوي الخام مقياس مريح لكنه مخادع. لا يأخذ في الاعتبار أهم مورد للمتداول — الوقت الذي يعمل فيه رأس المال.
ثلاث نقاط رئيسية:
-
احسب PnL لكل يوم نشط. استراتيجية بنسبة +27% خلال 38 يوم في المركز = +0.72%/يوم. استراتيجية بنسبة +300% خلال 338 يوم = +0.89%/يوم. الفرق ليس 11 ضعفاً بل 1.2 ضعف.
-
خذ fill_efficiency في الاعتبار. في محفظة أزواج عملات مشفرة مترابطة، fill_efficiency أقل مما يبدو. 10 أزواج لا تساوي 10 أضعاف التنويع. مع correlation_factor = 3، العدد الفعلي للأزواج حوالي 3 فقط.
-
عاقب العينات الصغيرة. 38 صفقة بمتوسط +0.71% تعطي CI من +0.14% إلى +1.28%. 418 صفقة بمتوسط +0.72% تعطي CI من +0.62% إلى +0.82%. الاستراتيجية الثانية أكثر موثوقية، حتى لو كانت المتوسطات متقاربة تقريباً.
مقياس PnL لكل وقت نشط لا يحل محل PnL@MaxLev — بل يُكمّله بإضافة بُعد كفاءة استخدام رأس المال. لاستراتيجية واحدة، PnL@ML كافٍ. لمحفظة استراتيجيات، PnL لكل وقت نشط ضروري.
المراجع
- 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
Citation
@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\%.}
}
MarketMaker.cc Team
البحوث والاستراتيجيات الكمية