ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python股票量化投资:从数据清洗到回测的完整路径

Python股票量化投资:从数据清洗到回测的完整路径

简介:这套课件面向希望系统入门Python股票量化投资的学员,覆盖从Python基础、Pandas/NumPy数据处理到技术指标、回测框架与自动化交易等核心环节,适合有一定编程基础、正在寻找实战项目参照的投资者自学或培训使用。压缩包共142个文件,以85个py源码脚本为主体,附带pyc编译文件、csv行情数据、xml配置、json参数及说明文档,可配合代码理解数据读取、策略构建与回测流程;整体约56.91MB,目录结构较规整,便于按模块检索。已有2393人学习下载。内容除了课件PPT与示例代码,还包含选股数据详细说明和自动交易准备工作文档,能帮助读者从数据清洗、指标计算到策略验证形成完整闭环,减少自行摸索的阻力,值得作为量化入门和练习的参考资料。

1. Python 股票量化投资的知识地图:这门课真正的学习顺序

很多人学 Python 股票量化投资,第一反应是去翻策略代码,觉得“写个买卖信号”就是全部。梳理这套课程配套课件的结构会发现,量化交易其实是一条完整的数据流水线:从行情数据接入、清洗对齐,到因子计算、策略信号,再到回测引擎、绩效评估和参数迭代,任何一个环节出错,最终结果都会被放大。整套课件里最有价值的部分,不是某一个策略的代码,而是把“数据 → 特征 → 信号 → 回测 → 评估 → 迭代”这条链路按工程化方式串起来。对于想系统学习量化交易课程、而不是零散看教程的人,这份地图能直接帮你定位自己缺在哪一环:是数据不会处理,还是回测逻辑有 bug,抑或评估指标看不懂。下面按模块把重点、难点和易错点逐一拆开。

2. 数据底座:行情、财务与清洗是对齐的起点

2.1 数据源的频率维度:日线、分钟线与 Tick 的取舍

量化交易的第一课不是策略,而是搞清楚数据在什么粒度上工作。行情数据按频率大致分三档:日线适合中低频策略和初学者验证逻辑;分钟线用来做日内或者高频调仓;Tick 级数据包含每笔成交的细节,数据量庞大,一般课程里只做了解。

频率典型用途数据量级主要难点
日线多因子、中长线趋势小,易处理复权、停牌、涨跌停
分钟线日内策略、盘口分析中等时间对齐、夜盘时段、撮合噪声
Tick高频、冲击成本研究极大存储、回放、微观结构噪声

课件里最容易被跳过的部分是分钟线的“时间对齐”。国内股票期货市场有集合竞价、午间休市和夜盘,不同交易所的交易时段不一样。如果直接把两个市场的分钟数据按时间轴拼接,会在边界上产生大量空值。多频率数据对齐要遵循“以事件时间为准,不以后台入库时间为准”的原则。梳理中我用过一个很笨但有效的办法:先画出某只股票连续三天的分钟收盘价折线,肉眼看有没有异常的跳变点,再回到数据源里排查,比单纯跑代码更能发现脏数据。

2.2 复权、停牌与幸存者偏差

行情数据的清洗里,复权是第一个绕不开的坎。股票分红送股后,价格会出现断崖式下跌,但持有者的资产并没有缩水,所以直接拿原始价格算收益率是错的。复权分为前复权和后复权:前复权保持最新价格不变,调整历史价格;后复权保持首发价格不变,调整后续价格。做回测时应使用前复权数据,因为回测是从历史往现在推,前复权数据能保证信号计算时不会引入未来信息。

import pandas as pd # 假设 df 包含 close(收盘价), factor(累计复权因子) # 复权因子的含义: 真实价格 = 原始价格 * factor df['close_adj'] = df['close'] * df['factor'] # 前复权处理: 以最新交易日为基准 latest_factor = df['factor'].iloc[-1] df['close_adj_forward'] = df['close'] * df['factor'] / latest_factor

这段逻辑里最关键的是“基准日”的选择。前复权数据会随着新除权事件的发生而整体变化,所以每次更新数据后,历史回测结果会发生细微变化,这是正常的,但如果你没有意识到这一点,就可能误以为自己的策略参数出了问题。最容易忽略的细节是:用前复权数据做回测时,计算的收益率其实包含了“复权因子变化”带来的校正,而因子本身是用分红送转信息推算的,数据源的因子计算口径不一致时,不同厂商之间会有显著差异。

停牌处理也是新手容易翻车的地方。停牌期间没有交易价格,数据里往往表现为连续重复上一日收盘价或直接缺失。回测引擎如果对停牌股票按“当日涨跌幅 0”处理,会引入严重的未来函数——因为复牌当天的价格跳空是停牌期间信息的集中释放,正确做法是对停牌超过一定天数的样本直接剔除或延迟到复牌后再纳入。

