4. 因子计算:基于PageRank的因子、基于社区结构的因子、基于图嵌入的因子
好,咱们进入图因子计算的核心环节。说实话,这部分内容我当年啃了很久才真正吃透。你想想看,传统的因子都是基于价格、成交量这些时间序列数据,而图因子是从股票之间的关联关系中挖出来的。这完全是另一个维度的信息。
我个人习惯把图因子分成三大类:基于PageRank的、基于社区结构的、基于图嵌入的。这三类各有各的脾气,咱们一个一个来拆解。
4.1 基于PageRank的因子
PageRank,大家可能都听说过,Google当年靠它起家的。但用在股票上,思路完全不一样。在股票关联网络中,PageRank值衡量的是一只股票在网络中的"重要性"或"影响力"。
怎么理解?一只股票如果被很多"重要"的股票高度关联,它的PageRank值就高。说白了,它就是网络中的"意见领袖"。
我在项目中遇到过这样的情况:用沪深300成分股构建关联网络,计算PageRank值后,发现排名靠前的股票往往是一些银行股和保险股。嗯,这其实很合理——金融板块的关联性本来就强。
具体计算步骤,我一般这么走:
- 构建关联矩阵:用股票收益率的相关系数或偏相关系数,构建N×N的邻接矩阵
- 归一化处理:对矩阵进行列归一化,确保每列和为1
- 迭代计算:用幂迭代法求解PageRank值,直到收敛
- 因子构造:将PageRank值作为因子值,或者取对数、排名等变换
import networkx as nx
import numpy as np
def compute_pagerank_factor(returns, threshold=0.3):
"""
基于收益率序列计算PageRank因子
returns: DataFrame, 股票收益率数据 (T x N)
threshold: 相关系数阈值,低于此值视为无连接
"""
# 1. 计算相关系数矩阵
corr_matrix = returns.corr().values
# 2. 构建邻接矩阵(只保留强相关)
adj_matrix = np.where(np.abs(corr_matrix) > threshold, np.abs(corr_matrix), 0)
np.fill_diagonal(adj_matrix, 0) # 去掉自环
# 3. 构建图
G = nx.from_numpy_array(adj_matrix)
# 4. 计算PageRank
pagerank = nx.pagerank(G, alpha=0.85)
# 5. 转为因子值
factor = pd.Series(pagerank).sort_index()
factor.index = returns.columns
return factor
4.2 基于社区结构的因子
社区结构,说白了就是"物以类聚,人以群分"。在股票网络中,社区就是那些走势高度同步的股票群体。比如,白酒板块、新能源板块,它们内部关联性强,跨板块关联弱。
基于社区结构的因子,核心思路是:一只股票在其所属社区中的地位。我常用的指标有两个:
- 模块度贡献度:衡量股票对其所在社区凝聚力的贡献
- 社区内中心度:股票在社区内部的连接强度
我曾经踩过一个坑:直接用Louvain算法做社区检测,然后取社区编号作为因子。结果回测效果很差。为什么?因为社区编号是离散的、无序的,没法直接作为数值因子使用。
我后来改用社区内度中心度作为因子,效果就好多了。具体做法:
def compute_community_factor(returns, threshold=0.3):
"""
基于社区结构的因子计算
"""
# 构建图(同上)
corr_matrix = returns.corr().values
adj_matrix = np.where(np.abs(corr_matrix) > threshold, 1, 0)
np.fill_diagonal(adj_matrix, 0)
G = nx.from_numpy_array(adj_matrix)
# 社区检测
communities = nx.community.louvain_communities(G, seed=42)
# 计算每个节点的社区内度中心度
community_factor = {}
for node in G.nodes():
# 找到节点所属社区
for comm_id, comm in enumerate(communities):
if node in comm:
# 计算社区内连接数
internal_edges = sum(1 for neighbor in G.neighbors(node) if neighbor in comm)
community_factor[node] = internal_edges / (len(comm) - 1) # 归一化
break
factor = pd.Series(community_factor).sort_index()
factor.index = returns.columns
return factor
你想想看,这个因子的逻辑是什么?一只股票在社区内的连接越密集,说明它和同板块股票联动越强。这种股票往往更容易受到板块情绪的影响,波动也更同步。
4.3 基于图嵌入的因子
图嵌入,这是近几年比较火的方向。它的核心思想是:把图中的每个节点(股票)映射到一个低维向量空间中,同时保留节点的拓扑结构信息。
说白了,就是把网络结构"压缩"成一组数值特征。这些特征向量就可以直接作为因子使用。
我常用的图嵌入方法有:
| 方法 | 核心思想 | 适用场景 |
|---|---|---|
| Node2Vec | 随机游走 + Word2Vec | 大规模网络,捕捉局部结构 |
| DeepWalk | 深度优先随机游走 | 社区结构明显的网络 |
| LINE | 一阶/二阶邻近度 | 边权重重要的场景 |
| SDNE | 自编码器 | 非线性结构复杂的网络 |
我个人习惯用Node2Vec。为什么?因为它有p和q两个参数,可以控制游走是偏向BFS还是DFS,灵活性高。
from node2vec import Node2Vec
def compute_node2vec_factor(returns, threshold=0.3, dim=16):
"""
基于Node2Vec的图嵌入因子
dim: 嵌入维度,一般取8-32之间
"""
# 构建图
corr_matrix = returns.corr().values
adj_matrix = np.where(np.abs(corr_matrix) > threshold, 1, 0)
np.fill_diagonal(adj_matrix, 0)
G = nx.from_numpy_array(adj_matrix)
# Node2Vec嵌入
node2vec = Node2Vec(G, dimensions=dim, walk_length=30,
num_walks=200, p=1, q=2, workers=4)
model = node2vec.fit(window=10, min_count=1)
# 提取嵌入向量
embeddings = np.array([model.wv[str(i)] for i in range(len(G.nodes()))])
# 这里可以取第一维作为因子,或者用PCA降维到1维
factor = pd.Series(embeddings[:, 0], index=returns.columns)
return factor
4.4 三类因子的对比与选择
好,三类因子都讲完了。你可能会问:到底该用哪个?
我个人的经验是:
- PageRank因子:适合捕捉系统重要性,对大盘股、金融股效果较好
- 社区结构因子:适合捕捉板块效应,对行业轮动策略有帮助
- 图嵌入因子:最灵活,但可解释性最差,适合做机器学习模型的输入特征
嗯,这里要注意:图嵌入因子虽然强大,但它的可解释性是个大问题。你很难说清楚"第3维嵌入值高"到底代表什么。所以,如果做学术研究或者需要向领导解释,我建议优先用PageRank和社区结构因子。
最后,我画了一张图,帮你理清这三类因子的关系:
这张图把三类因子的定位和关系说清楚了。你写代码的时候,可以对照着这张图,想想自己到底需要哪一类因子。
好了,因子计算这块就讲到这里。记住,图因子不是银弹,它和传统因子是互补关系。我建议你在回测中把图因子和传统因子结合起来用,效果往往更好。
- PageRank因子:衡量系统重要性,适合大盘股
- 社区结构因子:捕捉板块效应,适合行业轮动
- 图嵌入因子:最灵活,但可解释性差
- 阈值选择、嵌入维度等参数需要仔细调优
公众号:蓝海资料掘金营,微信deep3321