Appearance
策略绩效指标
为什么要用指标评价策略
完成了回测之后,你会得到一条净值曲线。仅凭肉眼观察净值曲线的走势来判断策略好坏是不够的——你需要一套量化指标来客观、系统地评价策略表现。
举个简单的例子:策略A在3年内赚了50%,策略B在3年内赚了30%。哪个策略更好?直觉上A更好,但如果A在这个过程中经历了40%的最大回撤(也就是说你的账户一度亏损40%),而B的最大回撤只有10%,很多投资者可能更愿意选择B,因为持有B时心理压力小得多。
所以评价策略不能只看收益,还要看风险、看稳定性、看效率。本章将系统介绍量化投资中最常用的绩效指标。
收益类指标
总收益率(Total Return)
定义:策略在整个回测期间的总收益比例。
公式:
总收益率 = (期末净值 - 期初净值) / 期初净值 × 100%代码实现:
python
def total_return(daily_values):
"""计算总收益率"""
return (daily_values[-1] / daily_values[0] - 1)参考标准:
- 总收益率本身没有绝对的"好"或"坏"标准,因为它与回测时间段、市场环境有关
- 如果回测期跨越了一个完整牛熊周期(通常3-5年),总收益率才有参考价值
- 应该与同期基准指数(如沪深300)对比,看策略是否跑赢了基准
注意:总收益率没有考虑时间因素。50%的总收益率,用1年实现和用10年实现,意义完全不同。
年化收益率(Annualized Return)
定义:将策略的总收益率折算为按年计算的平均收益率。
公式:
年化收益率 = (期末净值 / 期初净值) ^ (252 / 交易日数) - 1其中252是A股大约的年度交易日数。
代码实现:
python
import numpy as np
def annualized_return(daily_values, trading_days_per_year=252):
"""计算年化收益率"""
total_days = len(daily_values)
if total_days < 2:
return 0.0
total_ret = daily_values[-1] / daily_values[0]
return total_ret ** (trading_days_per_year / total_days) - 1参考标准:
| 年化收益率 | 评价 |
|---|---|
| < 5% | 不如买银行理财,策略意义不大 |
| 5% ~ 10% | 一般,需要看风险是否匹配 |
| 10% ~ 20% | 不错,多数优秀基金经理的水平 |
| 20% ~ 30% | 很好,顶尖基金经理水平 |
| > 30% | 非常优秀,但需警惕过拟合或策略容量问题 |
注意:年化收益率受回测时段影响很大。如果回测恰好覆盖了一轮大牛市,年化收益率会很高;反之则可能很低。因此,年化收益率需要结合市场环境和基准指数来解读。
风险类指标
最大回撤(Maximum Drawdown)
定义:在回测期间内,账户净值从最高点跌至最低点的最大跌幅。最大回撤衡量的是投资者在最坏情况下可能面临的最大亏损。
公式:
最大回撤 = max(1 - 第i天的净值 / 第i天之前的历史最高净值)代码实现:
python
def max_drawdown(daily_values):
"""计算最大回撤"""
peak = daily_values[0]
max_dd = 0.0
for value in daily_values:
if value > peak:
peak = value
dd = (peak - value) / peak
if dd > max_dd:
max_dd = dd
return max_dd
# 使用numpy的向量化实现(更快)
def max_drawdown_vectorized(daily_values):
"""向量化计算最大回撤"""
arr = np.array(daily_values)
peak = np.maximum.accumulate(arr)
drawdown = (peak - arr) / peak
return drawdown.max()参考标准:
| 最大回撤 | 评价 |
|---|---|
| < 10% | 非常稳健,适合保守型投资者 |
| 10% ~ 20% | 可接受,多数股票型基金的水平 |
| 20% ~ 30% | 较大,需要较强的心理承受能力 |
| 30% ~ 50% | 很大,需要评估是否值得承担 |
| > 50% | 极端风险,策略可能存在严重问题 |
最大回撤的意义在于:如果你的策略最大回撤是40%,那么在实盘运行中你需要做好准备——你的账户可能在某段时间亏损40%。你是否能在那种情况下坚持策略,不手动干预?如果你不能承受这个幅度的亏损,那么这个策略对你来说风险太大了。
年化波动率(Annualized Volatility)
定义:策略每日收益率的标准差,年化处理后反映策略收益的波动程度。波动率越高,策略的不确定性越大。
公式:
日收益率标准差 = std(每日收益率序列)
年化波动率 = 日收益率标准差 × sqrt(252)代码实现:
python
def annualized_volatility(daily_values):
"""计算年化波动率"""
daily_returns = np.diff(daily_values) / daily_values[:-1]
return daily_returns.std() * np.sqrt(252)参考标准:
- 沪深300指数的年化波动率通常在15%-25%之间
- 如果策略的年化波动率显著高于基准指数,说明策略承担了更大的风险
- 波动率本身不是"坏"的,关键看波动是否带来了足够的收益补偿
风险调整收益指标
夏普比率(Sharpe Ratio)
夏普比率是评价策略"性价比"的核心指标——每承担一单位风险,能获得多少超额收益。由于夏普比率非常重要,下一章会专门深入讲解,这里先给出基本定义。
公式:
夏普比率 = (年化收益率 - 无风险利率) / 年化波动率参考标准:
| 夏普比率 | 评价 |
|---|---|
| < 0 | 策略跑输无风险收益,不值得考虑 |
| 0 ~ 0.5 | 收益不足以补偿风险 |
| 0.5 ~ 1.0 | 一般 |
| 1.0 ~ 2.0 | 不错 |
| 2.0 ~ 3.0 | 优秀 |
| > 3.0 | 卓越,但需要警惕过拟合 |
Calmar比率
定义:年化收益率与最大回撤的比值。与夏普比率类似,但用最大回撤替代波动率来衡量风险。
公式:
Calmar比率 = 年化收益率 / 最大回撤代码实现:
python
def calmar_ratio(daily_values, trading_days_per_year=252):
"""计算Calmar比率"""
ann_ret = annualized_return(daily_values, trading_days_per_year)
max_dd = max_drawdown(daily_values)
if max_dd == 0:
return float('inf')
return ann_ret / max_dd参考标准:
| Calmar比率 | 评价 |
|---|---|
| < 0.5 | 风险收益比不佳 |
| 0.5 ~ 1.0 | 一般 |
| 1.0 ~ 2.0 | 不错 |
| 2.0 ~ 3.0 | 优秀 |
| > 3.0 | 卓越 |
Calmar比率的优势在于它用最大回撤来衡量风险,比波动率更直观——投资者更关心"我最坏会亏多少",而不是"收益率的统计离散程度"。
交易统计指标
胜率(Win Rate)
定义:盈利交易次数占总交易次数的比例。
公式:
胜率 = 盈利交易次数 / 总交易次数 × 100%代码实现:
python
def win_rate(trades):
"""计算胜率"""
if len(trades) == 0:
return 0.0
winning_trades = [t for t in trades if t['profit'] > 0]
return len(winning_trades) / len(trades)参考标准:
- 胜率本身不能单独评价策略好坏。一个胜率30%的策略可能是赚钱的(每次赚的多、亏的少),一个胜率70%的策略可能是亏钱的(每次赚的少、亏的多)
- 通常来说,趋势跟踪策略的胜率偏低(30%-40%),但盈亏比高
- 均值回归策略的胜率偏高(50%-60%),但单笔盈亏小
- 胜率需要与盈亏比结合分析
盈亏比(Profit/Loss Ratio)
定义:平均盈利交易的金额与平均亏损交易金额的比值。
公式:
盈亏比 = 平均盈利金额 / 平均亏损金额代码实现:
python
def profit_loss_ratio(trades):
"""计算盈亏比"""
profits = [t['profit'] for t in trades if t['profit'] > 0]
losses = [t['profit'] for t in trades if t['profit'] < 0]
if len(profits) == 0 or len(losses) == 0:
return float('inf') if len(profits) > 0 else 0.0
avg_profit = sum(profits) / len(profits)
avg_loss = abs(sum(losses) / len(losses))
if avg_loss == 0:
return float('inf')
return avg_profit / avg_loss参考标准:
| 盈亏比 | 评价 |
|---|---|
| < 1.0 | 每次亏的比赚的多,必须靠高胜率弥补 |
| 1.0 ~ 1.5 | 一般 |
| 1.5 ~ 2.0 | 不错 |
| 2.0 ~ 3.0 | 很好 |
| > 3.0 | 优秀 |
胜率与盈亏比的关系
一个策略能否盈利,取决于胜率和盈亏比的组合。盈亏平衡时的最低胜率为:
盈亏平衡胜率 = 1 / (1 + 盈亏比)| 盈亏比 | 盈亏平衡胜率 |
|---|---|
| 1.0 | 50.0% |
| 1.5 | 40.0% |
| 2.0 | 33.3% |
| 3.0 | 25.0% |
| 5.0 | 16.7% |
举个例子:如果你的策略盈亏比是2.0(平均每次盈利是平均每次亏损的2倍),那么只要胜率超过33.3%,策略就是盈利的。这就是为什么很多趋势跟踪策略虽然胜率只有30%-40%,但仍然能赚钱——它们的盈亏比通常在2.0以上。
平均持仓天数
定义:从买入到卖出的平均持有天数。
代码实现:
python
def avg_holding_days(trades):
"""计算平均持仓天数"""
if len(trades) == 0:
return 0
total_days = sum(t['holding_days'] for t in trades)
return total_days / len(trades)意义:
- 平均持仓天数反映了策略的交易频率
- 短线策略:1-5天
- 波段策略:5-30天
- 中线策略:30-90天
- 长线策略:90天以上
持仓天数越短,交易越频繁,交易成本的累积影响越大。一个日均换仓的策略,每月的交易成本可能就吃掉了大部分收益。
综合指标汇总
下面是一个计算所有绩效指标的完整函数:
python
def calculate_all_metrics(daily_values, trades, risk_free_rate=0.02):
"""计算所有绩效指标"""
metrics = {}
# 收益类
metrics['total_return'] = total_return(daily_values)
metrics['annualized_return'] = annualized_return(daily_values)
# 风险类
metrics['max_drawdown'] = max_drawdown(daily_values)
metrics['annualized_volatility'] = annualized_volatility(daily_values)
# 风险调整收益
excess_return = metrics['annualized_return'] - risk_free_rate
metrics['sharpe_ratio'] = excess_return / metrics['annualized_volatility'] if metrics['annualized_volatility'] > 0 else 0
metrics['calmar_ratio'] = metrics['annualized_return'] / metrics['max_drawdown'] if metrics['max_drawdown'] > 0 else float('inf')
# 交易统计
if trades:
metrics['total_trades'] = len(trades)
metrics['win_rate'] = win_rate(trades)
metrics['profit_loss_ratio'] = profit_loss_ratio(trades)
metrics['avg_holding_days'] = avg_holding_days(trades)
else:
metrics['total_trades'] = 0
metrics['win_rate'] = 0
metrics['profit_loss_ratio'] = 0
metrics['avg_holding_days'] = 0
return metrics
def print_metrics(metrics):
"""格式化输出绩效指标"""
print("=" * 50)
print(" 策略绩效报告")
print("=" * 50)
print(f"总收益率: {metrics['total_return']:.2%}")
print(f"年化收益率: {metrics['annualized_return']:.2%}")
print(f"最大回撤: {metrics['max_drawdown']:.2%}")
print(f"年化波动率: {metrics['annualized_volatility']:.2%}")
print(f"夏普比率: {metrics['sharpe_ratio']:.2f}")
print(f"Calmar比率: {metrics['calmar_ratio']:.2f}")
print(f"总交易次数: {metrics['total_trades']}")
print(f"胜率: {metrics['win_rate']:.2%}")
print(f"盈亏比: {metrics['profit_loss_ratio']:.2f}")
print(f"平均持仓天数: {metrics['avg_holding_days']:.1f}")
print("=" * 50)指标使用中常见的误区
误区一:只看收益率不看风险
"年化30%"听起来很诱人,但如果最大回撤50%,你在回撤期间能否坚持持有?大多数人在账户腰斩时已经止损离场了。永远将收益率和风险指标放在一起看。
误区二:过度追求高夏普比率
夏普比率虽然是很好的综合指标,但它有其局限性(下一章详细讨论)。不要为了优化夏普比率而牺牲策略的简洁性和可解释性。
误区三:忽视交易次数的统计显著性
如果你的策略只做了5笔交易,即使胜率80%、年化收益50%,这些数字也没有统计意义。一般来说,至少需要30笔以上的交易才能得出相对可靠的统计结论。
误区四:忽略与基准的对比
策略年化收益10%,看起来一般。但如果同期沪深300下跌20%,那么策略跑赢了基准30个百分点,实际上表现非常好。始终选择一个合适的基准指数进行对比。
下一步
掌握了绩效指标的计算方法后,下一章我们将深入探讨其中最重要的指标——夏普比率,理解它的数学含义、使用方法和局限性。