2. 数据准备:股票关系图构建、节点特征工程、边权重计算
好,咱们进入第二章。说实话,这一章是整个图因子回测的基石。你想想看,图都建不好,后面算出来的因子能靠谱吗?我在项目里见过太多人,一上来就急着跑模型,结果数据准备阶段全是坑,最后回测结果一塌糊涂。
这一章,我会带你一步步搞定三件事:股票关系图怎么搭、节点特征怎么提取、边权重怎么算。嗯,咱们一个一个来。
2.1 股票关系图构建
股票关系图,说白了就是把股票看成一个个节点,股票之间的关系看成边。那问题来了——什么才算「有关系」?
我个人习惯用三种关系:
- 行业关联:同行业股票天然有联动性。比如茅台和五粮液,你涨我也涨。
- 产业链上下游:比如锂矿和电池厂,上游涨价下游成本就高。
- 资金联动:机构重仓股之间经常同涨同跌。
构建图的时候,我建议用无向图。为什么?因为股票之间的影响往往是双向的。你想想看,A涨带动B涨,B涨也可能反过来影响A。
核心思路:节点 = 股票代码,边 = 股票之间的某种关联关系。图结构用邻接矩阵或边列表存储。
下面是我常用的构建代码框架:
import pandas as pd
import networkx as nx
# 假设我们有股票列表和行业分类
stocks = ['000001.SZ', '000002.SZ', '600519.SH']
industry_map = {'000001.SZ': '银行', '000002.SZ': '房地产', '600519.SH': '白酒'}
# 构建图
G = nx.Graph()
for stock in stocks:
G.add_node(stock, industry=industry_map[stock])
# 同行业股票加边
for i, s1 in enumerate(stocks):
for s2 in stocks[i+1:]:
if industry_map[s1] == industry_map[s2]:
G.add_edge(s1, s2, weight=1.0)
小技巧:别一开始就把图搞得太复杂。我建议先用行业关系建一个基础图,后面再慢慢加其他关系。这样调试起来方便。
2.2 节点特征工程
节点特征,就是每个股票自己的属性。这部分我踩过不少坑,给你说说我的经验。
常用的节点特征分三类:
| 特征类型 | 具体指标 | 说明 |
|---|---|---|
| 基本面特征 | PE、PB、ROE、市值 | 反映公司内在价值 |
| 量价特征 | 收益率、波动率、换手率 | 反映市场交易行为 |
| 情绪特征 | 北向资金净流入、融资余额变化 | 反映资金态度 |
我曾经犯过一个错误——把所有特征一股脑全塞进去。结果模型过拟合得一塌糊涂。后来我学乖了,特征要精不要多。一般选5-8个核心特征就够了。
特征处理上,有几点要注意:
- 标准化:不同量纲的特征要归一化。我习惯用Z-score。
- 缺失值处理:别直接删。可以用行业均值填充,或者用前向填充。
- 时序对齐:所有特征必须用同一时间截面的数据。这个坑我踩过——用了不同日期的数据,结果回测曲线漂亮得不像真的,后来才发现是数据没对齐。
from sklearn.preprocessing import StandardScaler
# 假设 features_df 是特征数据,index为股票代码,columns为特征名
scaler = StandardScaler()
features_scaled = scaler.fit_transform(features_df)
# 转回DataFrame
features_scaled_df = pd.DataFrame(
features_scaled,
index=features_df.index,
columns=features_df.columns
)
注意:标准化一定要在训练集上fit,再transform到测试集。别把未来信息泄露进去。我见过有人对整个数据集做标准化,结果回测效果虚高——说白了就是作弊了。
2.3 边权重计算
边权重,就是衡量两只股票之间关系强弱的数值。权重越大,关系越紧密。
常用的计算方法有:
- 相关系数法:用历史收益率的皮尔逊相关系数。简单粗暴,但有效。
- 互信息法:能捕捉非线性关系。计算量稍大。
- 行业相似度:同行业设为1,不同行业设为0。或者用行业分类的层级距离。
我个人最常用的是滚动相关系数。为什么?因为股票之间的关系是动态变化的。去年同涨同跌的两只股票,今年可能就分道扬镳了。
import numpy as np
def calculate_edge_weight(returns_1, returns_2, window=60):
"""
计算两只股票的滚动相关系数作为边权重
returns_1, returns_2: 收益率序列
window: 滚动窗口大小
"""
corr = returns_1.rolling(window).corr(returns_2)
# 取最近一个值作为当前权重
return corr.iloc[-1]
# 示例
weight = calculate_edge_weight(stock_a_returns, stock_b_returns)
print(f"边权重: {weight:.4f}")
经验之谈:边权重不要设得太绝对。我一般会做一个阈值处理——相关系数低于0.3的边直接去掉,高于0.7的设为1。这样图不会太稠密,也不会太稀疏。
嗯,到这里数据准备的核心内容就差不多了。你可能会问:这些特征和权重怎么组合到一起?别急,下一节我们会讲怎么把这些数据喂给图神经网络。但在此之前,我建议你先把这章的内容动手跑一遍。
最后送你一句话:数据准备花的时间,后面模型调试会十倍还给你。别图快,慢慢来。
本章小结:数据准备阶段,图结构决定了你能捕捉到什么关系,节点特征决定了信息量,边权重决定了关系强弱。三者缺一不可。我建议你从最简单的行业图开始,逐步迭代优化。