3. 投资组合构建流程:数据获取与清洗、因子选择、权重优化、回测框架搭建
做量化投资这些年,我最大的感触就是:组合构建不是一锤子买卖。它更像是在搭积木——每一块都得严丝合缝,否则整个结构说塌就塌。今天咱们就聊聊这个流程里的四个核心环节。
核心逻辑一句话:数据是原料,因子是配方,权重是火候,回测是试吃。缺一个,这菜就做不地道。
3.1 数据获取与清洗:地基打不牢,房子会晃
说实话,我见过太多人一上来就搞什么机器学习、深度学习,结果数据源都没搞清楚。你想想看,如果拿到的日频数据里混着停牌日、复权没做对、甚至还有未来数据——那后面的一切都是白搭。
数据获取的常见来源:
- 行情数据:日线、分钟线、Tick 级数据。我个人习惯用 Wind 或聚宽做主力源,再用 Tushare 做交叉验证。
- 财务数据:季报、年报、业绩预告。注意财报的发布时间——我踩过坑,直接用财报发布日期当信号,结果发现数据里混入了「未来信息」。
- 另类数据:舆情、龙虎榜、供应链数据。这些数据噪声大,但有时能挖到超额收益。
我的小习惯:每次拿到新数据,先画个时间轴,看看数据覆盖的起止日期、缺失率、异常值。别急着建模,先「摸一摸」数据长什么样。
清洗环节,我一般做这几步:
- 去重:同一个交易日出现两条数据?保留第一条,或者取均值。
- 处理缺失值:用前向填充(ffill)还是插值?看情况。如果是停牌日,我倾向于用复牌后的第一笔数据。
- 异常值检测:比如某只股票一天涨了 1000%——这大概率是数据错误,不是真的收益。
- 复权处理:后复权还是前复权?我建议用后复权做回测,因为能真实反映持有期的收益。
# 一个简单的数据清洗示例(Python 伪代码)
import pandas as pd
def clean_price_data(df):
# 去重
df = df.drop_duplicates(subset=['date', 'code'])
# 前向填充缺失值
df['close'] = df.groupby('code')['close'].ffill()
# 剔除异常值(超过 5 个标准差)
df = df[df['close'].groupby(df['code']).transform(
lambda x: (x - x.mean()).abs() < 5 * x.std()
)]
return df
⚠️ 曾经踩过的坑:有一次我用聚宽的数据做回测,发现策略收益高得离谱。查了半天,原来是数据里混入了「未来复权因子」——回测时用了未来的复权比例,导致历史收益被放大。从那以后,我每次都会手动检查数据的时间戳。
3.2 因子选择:不是越多越好,是越「纯」越好
因子选择这块,说白了就是找那些能解释股票收益的「特征」。但这里有个误区——很多人觉得因子越多越好,恨不得把几百个因子全塞进去。结果呢?过拟合,样本外一塌糊涂。
我常用的因子分类:
| 因子类型 | 常见因子 | 我的经验 |
|---|---|---|
| 动量因子 | 过去 1 个月、6 个月、12 个月收益 | 短期动量(1 个月)在 A 股效果不错,但要注意反转效应 |
| 价值因子 | PE、PB、PS、股息率 | 低估值策略在熊市里很抗跌,但牛市容易跑输 |
| 质量因子 | ROE、毛利率、资产负债率 | 高 ROE 的股票长期持有体验好,但短期可能不涨 |
| 情绪因子 | 换手率、波动率、分析师评级 | 换手率高的股票,短期波动大,适合做波段 |
因子筛选的步骤:
- 单因子测试:算 IC(信息系数)、IR(信息比率)。IC 绝对值大于 0.02 才算有点用。
- 相关性分析:两个因子相关系数超过 0.7?留一个就行,不然多重共线性会让你头疼。
- 分组回测:把股票按因子值分成 5 组或 10 组,看看多空组合的收益曲线是否平滑。
一个实用的原则:因子要「逻辑自洽」。你选的因子,得能讲出故事来。比如「低波动率因子」——为什么低波动的股票长期跑赢高波动的?因为投资者过度追捧彩票型股票,导致高波动股被高估。这个逻辑说得通,因子才靠谱。
3.3 权重优化:数学很美,但别太迷信
权重优化,说白了就是决定每只股票买多少。经典的均值-方差模型(Markowitz)大家都学过,但实际用起来,你会发现它特别「敏感」——输入参数稍微变一点,权重就天翻地覆。
常见的优化方法:
- 等权重:简单粗暴,但效果不差。我做过回测,等权重组合长期跑赢市值加权组合。
- 风险平价:让每个资产对组合的风险贡献相等。适合低波动环境。
- 均值-方差优化:追求给定风险下的最大收益。但要注意——预期收益率的估计误差会放大权重偏差。
- Black-Litterman 模型:结合先验分布和主观观点,比纯均值-方差更稳健。
# 风险平价权重的简单实现
import numpy as np
def risk_parity_weights(cov_matrix):
n = cov_matrix.shape[0]
# 初始化等权重
w = np.ones(n) / n
for _ in range(100):
# 计算边际风险贡献
mrc = cov_matrix @ w
# 调整权重使风险贡献相等
w = w * (1 / mrc)
w = w / w.sum()
return w
我的建议:别把优化结果当圣旨。每次跑完优化,我都会手动检查一下权重——有没有某只股票占比超过 10%?有没有行业过度集中?数学上最优的,现实中可能根本执行不了。
3.4 回测框架搭建:别让回测骗了你
回测框架,是检验策略的「试金石」。但说实话,回测本身很容易「作弊」——比如用了未来数据、没考虑交易成本、假设可以无限成交。这些坑,我几乎都踩过一遍。
一个靠谱的回测框架应该包含:
- 事件驱动 vs 向量化:事件驱动更贴近真实交易,但速度慢;向量化快,但容易忽略细节。我一般先用向量化做快速验证,再用事件驱动做精细回测。
- 交易成本:佣金、印花税、滑点。滑点我一般按 0.1% 估算,流动性差的股票会更高。
- 持仓限制:单只股票最大仓位、行业集中度、换手率限制。
- 绩效指标:年化收益、夏普比率、最大回撤、胜率、盈亏比。
⚠️ 我曾经犯过的错:有一次回测一个高频策略,收益曲线漂亮得不行。结果发现——我忘了加滑点!实际交易中,每次买卖都要付出 0.1% 的成本,高频交易下来,收益全被吃掉了。从那以后,我回测的第一件事就是检查「成本假设」。
回测的「避坑指南」:
- 前视偏差:确保回测时只用到了「当时」能获取的数据。
- 生存偏差:别忘了把退市的股票加进来,否则回测结果会偏乐观。
- 过拟合:参数调得越多,样本外表现越差。我一般限制参数不超过 3 个。
最后说一句:回测通过只是第一步。真正的考验是实盘。我见过太多策略回测年化 30%,实盘一跑就亏钱。原因?回测时没考虑「流动性冲击」和「市场冲击成本」。所以,回测框架里一定要加上「冲击成本模型」——哪怕只是个简单的线性近似。
好了,这一章的内容就到这儿。数据、因子、权重、回测——这四个环节环环相扣,任何一个出问题,整个组合都会受影响。下一章咱们聊聊更具体的因子挖掘方法,到时候再细说。