简介:这份Python股票量化投资课程配套课件,定位为零基础到进阶的量化交易学习资料,面向想用Python进行金融数据分析、策略构建与自动化交易的学员,可系统补齐编程、数据、策略、回测、风控与机器学习全链路能力。资源压缩包共142个文件、约56.91MB,核心是85个.py可运行脚本与21个.pyc编译文件,配合9个.csv股票行情数据、12个.xml配置、3个.zip依赖包及若干说明文档,形成从数据到代码的完整配套。目前已有2393人学习下载,是被量化初学者反复参考的一套课件。内容覆盖Pandas/NumPy数据处理、Matplotlib可视化、Yahoo Finance等数据获取、移动均线与布林带指标、Backtrader回测、经纪商API自动交易、止损与资金管理,以及机器学习预测实战;压缩包内还提供A股历史行情CSV与选股数据详细说明,便于直接运行演练,从数据读取到策略回测一气呵成。
1. 量化投资的知识地图:Python、股票与策略怎么接起来
很多人学 Python 股票量化投资会遇到同一个卡点:语法书看了两遍,但不知道整条路怎么走。课程配套课件的价值,不是把某个指标公式贴出来,而是把量化交易从头到尾拆成一条可执行的生产链路:数据获取与清洗、因子计算、策略信号、回测验证、绩效归因、风险控制。这条链路里,数据是地基,因子是武器,回测是照妖镜,风险控制则是活下来的前提。
这份课件适合两类人:一是已经掌握 Python 基础、想系统进入量化交易方向的学习者,二是做过简单回测但总被“回测很漂亮、实盘很惨淡”困扰的复习者。复习时不需要逐条重读公式,只要抓住主线,就能判断自己漏在哪一环。把整门课想象成一条流水线,位置越靠前,错误越致命:数据一旦带错,后面所有因子和回测都是空中楼阁。知识地图的顺序,就是复习的顺序。
2. 数据是第一关:行情、财务与预处理怎么做
进入量化学习后的第一个“劝退点”通常是数据。很多人以为拿到收盘价就能开始算信号,结果在处理复权、停牌、公告日期时不断翻车。梳理下来会发现,数据层解决的问题就三个:拿什么数据、数据长什么样、哪些数据不能直接用于回测。
2.1 先想清楚:行情数据要什么样的列结构和频率
规范的行情表不是 Excel 那样的宽表格,而是长表。每一行对应一个股票在一个交易日的记录,核心字段包括:
| 字段 | 含义 | 注意事项 |
|---|---|---|
| symbol | 股票代码 | 统一格式,不要混用带前缀和不带前缀 |
| date | 交易日期 | 必须是datetime类型,不要用字符串 |
| open / high / low / close | 开高低收 | 复权前或复权后要统一 |
| volume | 成交量 | 确认单位是“股”还是“手” |
| amount | 成交额 | 做流动性过滤时很有用 |
拿到数据后的第一步不是画 K 线,而是做排序、去重、类型修正:
import pandas as pd df = pd.read_csv("daily.csv", parse_dates=["date"]) df = df.sort_values(["symbol", "date"]).reset_index(drop=True) df = df.drop_duplicates(subset=["symbol", "date"], keep="last") df["ret"] = df.groupby("symbol")["close"].pct_change()代码里最关键的是pct_change(),它计算的是相邻两行之间的收益率。若中间有缺失交易日,它不会自动跳过缺失行,而是在出现 NaN 的位置直接给到缺失收益。更严谨的做法是先用交易日历对齐,再决定填充策略。频率上,日线足够覆盖选股和低频择时,分钟线适合日内策略,但初期不建议直接上分钟级,先跑通日线再说。
2.2 复权口径:前复权、后复权与回测的对应关系
复权问题是数据层最大的坑。除权除息会让价格出现人为跳空,如果不复权,收益率的计算就会失真。前复权以最近价格为基准调整历史价格,适合看图;后复权以最早日期为基准,适合计算真实收益率。回测中计算信号和收益率,应该统一使用后复权价格,否则分红次数多的股票会被系统误判为暴跌。
代码里通常这样处理:
df["adj_close"] = df["close"] * df["adj_factor"] df["ret_adj"] = df.groupby("symbol")["adj_close"].pct_change()adj_factor是行情源提供的复权因子,它通常是累乘的,拿来直接乘原始收盘价即可。最容易漏的一点是:前复权价格会随着新行情发布而整体变化,今天算出的前复权历史值和下周算出的不一样。因此,研究过程中的数据快照要固定版本,不要每次回测都重新下载,否则回测结果无法复现。
2.3 预处理三坑:缺失值、停牌、零成交
缺失值不能无脑删除,也不能无脑前向填充。停牌期间没有成交,本来就该没有价格记录;如果你用ffill()把上一交易日的价格延续到停牌日,回测引擎就会在停牌日“假装”成交,这是典型的隐性未来函数。
正确处理是保留交易状态信息:
df["tradable"] = df["volume"] > 0 df["can_buy"] = df["tradable"] & (df["ret"] < 0.095) df["can_sell"] = df["tradable"] & (df["ret"] > -0.095)这里用涨跌幅近似过滤涨跌停无法买入卖出的情况:涨幅接近 10% 时买单大概率无法成交,跌幅接近 10% 时卖单大概率无法成交。阈值是示意,创业板、科创板需要换挡。更准确的方案是让行情源直接提供涨跌停价格字段,计算前先判断当日能否成交。
2.4 财务数据不能用报告期对齐:用公告日期
年报、季报发布存在明显滞后。如果只按“报告期”把财务数据和行情合并,等于在 4 月末就使用了 3 月末才发布的季报数据,这在回测里是未来函数。正确做法是以公告日期为信息生效日,让因子在公告发布之后才“看见”数据。
fin = fin.sort_values("anno_date") price = price.sort_values("date") merged = pd.merge_asof( price.sort_values("date"), fin, left_on="date", right_on="anno_date", by="symbol", direction="backward", )merge_asof会为每个交易日找到最近一次已经公告的财务数据,direction="backward"表示只取早于或等于当前日期的记录。这个方向一旦写反,就变成了未来数据。数据层最后应该有一份自查脚本,检查每个股票的最短历史长度、缺失值比例、复权因子是否单调,以及merge_asof之后是否有空值。数据没有问题,后面的策略才有讨论意义。
3. 指标与因子:从均线到多因子选股的转化
策略层是另一个学习分水岭。很多人以为把 MACD、RSI、布林带凑在一起就是量化策略,实际上技术指标只是信号的一种来源。更系统的做法是先理解指标的计算口径,再从单因子到多因子一步步建立选股逻辑。
3.1 技术指标的计算口径:均线、MACD、布林带
指标计算在 Pandas 里几乎是模板化操作,但有一个共性要求:按股票分组后计算,不能把不同股票混在一个序列里滚动。
df = df.sort_values(["symbol", "date"]) df["ma5"] = df.groupby("symbol")["adj_close"].transform( lambda x: x.rolling(5).mean() ) ema12 = df.groupby("symbol")["adj_close"].transform( lambda x: x.ewm(span=12).mean() ) ema26 = df.groupby("symbol")["adj_close"].transform( lambda x: x.ewm(span=26).mean() ) df["macd"] = ema12 - ema26 df["macd_signal"] = df.groupby("symbol")["macd"].transform( lambda x: x.ewm(span=9).mean() )transform的作用是让计算结果保持原索引对齐,不会因为分组而打乱行序。滚动窗口的前 N 行一定会产生 NaN,后续生成信号前必须dropna(),否则 NaN 会被当成空仓或买入信号。布林带就是均线加减两倍标准差,核心不是指标本身,而是“突破”和“回归”两种状态分别对应趋势策略和均值回归策略。同一个指标,放在不同市场状态下含义完全不同。
3.2 因子与未来收益:IC、分组回测与因子方向
因子的本质是排序工具。它不直接告诉你买什么,而是告诉你哪些股票应该排在前面。判断一个因子有没有效,不能只看收益曲线,要看因子值和未来收益之间的单调关系。
信息系数 IC 是因子与未来 N 期收益的相关系数,通常用 Spearman 秩相关计算:
from scipy import stats def factor_ic(df, factor_col, fwd_ret_col): ic_by_date = df.groupby("date").apply( lambda g: stats.spearmanr(g[factor_col], g[fwd_ret_col])[0] ) return ic_by_date.mean(), ic_by_date.std() ic_mean, ic_std = factor_ic(df, "zscore", "fwd_ret_5") icir = ic_mean / ic_std * np.sqrt(52)IC 均值为正且稳定,说明因子有预测力;ICIR 是经波动调整后的指标,用来判断因子稳定性。只看 IC 均值不够,一个因子可能均值很高但方向经常反转,实盘根本无法信任。更直观的验证是分组回测:把每日股票按因子值分成 5 组,每组建仓后持有 N 天,观察收益是否随因子值单调变化。如果第 3 组收益最高、第 1 组次之、第 5 组最低,因子很可能只是噪声,而不是有效排序。
3.3 多因子合成:标准化、中性化与合成
单因子容易失效,多因子合成可以分散风险,但前提是合成方法正确。不同因子的量纲不同,直接相加等于给量纲大的因子更高权重。正确做法是先在每个交易日截面上做标准化。
df["zscore"] = df.groupby("date")["factor"].transform( lambda x: (x - x.mean()) / x.std() )标准化之后还要做行业中性化。如果一个因子只是选出了某个行业的一堆股票,那它捕捉的可能不是 alpha,而是行业 beta。最简单的中性化是减去行业均值:
df["factor_neutral"] = ( df.groupby(["date", "industry"])["zscore"] .transform(lambda x: x - x.mean()) )行业分组股票数量太少时,组内均值会不稳定,这时可以考虑行业哑变量回归取残差。市值中性化思路类似,把因子值对市值做回归,用残差代替原始因子值。中性化不是可选项,而是多因子研究的标准动作。跳过这一步,后面所有分析结果都会带上一层隐性的行业或市值风格。
3.4 从因子到策略:打分、选股与择时的配合
单因子研究最终要落到一篮子股票。常见的落地方案是每个调仓日按因子 zscore(或中性化后分值)排序,取前 N 只等权买入,持有到下一个调仓日。这里面有两个容易被忽略的量:调仓频率和换手成本。
signal = df.sort_values(["date", "zscore"], ascending=[True, False]) signal = signal.groupby("date").head(20) targets = signal[["date", "symbol"]].copy()每天调仓会让手续费和滑点吃掉大量收益,更常见的是周频或月频调仓。技术指标(如指数均线)可以作为择时过滤条件,市场整体处于下跌趋势时降低仓位或空仓,这比单独使用某一种指标更稳健。到这里,一个完整的策略已经从“数据”走到了“信号”,下一步是回测验证。
4. 回测引擎与绩效分析:别让图表骗了你
回测是量化学习中最容易被“自欺”的环节。初学者看到一段漂亮的净值曲线就以为策略有效,实际上回测框架的一个小错误,可能让结果严重失真。这一章从最小回测框架开始,把绩效指标和常见偏差一起理清。
4.1 最小可用的回测框架:逐笔结算还是逐 bar 结算
最简单的回测逻辑是:在每个交易日,先结算持仓市值,再按信号决定买卖。
from dataclasses import dataclass @dataclass class Position: symbol: str shares: int def run_backtest(daily, select_func, initial_cash=1_000_000): cash = initial_cash positions = [] timeline = sorted(daily["date"].unique()) for date in timeline: today = daily[daily["date"] == date] for pos in positions: px = today.loc[today["symbol"] == pos.symbol, "close"].values if len(px) > 0: pos.last_price = px[0] targets = select_func(today) # 先卖后买,简化处理,但要有滑点与手续费 for pos in list(positions): if pos.symbol not in targets: cash += pos.shares * today.loc[ today["symbol"] == pos.symbol, "close" ].iloc[0] positions.remove(pos) for symbol in targets: if symbol not in [p.symbol for p in positions]: price = today.loc[today["symbol"] == symbol, "close"].iloc[0] shares = int(cash * 0.1 // price) cash -= shares * price positions.append(Position(symbol=symbol, shares=shares)) equity = cash + sum(p.shares * p.last_price for p in positions) yield date, equity这个框架里至少有两个问题需要明确:第一,这里用“当日收盘信号、当日收盘价成交”,在严格回测中属于前视偏差,相当于你看到了收盘价才决定买入,却又按收盘价成交;第二,没有手续费和滑点,收益会虚高。更稳妥的做法是信号在收盘后产生,成交放在次日开盘价。框架可以先简后严,但心里要清楚它偏乐观。
4.2 绩效指标的计算口径:年化、最大回撤、夏普比率
净值曲线出来后,第一件事不是画图,而是算四个核心指标。
equity = pd.Series([e for _, e in results], index=[d for d, _ in results]) daily_ret = equity.pct_change().dropna() total_ret = equity.iloc[-1] / equity.iloc[0] - 1 days = len(equity) annual_ret = (1 + total_ret) ** (252 / days) - 1 roll_max = equity.cummax() drawdown = equity / roll_max - 1 max_dd = drawdown.min() excess = daily_ret - 0.02 / 252 # 假设无风险年化2% sharpe = np.sqrt(252) * excess.mean() / excess.std()年化收益率不是简单地把总收益除以年数,而是按 252 个交易日做复利折算。最大回撤要看净值峰值到谷底的跌幅,而不是只看某一段收益波动。夏普比率对异常值非常敏感,日收益标准差偏小时,夏普会被算得异常高,最好同时看最大回撤和收益回撤比。
| 指标 | 计算口径 | 常见错误 |
|---|---|---|
| 年化收益率 | 对总收益做 252 日复利折算 | 直接总收益/年份 |
| 最大回撤 | 净值峰值到后续最低点的最大跌幅 | 用收益率序列代替净值 |
| 夏普比率 | 日超额收益均值/标准差 再年化 | 忘记减无风险利率 |
| 胜率 | 盈利交易次数/总交易次数 | 胜率高但盈亏比很低 |
| 盈亏比 | 平均盈利/平均亏损 | 单独使用容易误判 |
4.3 回测偏差自查清单
回测结果异常漂亮时,先别高兴,按下面的清单逐一排查。
第一,幸存者偏差。股票池只包含“今天还活着”的股票,历史上已经退市的股票被排除在外,回测自然会偏向乐观。复盘时要加入历史退市股,或者用全量历史成分股。
第二,前视偏差。未来数据进入了信号计算,比如用当日收盘价生成当日信号,再按当日收盘价成交。排查方式是检查所有信号计算中使用的数据,是否都在信号生成时刻之前。
第三,过拟合。策略参数反复调整到与历史完美匹配,换一段样本外数据立刻失效。应对方法是做滚动训练测试,或用参数敏感性分析,观察邻近参数下绩效是否剧烈波动。
第四,成本缺失。双边手续费、印花税、滑点全部设为 0,高频调仓策略在回测里会显得异常优秀。实际回测至少应计入双边万三到千一的手续费,以及一个最小滑点。
5. 最该警惕的易错点:量化交易里的“黑匣子”陷阱
学完数据、因子和回测之后,靠前的坑大多能避开,但有一些错误藏在代码细节里,属于“看着没问题,跑起来全错”的类型。梳理整个课程内容,我总结出五个最容易漏掉、也最值得反复检查的易错点。
5.1 未来函数:回测悄悄用了未来的数据
易漏点:信号生成时没有注意数据的时间边界,最常见的是“用当日收盘价计算信号,再用当日收盘价成交”。
为什么漏:因为逐行处理时,同一行里既有价格又有信号,代码不会报错,逻辑上也浑然不觉。尤其是在计算因子时,如果因子值来自未来 N 天的收益,那整个回测都是自欺欺人。
怎么补:写一个统一的工具函数,把所有信号统一到“信号生成日”的次日处理。每次回测前做一次数据泄漏检查:把因子值随机打乱,如果绩效不变差,说明原有因子可能是噪声;如果把因子滞后一天后绩效显著变差,就要警惕未来函数。简单有效。
5.2 复权口径混用:前复权数据算收益率,后复权数据算信号
易漏点:同一套代码里,历史价格有的来自前复权接口,有的来自后复权接口,两者混用会导致除权日附近的信号和收益无法对齐。
为什么漏:因为前复权价格会随时间变化,今天下载的历史前复权价格和下周下载的可能不同。如果研究过程跨越多次数据更新,复现结果就会悄悄改变。
怎么补:建立研究数据快照,整个实验周期内固定使用同一份数据;计算收益统一用复权因子,看行情图才用前复权。如果条件允许,直接在后复权价格上完成全部计算。
5.3 手续费和滑点设为零:低换手策略也会“虚胖”
易漏点:回测里把交易成本设为 0,策略年化收益看着很高,实盘一执行就缩水。
为什么漏:因为初学者通常先用简单框架验证思路,忽略了每笔交易的摩擦成本。尤其是多因子周频调仓策略,每次调仓一篮子股票,换手率很容易超过 100%,成本会被快速放大。
怎么补:回测框架至少内置一个cost_model参数:买入收万二佣金,卖出收万二佣金加千一印花税,滑点按固定金额或固定比例计入。调仓频率越高,成本模型越要精细。在策略筛选阶段,可以直接看“扣除成本后的收益”,不要看毛收益。
5.4 股票池里的幸存者偏差:退市股被悄悄丢掉了
易漏点:从某个“当前存活股票列表”里取股票池,历史回测自然只包含现在还在的公司,退市股票全部缺失。
为什么漏:因为大多数行情源默认只提供当前可交易的股票,历史停牌和退市记录需要额外接口补齐。很多人不知道这一点,导致回测结果天然偏高。
怎么补:回测的股票池要使用历史时点的成分股全集,至少包含当时存在过的所有股票代码。可以用退市股列表回填数据源,或者在研究报告中明确标注“仅包含当前存续股票”,并接受这个偏差。复习时,这一条直接决定回测结论是否可信。
5.5 时间切分错误:随机划分训练集和测试集
易漏点:做因子测试时把历史数据随机切分,一部分当训练集、一部分当测试集,导致同一时间段的数据既参与训练又参与验证。
为什么漏:因为通用机器学习的交叉验证习惯是随机划分,但金融数据是强序列相关的时间序列,随机切分会让模型“偷看”到未来数据的信息。
怎么补:金融场景必须按时间顺序切分:前 70% 做训练,后 30% 做样本外测试;更严格的做法是滚动训练,每次只用过去一段窗口训练,然后预测下一段。课程里反复强调这一点,因为它直接决定了因子测试结论能不能外推到实盘。
6. 复习清单:从头到尾复盘量化知识树的 12 个问题
把课件重新过一遍时,最有效的不是重读全部代码,而是按清单逐个回答“你能不能做到”。以下这 12 个问题,覆盖了数据、因子、回测三个阶段的关键能力。
| 模块 | 复习问题 | 完成标准 |
|---|---|---|
| 数据 | 能否把原始数据整理成规范长表 | 排序、去重、类型修正无报错 |
| 数据 | 能否说明前复权与后复权的区别 | 能说清回测用哪一档、实盘看哪一档 |
| 数据 | 能否正确处理停牌与缺失值 | 回测中不会出现停牌日成交 |
| 数据 | 能否用公告日期合并财务数据 | merge_asof方向正确且无未来信息 |
| 因子 | 能否独立计算均线、MACD、布林带 | 分组计算、不混股票 |
| 因子 | 能否计算 IC 和 ICIR | 能解释因子是否稳定有效 |
| 因子 | 能否做截面标准化和行业中性化 | 知道为什么要先标准化再合成 |
| 策略 | 能否把因子变成一个调仓方案 | 有明确的调仓频率和持仓数量 |
| 回测 | 能否写出最小回测框架 | 信号与成交分离 |
| 回测 | 能否计算年化、回撤、夏普 | 公式口径正确 |
| 回测 | 能否识别未来函数 | 能说出至少三个泄漏场景 |
| 回测 | 是否计入手续费和滑点 | 回测绩效为扣除成本后的结果 |
查漏补缺的方法也很直接:先跑一遍数据层自查脚本,再跑一个单因子 IC 分析,最后跑一次完整回测。哪个环节报错或结果异常,就回到对应模块复习。最容易漏的不是代码,而是“数据口径”和“时间对齐”这类看不见的逻辑。从那以后,我每次拿到新数据都会强制走一遍这套流程:先检查列结构和缺失值,再确认复权口径,最后用滞后一天的信号跑一次对照回测。如果对照结果和原结果差距过大,说明一定有未来函数藏在代码里。这套习惯帮我避免了很多次“回测很爽、实盘翻车”的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取