我花了三个月,走完了整个模块3。
回头看——
这12篇讲的其实就是一件事:
怎么把一堆乱七八糟的金融数据,变成模型能吃的东西。
你可能觉得每篇拆开看都挺零碎的。
今天讲特征工程,明天讲归一化,后天讲PCA,大后天又讲缺失值……
学完之后——
"好像每篇都懂了。"
"但合在一起,还是不知道从哪开始。"
别急。
今天这篇,我不讲新知识。
我把这12篇串成一条线。
从你拿到一个空的CSV文件开始,到模型能跑出第一行结果为止——
中间每一步,该怎么走。
一张图:模块3的完整地图
先看全貌。
整个模块3,可以分成五个阶段:
阶段1:数据长什么样
↓
阶段2:怎么造因子(特征工程)
↓
阶段3:怎么让因子"干净"(预处理)
↓
阶段4:怎么降维压缩(特征选择)
↓
阶段5:怎么存、怎么取(数据工程)
每个阶段,对应几篇内容:
| 阶段 | 对应篇目 | 核心问题 | | --- | --- | --- | | 阶段1:认识数据 | 第38-40篇 | 金融数据和普通数据有什么不同?时间序列、平稳性、随机游走 | | 阶段2:造因子 | 第69-73篇 | 特征工程怎么做?动量、波动率、成交量、时间序列特征 | | 阶段3:洗干净 | 第74、78、79篇 | 归一化、去极值、缺失值处理 | | 阶段4:精简压缩 | 第75-76篇 | 特征选择、PCA降维 | | 阶段5:工程落地 | 第77篇 | CSV/Parquet/SQLite/HDF5怎么选 |
五个阶段。从认识数据,到工程落地。
这就是模块3的全部。
阶段1:先搞清楚数据的脾气
在第38篇,我说过一句话:
"金融数据不是普通的时间序列。它有记忆,但不多。"
这句话,到现在我仍然觉得是模块3最重要的起点。
你拿到一份股票数据,第一反应是什么?
画图。
画收盘价走势,画成交量柱状图,画收益率分布。
画完之后你会发现:
• 价格曲线像是在"随机游走"——昨天涨不意味着今天一定涨
• 收益率分布"胖尾巴"——极端涨跌比正态分布预测的要多
• 波动率会"聚类"——大的波动后面跟大的波动,小的跟小的
这些不是"有趣的观察"。
这些是数据的脾气。
你不了解这些脾气,直接套模型——
用线性回归预测价格?价格不平稳,回归全是假的。
用正态分布假设收益率?胖尾巴会让你的风险估计严重低估。
忽略波动率聚类?你的模型会在市场剧烈波动时全面崩溃。
阶段1的教训:金融数据有自己的规律,你得先尊重它,再建模。
关键知识点:
• 时间序列的特殊性(顺序不能打乱,数据点之间有依赖)
• 平稳性(均值和方差不能随时间漂移)
• 白噪声与随机游走(价格本身不可预测,可预测的是收益率的某些特征)
• ADF检验(怎么判断数据平不平稳)
阶段2:造因子——从原始数据到"有用的数字"
这是模块3最核心的部分。
第69篇讲过一个故事——
我把原始数据直接丢进模型,跑出来的结果一塌糊涂。
导师说:
"你见过厨师直接把原材料端上桌吗?"
原材料是食材。因子是菜品。
中间需要加工。
这个过程,就叫特征工程。
我们造了四类因子:
第一类:动量特征(第70篇)
过去N天的收益率。
// python
# 5日动量
df['momentum_5d'] = df.groupby('code')['close'].pct_change(5)
# 20日动量
df['momentum_20d'] = df.groupby('code')['close'].pct_change(20)
逻辑很简单:涨过的可能继续涨,也可能反转。
动量因子能不能用,取决于市场环境和回测验证。
第二类:波动率特征(第71篇)
过去N天收益率的标准差。
// python
# 20日历史波动率
df['volatility_20d'] = df.groupby('code')['daily_return'].transform(
lambda x: x.rolling(20).std()
)
波动率告诉我们:这只股票"脾气大不大"。
脾气大的股票,风险高,但机会也可能大。
脾气小的股票,稳,但可能也没什么弹性。
第三类:成交量特征(第72篇)
量比、OBV(能量潮)、资金流向。
// python
# 量比
df['volume_ratio'] = df['volume'] / df.groupby('code')['volume'].transform(
lambda x: x.rolling(5).mean()
)
价格告诉你"涨跌了多少"。
成交量告诉你"涨跌背后的力量有多大"。
量价配合才是完整的信号。
价格涨了,量也放大了——这是真涨。
价格涨了,量反而缩了——这是虚涨,小心反转。
第四类:时间序列特征(第73篇)
日期相关的特征——周几、月初月末、节假日前后。
// python
# 星期几
df['day_of_week'] = df['date'].dt.dayofweek
# 是否月初(前3个交易日)
df['is_month_start'] = df.groupby(['code', df['date'].dt.to_period('M')]).cumcount() < 3
看起来很简单。
但A股确实存在"周一效应"、"月末效应"。
这些不是迷信——是行为金融学的规律。
阶段2的核心收获:
因子不是越多越好。
每个因子都要能回答一个问题:"这个因子在捕捉什么信息?"
答不上来——这个因子就是噪音。
阶段3:洗干净——让因子变成模型能吃的样子
造完因子,不能直接喂模型。
因为模型对数据有"口味要求":
• 数值范围不能差太大(不然大数 dominate 小数)
• 不能有极端值(不然一个离群点带偏整个模型)
• 不能有空值(不然模型直接报错)
这就是阶段3——数据预处理。
归一化/标准化(第74篇)
把数据缩放到统一的范围。
// python
# 方法1:Min-Max归一化 → [0, 1]
df['momentum_norm'] = (df['momentum_5d'] - df['momentum_5d'].min()) / \
(df['momentum_5d'].max() - df['momentum_5d'].min())
# 方法2:Z-score标准化 → 均值0,标准差1
df['momentum_zscore'] = (df['momentum_5d'] - df['momentum_5d'].mean()) / \
df['momentum_5d'].std()
量化里更常用Z-score。 因为金融数据经常有负值,Min-Max归一化会把它压到0附近,失去信息。
还有一个坑:一定要按"训练集"的均值和标准差来标准化测试集。 不能用全局的——那就是前视偏差。
去极值(第78篇)
第78篇讲过一个故事——一个极端值487,让一个本来没用的因子"看起来"很有效。
去极值的方法:
// python
# MAD法(中位数绝对偏差)
median = df['factor'].median()
mad = (df['factor'] - median).abs().median()
upper = median + 5 * 1.4826 * mad
lower = median - 5 * 1.4826 * mad
# 缩尾处理(Winsorize)——不是删除,而是截断
df['factor_clean'] = df['factor'].clip(lower, upper)
缩尾 > 删除。 因为极端值虽然可能是噪音,但也可能是重要的信号。缩尾保留了"这个值确实很极端"的信息,只是不让它过度影响模型。
缺失值处理(第79篇)
三种类型,三种态度:
| 类型 | 原因 | 处理方式 | | --- | --- | --- | | 停牌日 | 无交易 | 前值填充(ffill) | | 数据源bug | 系统故障 | 删除或插值 | | 财务指标空值 | 非报告期 | 最近公布值填充 + is_missing标志 |
铁律:永远用ffill,永远不用bfill。 因为在t时刻,你只能知道t之前的数据。
阶段3的核心收获:
预处理不是"随便处理一下"。
每一步处理都要问:"我有没有用到未来的信息?"
如果有——你的回测就是假的。
阶段4:精简压缩——用更少的特征做更多的事
造了30个因子。
模型吃得消吗?
技术上——吃得消。
但实际上:
• 因子越多,越容易过拟合
• 很多因子之间高度相关(比如5日动量和10日动量)
• 模型训练时间随特征数量指数增长
所以你需要精简。
特征选择(第75篇)
三种方法:
// python
# 1. 过滤法:相关性筛选
correlation = df[['factor1', 'factor2', ..., 'return']].corr()
# 删除与目标变量相关性<0.02的因子
# 删除彼此相关性>0.95的因子(共线性)
# 2. 包装法:递归特征消除(RFE)
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
rfe = RFE(LinearRegression(), n_features_to_select=10)
rfe.fit(X, y)
# 3. 嵌入法:Lasso正则化
from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.01)
lasso.fit(X, y)
# Lasso会自动把不重要的特征系数压缩到0
PCA降维(第76篇)
如果你不想"选"特征——你可以"压缩"特征。
PCA把你的30个因子压缩成10个主成分。
每个主成分是原来30个因子的线性组合。
// python
from sklearn.decomposition import PCA
pca = PCA(n_components=10)
X_pca = pca.fit_transform(X_scaled)
# 看看前10个主成分解释了多少方差
print(f"累计解释方差: {pca.explained_variance_ratio_.sum():.2%}")
PCA的代价:可解释性丧失。
你原来的30个因子,每个都有明确含义——动量、波动率、成交量……
压缩成10个主成分之后,每个成分是什么含义?你不知道。
但模型不在乎。它在乎的是:数据干净、维度低、信息保留够。
阶段4的核心收获:
特征工程的上限,决定了模型的上限。
但特征不是越多越好——少而精 > 多而杂。
阶段5:工程落地——选对存储方案
最后一个阶段,最短,但不能忽略。
第77篇讲过——
当你有500只股票×3000个交易日×50个因子 = 7500万条数据。
你怎么存?
| 格式 | 大小 | 读取速度 | 适用场景 | | --- | --- | --- | --- | | CSV | 2GB | 慢 | 调试、分享 | | Parquet | 200MB | 快 | 日常回测 | | SQLite | 300MB | 中 | 需要SQL查询 | | HDF5 | 150MB | 最快 | 大规模计算 |
日常用Parquet。 压缩率高、读取快、Python生态兼容好。
// python
# 保存
df.to_parquet('factor_data.parquet', compression='snappy')
# 读取
df = pd.read_parquet('factor_data.parquet')
一行代码的事。但选错格式,你可能要多等10倍的读取时间。
完整流程:从0到1
现在,把所有阶段拼在一起。
假设你明天要跑一个选股策略。完整流程是这样的:
Step 1: 获取数据
→ 从Tushare/AKShare拉取日线+财务数据
→ 存入Parquet
Step 2: 认识数据
→ 画价格图、收益率分布、波动率时序
→ 做ADF检验,确认平稳性
→ 如果不平稳 → 差分
Step 3: 造因子
→ 动量因子(5/10/20日)
→ 波动率因子(20/60日)
→ 成交量因子(量比、OBV)
→ 时间因子(周几、月初月末)
Step 4: 洗数据
→ 缺失值处理(停牌ffill、成交量填0、财务指标+is_missing标志)
→ 去极值(MAD缩尾)
→ 标准化(Z-score,按训练集)
Step 5: 精简
→ 相关性筛选(删除高度共线的因子)
→ 或PCA压缩到10个主成分
Step 6: 建模
→ 线性回归 / XGBoost / LSTM
→ 训练集/测试集按时间切分(不能随机split)
Step 7: 回测验证
→ 计算夏普、最大回撤、年化收益
→ 检查有没有前视偏差
Step 8: 实盘/模拟盘验证
这就是模块3给你的东西——一条从原始数据到可执行策略的完整流水线。
模块3教会我的三件事
最后,说点私人的。
回顾这12篇,我觉得最重要的不是哪个技术细节——而是三个认知转变。
认知1:数据决定上限,模型只是逼近
你用了最复杂的深度学习模型,但数据全是噪音——结果不如一个用干净数据训练的线性回归。
花80%的时间在数据处理上,花20%的时间在模型选择上。
这个比例,对新手来说反直觉。但做多了你就知道——这才是真相。
认知2:前视偏差是最大的敌人
你的回测夏普2.0,实盘亏成狗。
90%的概率——是前视偏差。
用全局均值填充缺失值?前视偏差。
用bfill填充停牌日?前视偏差。
用测试集的标准化参数处理训练集?前视偏差。
随机split训练集测试集(不按时间)?前视偏差。
每一次数据处理,都问自己:"这个操作,在真实交易的时候,我能做到吗?"
做不到——就是前视偏差。
认知3:缺失和极端值不是噪音,是信号
新手看到NaN,第一反应:删掉。
新手看到极端值,第一反应:去掉。
老手看到NaN,会问:为什么空?
老手看到极端值,会问:这个极端背后发生了什么?
数据里的异常,往往藏着市场的情绪。
停牌可能意味着重大事件。
成交量为零可能意味着跌停封板。
PE为负可能意味着公司亏损。
这些信息,不是"脏数据"——是"珍贵信号"。
下一步:模块4预告
模块3讲完了。
你知道了怎么获取数据、造因子、清洗、降维、存储。
但这些——只是"备料"。
模块4,我们开始"做菜"。
模块4是经典策略解析。
从第81篇开始,我会一个一个拆解量化交易里最经典的策略类型:
• 动量策略(追涨杀跌的学问)
• 均值回归(高抛低吸的数学)
• 配对交易(两只股票之间的套利)
• 趋势跟踪(让利润奔跑)
• 统计套利(数学驱动的无风险收益?)
• 波动率策略(交易"不确定性")
• 日内策略(分钟级的博弈)
• 事件驱动(财报、并购、分红)
每个策略,我都会讲:
1. 核心逻辑(它赚的是什么钱?)
2. 数学原理(背后的公式不复杂,但很重要)
3. 参数选择(怎么调参数,怎么避免过拟合)
4. 优缺点(什么行情赚,什么行情亏)
5. 实战注意事项(回测容易踩的坑)
从模块4开始——
我们从"数据处理者",变成"策略开发者"。
准备好了吗?
OVER。