在量化投资领域,复权因子计算是一个基础但极其重要的环节。想象一下,你正在分析一只上市10年的股票,期间经历了多次分红、送股、配股等公司行为。如果不进行复权处理,K线图上会出现巨大的价格缺口,导致技术指标失真,回测结果完全不可靠。
传统方法使用循环计算复权因子,就像用算盘计算复杂的数学题。我曾在项目中处理过3000多只股票20年的历史数据,用循环方法计算一次全市场复权因子需要近30分钟。这还只是静态计算,如果考虑每日增量更新,时间成本更加惊人。
XtQuant提供的get_divid_factors接口返回的是原始除权数据,包含分红金额、送股比例等详细信息。这些数据就像食材原料,我们需要把它们"烹饪"成可以直接使用的复权因子。官方示例采用循环计算,虽然逻辑清晰易懂,但性能确实是个硬伤。
向量化计算的本质是把"逐行处理"变成"批量处理"。就像用大型收割机替代镰刀收割小麦,效率提升不是线性的而是指数级的。我们的优化方案主要基于两个关键点:
用join替代循环查找:建立一个包含所有交易日的空DataFrame,与除权数据进行合并。这相当于先准备好所有日期的"容器",再把对应日期的除权数据"倒"进去。
用cumprod替代累乘:Pandas的cumprod函数可以一次性完成所有日期的累乘计算,就像多米诺骨牌一样自动传递前一日的结果。
python复制def get_factor_ratio(symbol: str, start: datetime.date, end: datetime.date)->pd.Series:
# 数据准备
df = xt.get_divid_factors(symbol, EPOCH)
df.index = df.index.astype(int)
# 关键步骤1:join操作
frames = pd.DataFrame([], index=tf.day_frames)
factor = pd.concat([frames, df["dr"]], axis=1)
# 关键步骤2:填充和累乘
factor.sort_index(inplace=True)
factor.fillna(1, inplace=True)
query = f'index >= {start_} and index <= {end_}'
return factor.cumprod().query(query)["dr"]
在我的测试环境中,对平安银行(000001.SZ)从2005年到2023年的复权因子计算:
这个100倍的提升不是理论值,而是实际跑出来的结果。当扩展到全市场股票时,差异会更加明显。有次我处理全市场数据更新,老方法需要2小时,新方法1分钟就搞定了。
在量化系统中,数据存储就像图书馆的书架设计。传统关系型数据库像是按字母排序的书架,而ClickHouse更像是为高频查询优化的智能仓储系统。它的列式存储和向量化执行引擎特别适合我们的场景:
我们的ClickHouse表结构设计考虑了以下几个关键因素:
| 字段名 | 类型 | 说明 |
|---|---|---|
| trade_date | Date | 交易日 |
| symbol | String | 股票代码 |
| factor | Float64 | 复权因子值 |
| update_time | DateTime | 更新时间 |
创建表的SQL示例:
sql复制CREATE TABLE factor_ratio (
trade_date Date,
symbol String,
factor Float64,
update_time DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (symbol, trade_date)
SETTINGS index_granularity = 8192
每日更新时,我们采用"断点续传"的思路:
这种设计避免了全量计算的资源浪费,也保证了数据的连续性。我在实际运行中发现,全市场3000多只股票的每日更新通常在20秒内完成。
上市公司行为有时会集中在某些特定日期,比如年报披露后的除权除息日。有次遇到一只股票在同一天既有分红又有送股,原始数据表现为两条记录,需要先按日期聚合计算总影响。我们的解决方案是:
python复制# 对同一天的多条记录进行聚合
df = df.groupby(df.index).agg({
'dr': 'prod',
# 其他字段...
})
股票停牌期间虽然没有交易,但可能有除权信息。最初我们忽略了这点,导致复权因子出现偏差。后来调整逻辑,确保停牌日也包含在计算范围内。
直接逐条写入ClickHouse性能很差,我们通过以下方式优化:
实测下来,批量写入比单条写入快50倍以上。
整套系统的数据处理流程如下:
性能指标对比:
| 操作 | 传统方法 | 优化方案 | 提升倍数 |
|---|---|---|---|
| 单股计算 | 400ms | 4ms | 100x |
| 全市场计算 | 30min | 30s | 60x |
| 每日更新 | 2h | 20s | 360x |
| 存储空间 | 10GB | 0.8GB | 12.5x |
这套系统已经在我们的实盘环境中稳定运行半年多,处理过多次市场极端情况。最让我自豪的是,在今年6月的高频除权季,系统顶住了全市场同时更新的压力,没有出现任何延迟。