2.3 财务数据的时点对齐:避免未来函数的第二高发区

财务数据比行情数据更隐蔽。财报公布的营收、净利润属于“资产负债表日”的数据,但真正可用于交易的是“公告发布日期”。一个经典的错误是:用 2024 年年报的数据去回测 2024 年 12 月的行情,因为年报往往在次年 3 月才披露,这就引入了未来数据。任何财务因子的计算,都必须以公告日期为准,同时注意业绩预告、快报和正式报告的发布时间差异,课件里专门强调过这一点,但实操中还是会反复踩坑。

财务数据本身的口径也值得留意。合并报表和母公司报表的口径不同,多数选股因子应使用合并报表数据;净利润的“扣非”与“不扣非”差异在 ST 股和资产重组股上极其明显。对比多个数据源时,如果不统一口径,同一个因子的取值可能天差地别,最终导致因子测试结果不可重复。

3. 策略体系:趋势、均值回归与多因子

3.1 趋势跟踪:均线系统和动量因子的本质

趋势策略的逻辑很简单:价格有惯性,涨的股票倾向于继续涨,跌的股票倾向于继续跌。均线交叉是入门第一课,但真正要理解的是“均线是低通滤波器”这层含义。短周期均线反映快速变化,长周期均线反映慢速变化,金叉死叉本质上是快慢信号的相对变化。实际使用中,均线系统存在震荡市里的反复止损问题,所以更稳健的做法是用 突破类信号(如唐奇安通道)或动量因子。

import pandas as pd import numpy as np df['ma_fast'] = df['close_adj'].rolling(window=5).mean() df['ma_slow'] = df['close_adj'].rolling(window=20).mean() df['signal'] = 0 df.loc[df['ma_fast'] > df['ma_slow'], 'signal'] = 1 # 多头持仓 df.loc[df['ma_fast'] < df['ma_slow'], 'signal'] = 0 # 空仓 # 计算策略每日收益: 信号 * 当日收益率 df['ret'] = df['close_adj'] / df['close_adj'].shift(1) - 1 df['strategy_ret'] = df['signal'].shift(1) * df['ret']

注意shift(1)的使用,这是防止未来函数的关键,信号必须用上一交易日收盘时的信息,当天收盘后才能下单,实际成交价按下一日开盘价处理。回测时如果不 shift,等于你提前知道当天涨跌,策略收益会虚高到离谱,这是新手回测翻车的第一大原因。

3.2 均值回归:布林带、RSI 和配对交易

均值回归策略的前提假设是“价格会回到中枢”,适合震荡市,与趋势策略正好互补。布林带策略用中轨加标准差构造上下轨,价格触下轨买入、触上轨卖出。但这类策略在单边趋势里会不断抄底接飞刀,所以课件里往往会强调:均值回归策略必须搭配趋势过滤条件,比如只在价格位于长期均线上方才做多。

RSI 指标是另一个常见的均值回归工具。RSI 低于 30 视为超卖,高于 70 视为超买。它的计算窗口长度直接影响灵敏度,短窗口 RSI 更敏感也更容易误报,长窗口更平滑但反应慢。课件里的处理方式值得借鉴:不单独用 RSI 数值,而是结合“RSI 值与价格走势的背离”来判断反转,这种方法可以明显减少假信号。

配对交易是更进阶的均值回归玩法,通过两个高相关性标的的价格差(价差)来交易。这里最容易忽略的是协整性检验与相关性检验的区别:高相关性不代表价差会回归,必须做协整检验,且价差序列的平稳性要定期重检。用滚动窗口计算协整关系会比全样本一次性计算更贴近实战。

3.3 多因子选股:分层回测、IC 与因子正交化

多因子选股是课件中后期的主干内容,本质是把“选股”从主观判断变成“打分排序”。构建流程可以拆成四步:因子定义 → 因子预处理(去极值、标准化、中性化) → 因子测试(IC、分层回测) → 因子合成。每一步都有专门的检验方法,不可以跳过。

# 计算因子 IC: 因子值与下期收益的 Spearman 相关系数 from scipy.stats import spearmanr # factor: 某因子截面值, next_ret: 下一期收益 ic_series = [] for date, group in data.groupby('date'): if len(group) < 30: # 样本量太少时 IC 不稳定 continue ic, _ = spearmanr(group['factor_value'], group['next_ret']) ic_series.append(ic) ic_mean = np.mean(ic_series) ic_ir = ic_mean / np.std(ic_series) # IC_IR: 因子稳定性指标

