你有兩個策略。第一個:PnL +300%,418筆交易,持倉時間佔45%。第二個:PnL +27%,38筆交易,持倉時間佔5%。哪個更好?
如果你選了第一個——你答錯了。原因如下。
原始PnL的問題
原始PnL——整個回測期間的總收益率——沒有考慮策略在倉時間佔比。一個PnL +300%、交易時間45%的策略,使用你的資金不到一半時間。剩餘55%的時間,資金處於閒置狀態。
一個PnL +27%、交易時間5%的策略僅使用資金5%的時間——但剩餘95%的時間可以用於其他策略。
如果你通過編排器執行策略組合,一個策略的空閒時間會被其他策略填充。那麼關鍵指標就不是策略一年賺了多少,而是每單位活躍時間賺了多少。
有效收益率公式

基礎計算
其中:
- Active days — 持倉總時間(以天為單位)
- fill_efficiency — 編排器能用訊號填充的時間比例(0...1)
def pnl_per_active_time(
total_pnl: float, # 总PnL,%
test_period_days: int, # 回测长度,天数
trading_time_pct: float, # 活跃时间占比,0..1
fill_efficiency: float = 0.80, # 槽位填充效率
) -> dict:
"""
按活跃时间计算有效收益率。
"""
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) |
|---|---|---|---|---|---|
| Strategy C | +300% | 45% | 337.5 | 0.89%/天 | 259% |
| Strategy B | +27% | 5% | 37.5 | 0.72%/天 | 210% |
| Strategy A | +58% | 15% | 112.5 | 0.51%/天 | 150% |
按原始PnL排名:Strategy C (300%) >> Strategy A (58%) >> Strategy B (27%)。 按有效收益率排名:Strategy C (259%) > Strategy B (210%) > Strategy A (150%)。
PnL僅27%的Strategy B竟然與PnL 300%的Strategy C相當——因為它在少9倍的活躍時間內賺取了同樣的收益。剩餘95%的時間可以用其他策略填充。
線性與複合外推
上述公式是線性的。它更簡單也更保守。複合變體考慮了利潤再投資:
import numpy as np
def compound_annualized(total_pnl_pct, active_days, fill_efficiency=0.80):
"""复合外推。"""
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)
在複合外推下,Strategy B 超越了 Strategy C:540% vs 231%。排名完全反轉。
**建議:**使用線性外推進行排名。它更保守,不易獎勵少量交易中的過擬合。
陷阱:交易數量過少
Strategy 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:
"""
基于样本量调整的策略排名。
"""
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 = [...] # 每笔交易的持仓小时数
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下界 | 置信因子 | 調整後得分 |
|---|---|---|---|---|---|---|
| Strategy B | 38 | 0.71% | 0.28% | 0.14% | 0.20 | 210% x 0.20 = 42% |
| Strategy C | 418 | 0.72% | 0.05% | 0.62% | 0.86 | 259% x 0.86 = 223% |
| Strategy A | 491 | 0.12% | 0.02% | 0.08% | 0.67 | 150% x 0.67 = 100% |
經過置信度調整後,Strategy C穩居領先:418筆交易提供了窄置信區間和高置信因子。Strategy 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, # 加密货币——高相关性
max_slots: int = 10,
) -> float:
"""
fill_efficiency的解析估算。
Args:
trading_time_pct: 单个策略的活跃时间占比
n_pairs: 交易对数量
correlation_factor: 相关系数(1=独立,5=强相关)
max_slots: 最大同时持仓数
"""
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%、擁有10個相關交易對的Strategy 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:
"""
模拟编排器的实际槽位利用率。
"""
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:
"""
策略排名的最终得分。
考虑因素:
- 每活跃天PnL(资金使用效率)
- MaxLev(风险调整后的杠杆倍数)
- 置信度调整(小样本惩罚)
- 资金费率成本(杠杆下的实际成本)
"""
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 # 单位:%
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,而MaxLev是評分公式的組成部分。回撤越深,得分越低——由於恢復的不對稱性,這是非線性的。
-
**蒙特卡洛自助法:**自助法的置信區間比t分佈提供了更準確的置信因子估計。你可以用自助法的第5百分位數替代t分佈的CI。
-
**資金費率:**資金費率成本從每活躍天PnL中扣除。在高槓杆和低PnL/天的情況下,資金費率可能使淨得分為負——儘管原始PnL為正,策略實際上是虧損的。
為什麼這對編排很重要
每活躍時間PnL是編排器中策略排名的核心指標。當多個策略競爭同一個槽位時——經過置信度調整後得分最高的策略獲勝。
在實踐中,這導致了出人意料的決策:原始PnL"不起眼"但持倉時間短的策略,往往比PnL高但持倉時間長的"明星"策略獲得更高優先順序。前者在數十個策略組成的投資組合中更高效地使用資金。
關鍵洞察:唯一可以擴充的指標是每活躍天PnL。原始PnL無法擴充:你不能把同一個策略執行兩次。但你可以用其他策略填充空閒時間——而每活躍天PnL能準確預測你在投資組合中能賺多少。
結論
年度原始PnL是一個方便但具有誤導性的指標。它沒有考慮交易者最重要的資源——資金工作的時間。
三個要點:
-
**計算每活躍天PnL。**持倉38天獲得+27%的策略 = +0.72%/天。持倉338天獲得+300%的策略 = +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
引用
@article{soloviov2026pnlactivetime,
author = {Soloviov, Eugen},
title = {按活跃时间计算PnL:改变策略排名的指标},
year = {2026},
url = {https://marketmaker.cc/ru/blog/post/pnl-active-time-metric},
version = {0.1.0},
description = {为什么年度原始PnL不适合比较不同交易时间的策略。如何计算有效收益率,为什么需要fill\_efficiency,以及为什么27\% PnL的策略可能优于300\%的策略。}
}
Authors
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.