
为什么要对股票做聚类分析
打开行情软件,几千只股票看得眼花缭乱。你可能会想,有没有办法把那些“脾气”差不多的股票归到一起?聚类分析干的就是这件事。它不需要你提前告诉机器哪只股票属于哪个行业,而是让算法自己根据数据特征把相似的股票聚成团。这对做轮动策略、构建投资组合或者单纯想找替代品都有实际意义。
举个例子,你持有某只消费股,想找一只走势逻辑相近的备选,手动翻看F10资料效率太低。用聚类分析跑一遍,同一簇里的股票大概率在涨跌节奏、波动幅度上高度同步。
获取股票数据
做聚类的前提是有干净的数据。A股数据可以用akshare或者tushare获取。这里以akshare为例,它免费且接口稳定。我们需要的是每只股票一段时期内的日线行情,通常取最近一年的收盘价序列。

import akshare as ak
import pandas as pd
# 获取沪深300成分股列表
hs300 = ak.index_stock_cons(symbol="000300")
codes = hs300['品种代码'].tolist()[:50] # 取前50只做演示
# 定义获取单只股票历史数据的函数
def get_stock_data(code, start_date="20230101", end_date="20231231"):
df = ak.stock_zh_a_hist(symbol=code, period="daily", start_date=start_date, end_date=end_date, adjust="qfq")
df['日期'] = pd.to_datetime(df['日期'])
df = df.set_index('日期')
return df['收盘']
# 批量获取并合并成一个DataFrame
price_data = {}
for code in codes:
try:
price_data[code] = get_stock_data(code)
except:
pass
price_df = pd.DataFrame(price_data)
price_df = price_df.dropna(axis=1, how='any') # 剔除数据不全的股票
print(price_df.shape)
特征工程:把走势变成可比较的数字
直接把收盘价丢给聚类算法会出问题——不同股票价格绝对值差异太大,高价股和低价股会被强行分开,但它们的走势形态可能很像。我们需要做两步处理:
收益率标准化:计算每日收益率,消除价格量级影响。
特征提取:用收益率序列的统计特征来代表一只股票,比如年化波动率、偏度、峰度、与大盘的相关性、换手率均值等。这些特征能捕捉股票的“性格”。
更直接的做法是把每只股票的收益率序列当成一个向量,计算股票之间的相关性距离。但向量维度太高(一年250个交易日),聚类效果会打折扣。所以通常先降维,再用聚类。
# 计算日收益率
returns_df = price_df.pct_change().dropna()
# 构建特征矩阵:每只股票一行,列为各种统计量
features = pd.DataFrame(index=returns_df.columns)
features['年化波动率'] = returns_df.std() * (252 ** 0.5)
features['偏度'] = returns_df.skew()
features['峰度'] = returns_df.kurt()
features['平均换手率'] = ... # 需要换手率数据,此处省略获取步骤
features['与大盘相关性'] = returns_df.corrwith(returns_df.mean(axis=1)) # 用等权平均代替大盘
# 标准化特征
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
features_scaled = scaler.fit_transform(features)
用KMeans给股票分组
KMeans是最常用的聚类算法,核心思想是让同一簇内的点尽量靠近,不同簇的中心尽量远离。需要提前指定簇的数量K。对于股票聚类,K取5到10比较合理,对应不同的风格板块。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
# 用肘部法辅助确定K值
inertia = []
for k in range(2, 11):
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(features_scaled)
inertia.append(kmeans.inertia_)
plt.plot(range(2, 11), inertia, marker='o')
plt.xlabel('簇数量')
plt.ylabel('簇内误差平方和')
plt.title('肘部法确定最佳K值')
plt.show()
# 假设选择K=5
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
clusters = kmeans.fit_predict(features_scaled)
features['簇标签'] = clusters
# 查看每个簇包含哪些股票
for i in range(5):
print(f"簇 {i}:", features[features['簇标签']==i].index.tolist())
聚类结果怎么用
聚类本身不产生买卖信号,它的价值在于辅助决策。
构建股票池:如果你看好某个行业但不确定选哪只,可以在该行业所属的簇里挑波动率适中、与大盘相关性较低的股票。
风险分散:持仓如果集中在同一簇,一旦风格切换容易集体回撤。聚类结果能帮你检查持仓的分散程度。
轮动策略:当某个簇整体涨幅落后时,可以关注簇内基本面没问题的个股,等待补涨。
配对交易:同一簇内走势高度相关的两只股票,价差拉大时存在统计套利机会。
别踩这些坑
数据频率:日线数据噪音大,用周线收益率聚类结果更稳定。
时间窗口:不同年份的聚类结果会变化,建议每季度重新跑一次模型。
特征选择:不要塞太多相关性高的特征,否则相当于给某个维度加权,聚类会失真。
缺失值处理:停牌多的股票直接剔除,否则会扭曲簇的形状。
聚类分析是量化投资的入门砖,它帮你把主观的“感觉像”变成客观的“数据像”。Python让这件事的门槛降到了会复制粘贴就能跑通的程度。跑完一遍,你对股票之间关系的理解会上升一个层次。
