问题描述
解决方案
使用 networkx 分析股票间的关联是量化金融中“复杂网络”分析的一种常见应用。通过将股票视为节点(Nodes),将股票间的相关性(如收益率相关系数)视为边(Edges),我们可以构建一个市场网络图,从而识别出市场中的核心资产、板块轮动效应或潜在的风险传导路径。
以下是在 PTrade 平台中使用 networkx 进行股票关联分析的完整策略示例。
核心逻辑
- 数据获取:获取股票池(如上证50)的历史收盘价。
- 计算相关性:计算每只股票的日收益率,并生成皮尔逊相关系数矩阵。
- 构建网络:
- 设定一个阈值(例如 0.6)。
- 如果两只股票的相关系数大于该阈值,则在它们之间建立一条边。
- 网络分析:
- 使用度中心性(Degree Centrality):衡量一个节点与其他节点连接的紧密程度。度中心性最高的股票通常被认为是该板块的“领头羊”或市场情绪的核心。
- 交易决策:买入度中心性最高的股票(假设其最具代表性及上涨动力)。
PTrade 策略代码实现
import networkx as nx
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数
"""
# 设定股票池,这里以上证50为例,代表大盘蓝筹
# 注意:get_index_stocks 建议在 before_trading_start 调用,但为了初始化方便,
# 这里先定义一个全局变量存储代码,实际获取在盘前进行
g.index_code = '000016.SH'
g.stocks = []
# 设定相关系数阈值,只有相关性高于此值的股票才会被连接
g.corr_threshold = 0.6
# 设定持仓股票数量
g.target_hold_count = 5
# 设定历史数据窗口长度(用于计算相关性)
g.history_days = 60
# 每日运行一次
run_daily(context, trade_func, time='10:00')
def before_trading_start(context, data):
"""
盘前处理:获取成分股,构建网络,计算中心性
"""
# 获取上证50成分股
g.stocks = get_index_stocks(g.index_code)
if not g.stocks:
log.info("未获取到成分股信息")
return
# 1. 获取历史收盘价数据
# PTrade中,get_history获取多只股票单字段时,返回DataFrame,列名为股票代码
history_data = get_history(g.history_days, '1d', 'close', g.stocks, fq='pre', include=False)
if history_data is None or history_data.empty:
log.info("历史数据获取失败")
g.centrality_ranking = []
return
# 2. 计算日收益率
# pct_change() 计算 (现价-前价)/前价
returns_df = history_data.pct_change().dropna()
# 3. 计算相关系数矩阵
corr_matrix = returns_df.corr()
# 4. 构建 NetworkX 图
G = nx.Graph()
# 添加节点
G.add_nodes_from(g.stocks)
# 添加边:遍历相关系数矩阵
# 注意:矩阵是对称的,且对角线为1,我们需要过滤掉重复的和自身的
columns = corr_matrix.columns
for i in range(len(columns)):
for j in range(i + 1, len(columns)):
stock_a = columns[i]
stock_b = columns[j]
correlation = corr_matrix.iloc[i, j]
# 如果相关性大于阈值,则添加边
if correlation > g.corr_threshold:
# 将相关系数作为权重(可选)
G.add_edge(stock_a, stock_b, weight=correlation)
# 5. 分析网络:计算度中心性 (Degree Centrality)
# 度中心性越高,说明该股票与板块内其他股票的联动性越强
if len(G.nodes()) > 0:
degree_dict = nx.degree_centrality(G)
# 将字典转换为列表并排序 [(股票代码, 中心性数值), ...]
# Python 3.5 兼容写法
sorted_centrality = sorted(degree_dict.items(), key=lambda x: x[1], reverse=True)
# 提取排名靠前的股票代码
g.centrality_ranking = [item[0] for item in sorted_centrality]
log.info("今日网络关联度最高的股票前三名: %s" % str(g.centrality_ranking[:3]))
else:
g.centrality_ranking = []
def trade_func(context):
"""
交易函数
"""
if not g.centrality_ranking:
return
# 选取中心性最高的前N只股票
target_stocks = g.centrality_ranking[:g.target_hold_count]
# 获取当前持仓
current_positions = list(context.portfolio.positions.keys())
# 1. 卖出不在目标列表中的股票
for stock in current_positions:
if stock not in target_stocks:
order_target_value(stock, 0)
log.info("卖出非核心关联股票: %s" % stock)
# 2. 买入目标股票
# 简单的等权重分配资金
if len(target_stocks) > 0:
cash_per_stock = context.portfolio.portfolio_value / len(target_stocks)
for stock in target_stocks:
order_target_value(stock, cash_per_stock)
log.info("买入高关联度核心股票: %s" % stock)
def handle_data(context, data):
"""
盘中运行函数(本策略主要逻辑在 run_daily 中执行)
"""
pass
代码关键点解析
-
库的导入:
import networkx as nx:这是核心库。PTrade 环境通常内置了networkx(文档显示版本为 1.9.1),该版本较老,因此代码中使用了兼容性较好的基础方法(如G.add_nodes_from)。
-
数据处理:
get_history:一次性获取多只股票的收盘价。在 Python 3.5 环境下,当field为单个字段(如'close')时,返回的是 DataFrame,列索引为股票代码,这非常适合直接计算相关性。returns_df.corr():Pandas 内置方法,快速计算皮尔逊相关系数矩阵。
-
图的构建 (Graph Construction):
- 我们使用
nx.Graph()创建无向图。 - 双重循环遍历相关系数矩阵的上三角部分(避免重复计算
A-B和B-A),只有当correlation > g.corr_threshold时才添加边。这意味着我们只关注强相关的股票对。
- 我们使用
-
中心性指标 (Centrality):
nx.degree_centrality(G):计算节点的度中心性。在股票网络中,度中心性高的节点意味着它与大量其他股票同涨同跌,通常被视为市场风格的代表或系统性风险的敏感点。
扩展分析思路
除了上述代码中的度中心性,您还可以利用 networkx 进行更深度的分析:
- 最小生成树 (MST):使用
nx.minimum_spanning_tree(G)。在金融中,MST 常用于去噪,构建市场的骨干结构,观察资金如何在板块间流动。 - PageRank 算法:使用
nx.pagerank(G)。不仅考虑连接的数量,还考虑连接对象的质量(即与重要股票关联的股票更重要)。 - 社群检测 (Community Detection):识别网络中紧密连接的子图,这通常对应着实际的行业板块或概念板块(有时能发现跨行业的隐形关联)。
Q&A
Q1: 为什么选择相关系数作为边的权重?
A1: 相关系数直接反映了资产价格变动的同步性。正相关表示同涨同跌,负相关表示对冲。在构建网络时,通常关注高正相关性来寻找核心资产,或关注低/负相关性来构建分散化投资组合。
Q2: 阈值 g.corr_threshold 设置多少合适?
A2: 这取决于市场环境和股票池的大小。
- 在牛市或暴跌时,市场普涨普跌,相关性普遍较高,阈值应调高(如 0.7-0.8),以区分真正的核心关联。
- 在震荡市中,个股分化,阈值可适当调低(如 0.5)。
- 也可以使用动态阈值(例如取相关系数矩阵的前 10% 分位数)。
Q3: PTrade 中的 networkx 版本较老有什么影响?
A3: PTrade 文档显示支持 networkx 1.9.1。新版(2.x+)中很多方法的返回值从列表(List)变成了迭代器(Iterator/View),例如 G.nodes()。在编写代码时,尽量使用基础的图论方法,避免使用新版特有的语法。上述示例代码已考虑到这一点,使用了通用的写法。