跳到主要内容

换手率、胜率与盈亏比

直觉

除了风险调整收益,还有三个「过程指标」决定一个策略实不实用

  • 换手率:买卖有多频繁 → 决定交易成本
  • 胜率:赚钱的次数占比 → 心理舒适度;
  • 盈亏比:平均每次赚的 ÷ 平均每次亏的 → 即使胜率低也能盈利。

定义

设持仓序列为 wtw_t,单期收益 rtr_t

换手率=twtwt1,胜率=#{t:rt>0}n\text{换手率} = \sum_t |w_t - w_{t-1}|, \qquad \text{胜率} = \frac{\#\{t: r_t>0\}}{n} 盈亏比=r+r,r+=盈利期收益均值, r=亏损期收益均值\text{盈亏比} = \frac{\overline{r^+}}{|\overline{r^-}|}, \quad \overline{r^+}=\text{盈利期收益均值},\ \overline{r^-}=\text{亏损期收益均值}
「人话」解释:高胜率一定好吗?

不一定。一个「每次赚 1%、偶尔亏 20%」的策略胜率可能 90%,但一次大亏抹平所有利润。 反过来,趋势策略常常胜率只有 30~40%,但靠「亏小赚大」的高盈亏比照样盈利。 所以胜率和盈亏比要一起看:期望 = 胜率×平均盈利 − 败率×平均亏损,正期望才能长期赚钱。

可运行案例:双均线策略的过程指标

quant.vector_backtest 跑一个双均线策略,读出换手率,并从收益序列算胜率、盈亏比

import quant
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
close = df['adj_close']

# 双均线信号 → 持续仓位(ffill)
raw = (close.rolling(20).mean() > close.rolling(60).mean()).astype(float)
signal = raw.replace(0, np.nan).ffill().fillna(0)

res = quant.vector_backtest(close, signal, cost_bps=2.0, freq=252)
strat_ret = res['returns']

wins = strat_ret[strat_ret > 0]; losses = strat_ret[strat_ret < 0]
win_rate = len(wins) / len(strat_ret)
avg_win = wins.mean(); avg_loss = losses.mean()
pl_ratio = avg_win / abs(avg_loss) if avg_loss != 0 else float('nan')
expectancy = win_rate*avg_win + (1-win_rate)*avg_loss   # 单期期望

print(f"总换手率   : {res['turnover']:.1f}   (越大=交易越频繁, 成本越高)")
print(f"胜率       : {win_rate:.2%}")
print(f"平均盈利期 : {avg_win*100:.3f}%")
print(f"平均亏损期 : {avg_loss*100:.3f}%")
print(f"盈亏比     : {pl_ratio:.3f}")
print(f"单期期望   : {expectancy*100:.4f}%   (正=长期可赚)")
print("\n夏普:", round(quant.sharpe(strat_ret, freq=252), 3),
    " 最大回撤:", f"{quant.max_drawdown(res['equity']):.2%}")

动手改一改

把均线改成更敏感的 5/20——你会看到换手率飙升、交易成本吃掉收益,单期期望可能转负。这就是「过度交易」的代价。

小结

  • 换手率 = 持仓变动绝对值之和,决定成本(引擎按换手扣 cost_bps);
  • 胜率 = 盈利期占比;盈亏比 = 平均盈利/平均亏损;
  • 期望 = 胜率×盈利 − 败率×亏损,正期望才是长期盈利的根本。