4、因子构建基础:因子定义、因子计算逻辑、因子暴露度计算
好,咱们进入正题。因子构建这事儿,说白了就是给资产「拍X光片」——你得找到那些能解释收益差异的底层特征。我做了这么多年量化,见过太多人一上来就堆砌几百个因子,结果过拟合得一塌糊涂。今天咱们就聊聊,怎么把因子这件事做扎实。
4.1 因子定义:先想清楚你要抓什么
因子定义不是拍脑袋。我个人习惯,先问自己三个问题:
- 这个因子背后有没有经济学逻辑?
- 它能不能在样本外持续有效?
- 它的计算是否可重复、可验证?
举个例子,碳金融里有个经典因子叫「碳强度因子」。你想想看,一家公司单位营收的碳排放量越高,未来面临的碳税风险就越大。这个逻辑很清晰——政策压力会传导到利润表。
因子定义三要素:
- 名称:碳强度因子(Carbon Intensity Factor)
- 含义:企业每单位营收产生的碳排放量
- 方向:碳强度越高,预期收益越低(负向因子)
我在项目中遇到过一个问题:有人把「碳排放总量」直接当因子用。这其实有问题——大公司排放量天然高,但人家营收也高啊。你得做标准化处理,不然因子暴露度里全是规模效应。
4.2 因子计算逻辑:从原始数据到因子值
因子计算,说白了就是三步走:取数、加工、标准化。咱们拿碳强度因子来演示。
4.2.1 数据准备
你需要两类数据:
- 碳排放数据(Scope 1 + Scope 2,单位:吨CO₂)
- 营收数据(单位:万元)
嗯,这里要注意:Scope 3(供应链排放)数据质量参差不齐,我建议初期先别碰,容易引入噪声。
4.2.2 计算原始因子值
# Python示例:碳强度因子计算
import pandas as pd
import numpy as np
def calc_carbon_intensity(emission_df, revenue_df):
"""
计算碳强度因子
emission_df: 包含 ['ticker', 'year', 'scope1', 'scope2']
revenue_df: 包含 ['ticker', 'year', 'revenue']
"""
# 合并数据
df = pd.merge(emission_df, revenue_df, on=['ticker', 'year'])
# 计算总排放
df['total_emission'] = df['scope1'] + df['scope2']
# 碳强度 = 总排放 / 营收(单位:吨/万元)
df['raw_factor'] = df['total_emission'] / df['revenue']
return df[['ticker', 'year', 'raw_factor']]
这段代码看着简单,但坑不少。我曾经吃过一次亏——某家公司的营收数据是季度累计值,而碳排放是年度值,直接除出来个离谱的数字。所以数据对齐是第一步,千万别偷懒。
4.2.3 因子标准化
原始因子值没法直接用。不同行业的碳强度差异巨大——钢铁和互联网能比吗?所以要做行业中性化处理。
def standardize_factor(df, group_col='industry'):
"""
行业中性化 + Z-score标准化
"""
# 按行业分组,去均值
df['demeaned'] = df.groupby(group_col)['raw_factor'].transform(
lambda x: x - x.mean()
)
# 再除以标准差
df['zscore_factor'] = df.groupby(group_col)['demeaned'].transform(
lambda x: x / x.std()
)
# 缩尾处理(去掉极端值)
df['zscore_factor'] = df['zscore_factor'].clip(-3, 3)
return df
避坑指南:我曾经在缩尾处理时用了全局分位数,结果把钢铁行业的正常值给截掉了。后来改成按行业分位数处理,效果好了很多。记住:异常值要按行业语境来判断。
4.3 因子暴露度计算:把因子值映射到投资组合
因子暴露度,说白了就是「你的组合在多大程度上受这个因子影响」。计算方式有两种:
4.3.1 个股层面的暴露度
对于个股来说,因子暴露度就是标准化后的因子值本身。比如某公司碳强度Z-score是-1.5,那它的碳因子暴露度就是-1.5——意味着它比行业平均更「绿色」。
4.3.2 组合层面的暴露度
组合暴露度是持仓权重的加权平均:
def portfolio_exposure(holdings_df, factor_df):
"""
计算组合的因子暴露度
holdings_df: 包含 ['ticker', 'weight']
factor_df: 包含 ['ticker', 'zscore_factor']
"""
merged = pd.merge(holdings_df, factor_df, on='ticker')
# 加权平均
portfolio_exp = (merged['weight'] * merged['zscore_factor']).sum()
return portfolio_exp
举个例子:假设组合里只有两只股票——A公司权重60%,碳暴露度0.8;B公司权重40%,碳暴露度-0.3。那组合暴露度就是 0.6×0.8 + 0.4×(-0.3) = 0.36。正数意味着组合整体偏「高碳」风格。
注意:因子暴露度会随时间变化。公司可能减排了,行业结构可能变了。我建议至少每季度重新计算一次暴露度。别指望一年前的数据还能指导现在的交易。
4.4 知识体系总览
下面这张图把因子构建的完整流程串起来了。你仔细看,每一步都有坑,每一步都需要判断。
4.5 几个实战经验
最后分享几个我踩过的坑,希望能帮你省点时间:
- 数据频率要匹配:碳排放数据通常是年度,营收数据可能是季度。别拿季度营收去匹配年度排放,会算出奇怪的季节性波动。
- 行业分类别用太细:我试过用申万三级行业做中性化,结果每个组里就两三家公司,标准化后全是极端值。建议用一级或二级行业。
- 因子暴露度不是越高越好:有人觉得暴露度绝对值越大,因子越有效。其实不然——暴露度只是度量,不是信号。关键看暴露度能否稳定预测收益。
一个小技巧:构建完因子后,先做一期「回测前体检」——看看因子值的截面分布是否合理,行业覆盖是否均匀,时间序列是否平稳。这些检查花不了10分钟,但能帮你筛掉80%的无效因子。
因子构建这件事,说白了就是「把经济学直觉变成可计算的数字」。别追求花哨的算法,先把基础打牢。数据干净、逻辑清晰、计算可复现——这三点做到了,你的因子就已经超过市场上大半的人了。