一、课程导论:聚类算法在量化投资中的价值

各位同学好,我是你们这门课的主讲人。在正式开始之前,我想先聊聊一个很实际的问题——为什么我们要用聚类算法来做股票分类?

说实话,我刚入行做量化的时候,也踩过不少坑。那时候我习惯用行业板块来划分股票,比如银行股归一类、消费股归一类。但后来发现,同属「消费」板块的白酒股和乳业股,市场表现差异巨大。你想想看,茅台和伊利能放在一起分析吗?显然不行。

聚类算法的价值就在这里。它不依赖人为设定的标签,而是根据股票自身的特征——比如波动率、换手率、市盈率、动量因子等——自动把相似的股票归到一起。这样做的好处是:分类结果更客观,也更贴近市场的真实结构

我在做多因子选股策略时,就经常先用聚类把股票分成几类,然后对每一类分别构建因子模型。效果嘛,比一刀切的做法好不少。

核心价值总结:

  • 发现市场内在结构,而非依赖行业标签
  • 为多因子模型提供更精细的分组基础
  • 识别异常股票(比如庄股、妖股)
  • 辅助构建分散化投资组合

二、课程目标与学习路径

这门课一共30章,咱们今天讲的是第一章。整个课程的设计思路是:从数据到算法,从理论到实战

我个人习惯把学习路径分成三个阶段:

  1. 基础篇(第1-10章):数据获取、清洗、特征工程,以及K-Means、层次聚类等经典算法
  2. 进阶篇(第11-20章):DBSCAN、高斯混合模型、谱聚类等更复杂的算法,以及聚类效果评估
  3. 实战篇(第21-30章):把聚类应用到选股、风控、行业轮动等真实场景中

嗯,这里要注意一点:不要跳过数据准备部分直接去调算法。我曾经见过不少同学,拿到数据就急着跑聚类,结果因为数据没处理好,聚类结果一塌糊涂。数据质量决定了模型的上限,这话一点不假。

三、获取股票历史行情数据

做量化分析,第一步就是搞到数据。国内常用的数据源有两个:TushareAkShare。我个人更推荐初学者用 AkShare,因为它不需要注册 token,直接 pip install 就能用。

下面我演示一下如何获取沪深300成分股的历史数据:

import akshare as ak
import pandas as pd

# 获取沪深300成分股列表
stock_300 = ak.index_stock_cons_csindex("000300")
print(stock_300.head())

# 获取某只股票的历史日线数据
df = ak.stock_zh_a_hist(symbol="000300", period="daily", 
                        start_date="20230101", end_date="20231231")
print(df.head())

如果你用 Tushare,代码会稍微不同:

import tushare as ts
ts.set_token('你的token')
pro = ts.pro_api()

# 获取日线数据
df = pro.daily(ts_code='000300.SH', start_date='20230101', 
               end_date='20231231')
print(df.head())

小提示:AkShare 的数据是免费的,但更新频率和稳定性略逊于 Tushare Pro。如果是做高频策略,建议用 Tushare 付费版。我自己的生产环境用的是 Tushare Pro,但平时做研究用 AkShare 完全够用。

四、数据清洗与预处理

拿到原始数据后,千万别直接扔进聚类算法。真实数据有多脏,你根本想象不到。我曾经处理过一份数据,某只股票连续停牌三个月,中间全是 NaN,如果不处理,聚类结果直接崩掉。

4.1 处理缺失值

缺失值处理有三种常见方式:

  • 删除法:直接删除有缺失的行或列。适用于缺失比例很小的情况。
  • 填充法:用均值、中位数、前向填充等方式补全。
  • 插值法:用线性插值或时间序列插值。
# 检查缺失值
print(df.isnull().sum())

# 方法1:删除缺失值
df_clean = df.dropna()

# 方法2:用前一天的收盘价填充
df['close'] = df['close'].fillna(method='ffill')

# 方法3:线性插值
df['close'] = df['close'].interpolate(method='linear')

注意:千万不要用全局均值填充时间序列数据!股票价格有趋势性,用全局均值会引入严重偏差。我建议优先用前向填充或插值法。

4.2 去极值

股票数据里经常出现极端值,比如某天因为乌龙指导致价格暴涨100倍。这种数据如果不处理,聚类时会被当成一个单独的类别,影响结果。

常用的去极值方法有:

  • MAD法:基于中位数绝对偏差,稳健性较好
  • 百分位法:直接截断上下1%或5%的数据
  • 标准差法:超过3倍标准差的数据视为异常
def winsorize_series(s, limits=(0.01, 0.01)):
    """百分位去极值"""
    lower = s.quantile(limits[0])
    upper = s.quantile(1 - limits[1])
    return s.clip(lower, upper)

# 对收益率列去极值
df['pct_chg'] = winsorize_series(df['pct_chg'], limits=(0.01, 0.01))

4.3 标准化

聚类算法大多基于距离度量(比如欧氏距离)。如果特征量纲不同——比如市盈率是几十倍,而换手率是百分之几——那么市盈率会主导距离计算,换手率几乎不起作用。

所以,标准化是必须的。常用的方法:

  • Z-score标准化:减去均值除以标准差
  • Min-Max标准化:缩放到[0,1]区间
  • Robust标准化:基于中位数和四分位距,对异常值更鲁棒
from sklearn.preprocessing import StandardScaler, MinMaxScaler

# Z-score标准化
scaler = StandardScaler()
features = ['pe', 'turnover', 'pct_chg', 'volume']
df_scaled = df[features].copy()
df_scaled[features] = scaler.fit_transform(df_scaled[features])

# 或者用Min-Max
scaler_mm = MinMaxScaler()
df_scaled_mm = pd.DataFrame(
    scaler_mm.fit_transform(df[features]),
    columns=features
)

我的建议:如果数据中存在明显的异常值,优先用 RobustScaler。它不受极端值影响,比 StandardScaler 更稳定。我在处理A股数据时,90%的情况都用 RobustScaler。

五、本章知识体系总览

下面这张图总结了第一章的核心内容,从数据获取到预处理,每一步都是后续聚类分析的基础。

第一章:课程导论与数据准备 获取股票行情数据 处理缺失值 去极值处理 数据标准化 进入聚类分析 数据源:Tushare / AkShare 数据范围:沪深300成分股 缺失值处理:删除/填充/插值 推荐:前向填充或线性插值 去极值:MAD法/百分位法/标准差法 推荐:百分位法(1%截断) 标准化:Z-score/Min-Max/Robust 推荐:RobustScaler

这张图把本章的流程串起来了。你想想看,从数据获取到最终可以跑聚类,中间要经过缺失值处理、去极值、标准化三个关键步骤。每一步都有坑,但每一步也都有成熟的解决方案。

避坑指南:我曾经在去极值时用了3倍标准差法,结果因为数据本身分布偏态严重,把很多正常值也删掉了。后来改用MAD法,效果好了很多。所以,不要盲目套用方法,要根据数据分布特点来选择

好了,第一章的内容就到这里。数据准备好了,下一章我们就可以开始跑第一个聚类算法了。记住:数据清洗花的时间,会在模型效果上十倍地回报给你


蓝海数据掘金营,专注资料整理