因子预处理里,去极值(MAD 法或分位数法)解决的是异常值对横截面排序的影响;标准化让不同量纲的因子可以比较;行业中性化和市值中性化则是为了剔除行业与市值风格的干扰,让因子真正反映自身选股能力。中性化通常用回归取残差的方式实现,课件里大概率会给出用某统计库做横截面回归的代码,实操时要注意回归必须按每个调仓周期分别做,而不是全样本混在一起做,否则又会引入未来信息。

因子测试通过后,合成环节的坑也很多。多个因子之间往往存在共线性,直接等权相加会放大共同因子暴露。用 IC 加权或 IC_IR 加权比等权更合理,但要防止因子在样本内被过度拟合。更稳健的做法是正交化后等权——先剔除因子间的公共部分,再对纯增量信息打分。这个细节决定了因子组合在样本外是否还能保持稳定表现。

4. 回测引擎:信号、执行与绩效评估的完整闭环

4.1 信号与执行分离的回测框架

一个能用的回测引擎,至少要区分三件事:信号生成、仓位管理、撮合执行。很多自写回测代码把这三步混在一个循环里,虽然简单,但很难扩展出滑点模型、手续费模型和资金管理。课件里推荐的模式是“信号函数独立 + 回测引擎统一调度”,每一期先根据历史数据算出目标仓位,再交给执行模块按当日开盘价或 VWAP 成交。

def generate_signal(data, params): """根据历史数据计算目标仓位, 返回 0~1 之间的权重""" signal = ... return signal / signal.abs().sum() # 组合内等权分配 def backtest(data, params): position = 0 equity = 1.0 for i in range(len(data)): if i == 0: continue target = generate_signal(data.iloc[:i], params) # 只用历史数据 # 按当日开盘价执行, 扣手续费与滑点 trade_price = data.iloc[i]['open'] * (1 + slippage) fee = abs(target - position) * fee_rate ... return equity_curve

这个框架里,generate_signal每次只能看到截至前一日的数据,这是回测代码与生产代码共用的同一套逻辑——只要回测时不 shift,上线时也容易不 shift,到时候实盘和回测的巨大差异会让你很痛苦。

4.2 交易成本模型:手续费、印花税与滑点

交易成本是回测中最容易被低估的变量。常见处理方式有固定比例手续费、最低佣金、印花税、滑点。国内股票卖出要缴印花税,双边手续费因券商而异,滑点则与流动性相关,小市值股票的冲击成本远高于大市值。课件里通常会给出“万分之几”的默认参数,但更重要的是能看出这个默认值是否合理。

滑点建模有三种常用假设:固定滑点(每笔交易额外损失 X 个基点)、比例滑点(按成交金额的百分比扣减)、基于流动性的动态滑点(按盘口深度推算)。初学者用固定滑点就可以验证策略逻辑,但最终提交的报告里必须把滑点从 0 逐步加到 1% 左右,绘制“收益随成本变化”的敏感性曲线。一个成本从万 1 涨到千 1、收益就从年化 30% 变成 5% 的策略,在实盘里基本不可用——这不是策略问题,是容量问题。

4.3 绩效指标怎么看:年化、回撤、夏普与卡玛

指标计算方式用途常见误区
年化收益率累计收益率的年化转换衡量绝对收益不结合波动率看没意义
最大回撤净值从峰值回落的最大幅度衡量极端风险只看数值不看回撤持续天数
夏普比率(组合收益 - 无风险利率)/收益标准差衡量风险调整后收益对非正态收益分布失真
卡玛比率年化收益 / 最大回撤衡量收益回撤性价比回撤时间短但频率高时失真

夏普比率的问题是它对正负波动一视同仁,而投资者只介意下行波动。所以实操里还要看索提诺比率(下行标准差替代总标准差)和 Sortino。最大回撤的持续时长(回撤修复天数)同样重要,一个回撤 20% 但一个月修复的策略,比回撤 15% 却三年没修复的策略体验好得多。做策略对比时,不要只看收益排名,要把最大回撤和卡玛比率排进去一起看。

4.4 参数优化与防过拟合:寻参、交叉验证与参数高原

参数优化这一节课件的重点是“参数高原思维”。网格搜索在二维平面上找最优参数组合,表面上很简单,但样本内最优通常落在“孤峰”上,参数稍微偏离一点就性能骤降;稳健的策略参数应该落在“高原”上——一个连续区域内的表现都接近最优,意味着策略对参数不敏感。判断方法是画出参数热力图,看最优参数周围是否存在渐变梯度,而不是一个孤立亮点。

更进一步是 Walk-forward 分析(滚动前进式样本外测试):把时间轴切成交叠的训练段与测试段,先在训练段寻参,再到紧随其后的测试段验证,逐步前移。课件里的核心提醒是:测试段绝不能包含训练段数据,且训练段和测试段的长短要与策略调仓周期匹配。用这种方法做出来的样本外收益,才有可能逼真实盘表现。

