换手率分析:你的因子策略的隐藏成本
高换手率可能摧毁一个高 IC 因子。换手率分析测量你的组合在再平衡期间变化多少——以及扣除成本后你的净 Sharpe 是否仍然为正。
第 4 篇 · 共 10 篇 约 2 分钟
你有一个 IC = 0.05 的因子。很优秀。但你的组合日换手率 100%——你实际上每天都在替换整个组合。扣除每笔交易 10 bps 的交易成本后,你的总 Sharpe 1.2 变成净 Sharpe 0.3。突然这个因子就没那么好了。
换手率分析是测量你的组合在再平衡期间变化多少,以及你的因子在扣除成本后是否仍有收益的做法。
定义换手率
组合换手率是两个再平衡日期之间变化的组合权重的分数:
½ 因子确保换手率在 0(无变化)和 1(完全替换)之间。
对于多空组合:
在 Python 中计算换手率
import pandas as pd
import numpy as np
def compute_turnover(weights_df):
"""
计算连续再平衡期间之间的组合换手率。
Parameters
----------
weights_df : DataFrame
组合权重 (date x asset)
Returns
-------
turnover : Series
每个再平衡日期的换手率
"""
turnover = (weights_df.diff().abs().sum(axis=1) / 2)
return turnover
def compute_factor_turnover(factor_values, n_quantiles=5):
"""
计算基于因子排名的换手率。
"""
# 每日因子排名
ranks = factor_values.rank(axis=1, pct=True)
# 五分位边界
quantile_bounds = np.linspace(0, 1, n_quantiles + 1)
# 转换为五分位权重(等权在每个五分位内)
def to_quantile_weights(ranks):
weights = pd.DataFrame(0, index=ranks.index, columns=ranks.columns)
for i in range(n_quantiles):
low = quantile_bounds[i]
high = quantile_bounds[i + 1]
mask = (ranks >= low) & (ranks < high)
weights[mask] = 1.0 / mask.sum(axis=1)
# 最后一期包含 high=1
mask = ranks == 1.0
weights[mask] = 1.0 / mask.sum(axis=1)
return weights
quantile_weights = to_quantile_weights(ranks)
turnover = compute_turnover(quantile_weights)
return turnover
def compute_turnover_decomposition(weights_df, factor_values):
"""
将换手率分解为:
- 信号换手率:由于因子信号变化
- 噪声换手率:由于随机权重波动
"""
signal_turnover = compute_turnover(weights_df)
# 纯信号:从纯因子排名合成的权重(无优化噪声)
rank_weights = factor_values.apply(lambda x: x / x.abs().sum(), axis=1)
pure_signal_turnover = compute_turnover(rank_weights)
noise_turnover = signal_turnover - pure_signal_turnover
return {
'total_turnover': signal_turnover,
'signal_turnover': pure_signal_turnover,
'noise_turnover': noise_turnover
}
换手率和净 Sharpe
关键关系:
其中 TC 是交易成本(作为分数)。这个近似表明 交易成本对高换手率策略有不成比例的影响。
对于 IC = 0.05 且日再平衡的因子:
- 总 Sharpe ≈ 0.8
- 日换手率 ≈ 80% (0.8)
- TC = 0.001(往返 10 bps)
- 净 Sharpe ≈ √(2 × 0.8 × 0.001 × 0.05) − 0.001 ≈ 0.0003
该因子在日再平衡 + 10 bps 成本下基本上无法交易。
净 Sharpe 计算器
def estimate_net_sharpe(gross_sharpe, daily_turnover, tc_bps, n_days=252):
"""
估计扣除交易成本后的净 Sharpe。
Parameters
----------
gross_sharpe : float
再平衡前的总 Sharpe
daily_turnover : float
日换手率(0 到 1 之间)
tc_bps : float
每次交易的交易成本(以 bps 为单位)
n_days : int
年化天数
"""
tc = tc_bps / 10000 # 转换为分数
# 年换手率
annual_turnover = daily_turnover * n_days
# 交易成本对收益的年化影响
# 每笔交易成本 * 换手次数
tc_cost = annual_turnover * tc
# 假设收益与换手率成比例减少
gross_return = gross_sharpe * np.sqrt(n_days) # 粗略近似
net_return = gross_return - tc_cost
# 假设波动率不变
volatility = 1.0 # 归一化
net_sharpe = net_return / volatility
return {
'gross_sharpe': gross_sharpe,
'net_sharpe': net_sharpe,
'annual_turnover': annual_turnover,
'annual_tc_cost': tc_cost,
'tc_erosion_pct': tc_cost / gross_return * 100
}
降低换手率而不破坏 alpha 的方法
信号平滑。 在排名之前对因子值应用指数移动平均。减少换手率代价是信号延迟。
def smooth_factor(factor_values, halflife=5):
"""
应用指数平滑减少换手率。
"""
return factor_values.ewm(halflife=halflife).mean()
def decay_adjusted_ranks(factor_values, decay_half_life=10):
"""
根据衰减调整因子排名。
衰减慢的因子给予更高权重,
这样可以以较低频率再平衡。
"""
smoothed = smooth_factor(factor_values, halflife=decay_half_life)
return smoothed.rank(axis=1, pct=True)
阈值再平衡。 只有当前权重与目标权重的偏差超过阈值时才再平衡。
def threshold_rebalance(current_weights, target_weights, threshold=0.02):
"""
只有当权重差异超过阈值时才再平衡。
"""
diff = (target_weights - current_weights).abs()
trades = pd.Series(0, index=current_weights.index)
trades[diff > threshold] = target_weights[diff > threshold] - current_weights[diff > threshold]
return trades
def compute_threshold_turnover(target_weights, threshold=0.02):
"""
计算阈值再平衡策略的换手率。
"""
n_periods = len(target_weights)
turnover_list = []
current = target_weights.iloc[0].fillna(0)
for t in range(1, n_periods):
target = target_weights.iloc[t].fillna(0)
diff = (target - current).abs()
rebalance_mask = diff > threshold
turnover_period = diff[rebalance_mask].sum() / 2
turnover_list.append(turnover_period)
# 更新当前权重(仅对超过阈值的头寸)
current = current.copy()
current[rebalance_mask] = target[rebalance_mask]
return pd.Series(turnover_list, index=target_weights.index[1:])
关键阈值
| 日换手率 | 分类 | 可行性 |
|---|---|---|
| < 5% | 低 | 高度可行 |
| 5-20% | 中等 | 合理成本下可行 |
| 20-50% | 高 | 需要低 costs 或强 alpha |
| > 50% | 极高 | 几乎不可能盈利交易 |
常见错误
报告总 Sharpe 而不提换手率。 显示 Sharpe 1.5 + 日再平衡 + 100% 换手率的回测,在扣除交易成本后可能是 Sharpe 0.4。始终报告净 Sharpe。
优化权重放大换手率。 均值-方差优化可能产生极端权重,在期间之间翻转符号。使用权重约束限制换手率。
忽略市场冲击。 如果你运行 1 亿美元组合,日再平衡需要交易平均日成交量的 2%,市场冲击将超过你的 alpha。
假设固定交易成本。 实际交易成本随市场条件变化。在高波动日,买卖价差扩大,实际交易成本可能比你假设的高 2-3 倍。
参考文献
- López de Prado, Advances in Financial Machine Learning, 第 4 章. Wiley, 2018. ISBN: 978-1-119-48208-6.
- Ang, Asset Management, 第 11 章. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.