5. 量化交易中容易忽略的五个致命细节

5.1 未来函数:出现在信号计算与交易执行边界

易漏点:回测代码在计算当日信号时使用了当日收盘价,但买入价也按当日收盘价成交,信号与成交发生在同一时间。为什么漏:因为行情数据里只有 OHLC,初学者天然觉得“当天收盘后就能知道收盘价”,但实际下单是在收盘前决策、次日执行。怎么补:信号计算强制使用shift(1),成交价使用次日开盘价或当日收盘价 + 滑点。每次写完回测先跑一遍“未来函数自查”:把策略收益的累计曲线和标的涨幅曲线叠加,如果空头年份还能稳定正收益,基本可以断定有泄漏。

5.2 幸存者偏差:成分股清单随时间变化

易漏点:用“当前时间的指数成分股”回测过去十年,回测结果远好于真实可得的收益。为什么漏:指数成分股会定期调整,表现差的公司被剔除,留下来的都是“幸存者”,买入过去十年的历史成分股组合,实际上等于开天眼。怎么补:回测必须使用“时点有效的股票池”,即每一个调仓日只使用当时还在上市且未停牌的股票,并记录当时的成分股清单。如果找不到历史清单,就要在报告中明确承认这一偏差,并评估其对收益的影响幅度。

5.3 交易成本漏算:印花税、停牌期间的“纸面收益”

易漏点:只算了佣金,忽略印花税和滑点;停牌股票在复牌当日的跳空收益被计入策略收益。为什么漏:因为回测框架里仓位调整直接按价格差计算盈亏,没有模拟“停牌期间无法交易”的限制。怎么补:为持仓股票维护一个“可交易状态”标记,停牌时禁止调仓;在成本模型中加入印花税并按双边滑点打折——即便打折,也要把成本跑一遍敏感性测试。

5.4 复权因子更新的“回测波动”

易漏点:每次更新行情数据后,同一策略回测结果明显变化,误以为是代码 bug。为什么漏:前复权数据在每次新增除权事件后都会重新计算历史价格,导致信号序列微调,策略对边界信号敏感时结果自然变化。怎么补:把策略代码与数据版本解耦——数据下载时记录数据源版本号和复权基准日期,回测结果附带数据指纹。评估策略时采用“固定一个数据切片”,不要在未标明数据版本的情况下反复调参。

5.5 信号频率与交易频率错配:换手率失控

易漏点:策略日频产生信号,但换仓周期是周频,回测时把两种频率混在一起模拟,导致换手率虚高、交易成本被低估。为什么漏:信号频率(计算频率)和调仓频率(实际下单频率)是两回事,许多初学者在回测里用每日信号逐日调仓,但实盘根本做不到或成本无法承受。怎么补:在回测框架中显式定义调仓周期,以实现调仓日的信号为准;输出净值曲线的同时统计年化换手率,换手率超过某阈值(比如 10 倍/年)就要审视策略是否在追逐噪声。

6. 复习清单与查漏补缺:把课件变成自己的检查表

整套课程学完后,真正留在手中的不是代码文件,而是下面这张自查表。我现在每跑一个新策略,都会按这张表逐项过一遍,你可以直接把它当成复习清单。

检查项具体问题通过标准
数据质量复权因子是否最新;停牌日是否标记抽查 3 只股票无异常跳变
未来函数信号与成交时间是否分离信号shift(1)后收益不骤降
股票池是否使用时点有效成分股历史调仓列表可追溯
成本模型佣金、印花税、滑点是否全含滑点敏感性曲线已生成
绩效指标是否看全收益、回撤、夏普、卡玛与基准对比有明显超额
稳健性参数是否落在“高原”区域网格热力图已观察,非孤峰

具体的查漏补缺方法上,我最推荐一条“强制复盘制度”:每完成一版回测,先写三句话说明收益来源(赚的是趋势的钱、回归的钱,还是承担了某种风险),再写一个“这个策略在什么市场环境下会失效”的假设。如果写不出来,说明你对策略的理解还不够,回测再好看也没用。这是我自己的血泪经验——曾经做一个动量因子策略,样本内表现极好,但后来仔细分析发现收益全部集中在某几个行业,一旦行业轮动转冷,策略就连续六个月跑输基准,后来才补上了行业中性化这一步。

从那以后,我每次跑完回测都强制自己走一遍这张清单,不看完整就不允许自己进入下一轮参数调整。尤其对初学者,把“回测结果很完美”当作危险信号,而不是兴奋信号,反而是进步的开始。希望帮到你。

获取这份课件的完整资源后,建议先跳过所有策略代码,从数据清洗章节开始读,花两天时间把示例代码手动重写一遍,你会明显发现自己对“未来函数”和“复权”的敏感度提升了。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进