简介:《量化投资:以Python为工具》课后配套资源合集,面向系统学习量化策略开发的金融专业学生、自学者及初级量化分析师,解决课后练习缺少参考解答、示例数据与代码需自行整理的痛点。压缩包共78个文件、约91.84MB,其中包含37个CSV数据文件、34个Python脚本、5个txt说明、1个PDF答案及1个ACCDB数据库;章节脚本覆盖第2章至第34章,CSV数据按Part1~Part5组织,可用于数据清洗、特征计算、策略回测与结果可视化等完整练习流程。目前已有1659人学习下载,适合刚接触量化投资、希望快速复现书中案例并验证自身理解的读者使用。PDF答案覆盖风险与收益衡量、组合优化、时间序列、机器学习应用等章节,能辅助校准思路;Chapter系列脚本与movingAverage、candle等模块可直接运行,txt路径说明还帮助快速定位文件,省去从零整理数据与代码的时间。
1. 一套能跑的量化Python课后答案:这个ZIP包里有什么,值不值得下
网上搜量化投资的Python课后答案,最常见的体验是找到一份PDF,结果代码全是截图,CSV也不全,抄进编辑器里跑第一步就报错。我拿到这份「答案PDF+源代码+CSV数据」的ZIP包时,第一反应是终于有人把“能复现”这个事当回事了。它不只是一份习题答案,而是把每道题的解法、可运行的.py或.ipynb代码,以及习题里用到的原始数据打包在同一套目录里。适合正在学量化投资、卡在“理论懂但代码跑不通”阶段的人,也适合想快速核对某一章策略实现细节的从业者。这篇笔记会把包里的结构、代码和数据怎么配合用写清楚,顺带把常见的坑也列出来。
2. 拆开ZIP包:答案PDF、源代码、CSV数据三部分是怎么配合的
打开ZIP包之后,第一印象是目录层级非常规整。顶层通常是三个部分:一个放PDF答案的目录、一个放代码的目录、一个放CSV数据的目录。这个结构不是随手排的,而是为了让你能按“看题目 -> 看答案思路 -> 看代码实现 -> 用数据验证结果”的路径走通。ZIP包里的PDF是按章节组织的,每章对应若干道习题,答案里有文字推导还有代码片段;代码目录里的文件命名通常带章节号和题号,比如某种“ch3_2_ma_cross.py”风格,可以直接对应上PDF里的某一题;CSV目录则放的是习题里需要读取的行情数据、收益率序列或者因子数据。
2.1 答案PDF的价值不止是“对答案”
PDF的价值在于它保留了完整的推导链路。很多习题不是“算一个数就完事”,它会要求你证明某个公式、解释某个参数的意义、比较两种策略的优劣。这些部分没法靠跑代码得出来,PDF里的文字推导正好补齐了这个缺口。我的习惯是:先自己做一遍,做不出来的题去PDF里看思路,看完思路之后不看代码,自己动手写一遍,写完再对照包里的官方代码,看差异在哪里。
这样用PDF才是有意义的,不然就变成了抄作业。特别是量化投资里那些关于风险厌恶系数、夏普比率计算口径、协方差矩阵估计方法的题,文字推导才是核心考点,代码反而是次要的。
2.2 代码目录:从“答案代码”到“可运行代码”之间缺的只是数据路径
大多数人拿到代码后最常犯的错,是直接在代码文件所在目录里按原样运行,结果报“FileNotFoundError: xx.csv”。原因很简单:代码里读取CSV用的是相对路径,而这个相对路径是按照ZIP包的原始目录结构写的。你需要让CSV文件所在目录和代码文件所在目录保持原有层级关系,或者在代码里改成绝对路径。
我一般会这样处理:在代码目录里建一个临时脚本,统一把CSV路径指到解压出来的data文件夹。以某个“双均线回测”习题为例,代码通常长这样:
import pandas as pd # 原始代码里可能是:df = pd.read_csv('data/000300.csv') # 如果你把代码和数据分开解压,就会报错 # 常见做法是在脚本开头设置一个DATA_DIR常量 DATA_DIR = '../data' # 根据实际解压位置调整,指向放CSV的目录 df = pd.read_csv(f'{DATA_DIR}/000300.csv', index_col=0, parse_dates=True) df['ma_short'] = df['close'].rolling(window=5).mean() df['ma_long'] = df['close'].rolling(window=20).mean() df['signal'] = (df['ma_short'] > df['ma_long']).astype(int) df['position'] = df['signal'].diff().fillna(0)这段代码的逻辑很直接:先用一个常量把数据路径固定下来,然后读入沪深300行情数据,计算5日均线和20日均线,短均线上穿长均线时产生买入信号。rolling(window=5).mean()是滚动窗口求均值,window参数控制均线周期,你改成10和30就变成另一组均线策略。diff().fillna(0)用于计算持仓变化,1代表开仓,-1代表平仓。这三行是习题里最常见的模板,很多题目都是在此基础上加手续费、加滑点、加止损。
2.3 依赖边界:这份代码能跑在什么环境下
拿到ZIP包之后,先用一份清单核对环境依赖,免得跑一步卡一步,那是检查代码前要做的第一件事。从代码里的import语句看,核心依赖是pandas、numpy和matplotlib,少部分涉及scipy的优化函数。这几个库的版本不需要多新,pandas 1.x以上基本都能跑。如果代码里用了pd.Timestamp、df.rolling这类老API,那在pandas 2.x下也完全兼容;但如果用了df.append()这种从1.4开始就弃用的方法,pandas 2.x会直接报AttributeError。所以建议在装好依赖后先跑一遍最基本的导入,确认没有“No module named xxx”和属性不存在这两类低级错误。
3. 把CSV数据读进DataFrame:路径、编码、字段核对三个关键操作
CSV数据是这套ZIP包里容易被低估的部分。很多课后习题本身不提供数据,只给你一个“假设你手头有某股票的日线数据”的题目背景,答案只能写出代码逻辑而无法给出可验证的数值结果。这份资源把数据一起打包,意味着你可以真正跑出结果,对照答案PDF里的输出数值。这一点在量化习题里非常关键——参数优化类题目,答案写“最优参数为window=20”,如果你没有数据,完全无法验证这个结果对不对。
3.1 先搞清楚CSV里存的是什么
打开数据目录,你会看到一批CSV文件。命名大致能看出用途:一部分是行情类数据,比如“某指数日线.csv”,字段一般是date、open、high、low、close、volume;另一部分是预处理好的序列,比如“某因子值.csv”,字段是date加若干列因子名。还有一部分是截面数据,比如“某财务指标.csv”。三种数据的读取方式不太一样,但第一步都是先看表头。
我开始时犯过一个低级错误:拿pandas直接读CSV,没看分隔符,结果读出来的DataFrame只有一列,因为文件用的是tab分隔而不是逗号。这里建议不管什么数据,先统一做一步检查:
import pandas as pd # 先以默认逗号分隔读一次,再读原始文件的前几行文本 raw = open('../data/某指数日线.csv', encoding='utf-8').readline().strip() print(repr(raw)) df = pd.read_csv('../data/某指数日线.csv', encoding='utf-8') print(df.head()) print(df.dtypes)输出里能看到分隔符到底是什么,dtypes能告诉你date列是不是被读成了object而不是datetime。这一步检查只花几秒钟,但能避免后续所有关于时间序列索引和数值计算的隐性问题。encoding='utf-8'是常规选择,但如果读出来是乱码,试一下'gbk'或'gb18030',这批CSV里有的文件可能是从Excel另存出来的,编码不统一很常见。
3.2 日期字段和时间索引是第一个隐形考点
量化习题里几乎所有计算的起点都是“把日期列转成索引,按时间排序”。但CSV里的日期格式五花八门:有的是“2023/01/04”,有的是“2023-01-04”,有的是“20240104”。如果代码里直接pd.to_datetime(df['date']),大多数情况能自动解析,但遇到“20240104”这种纯数字字符串,pandas会按时间戳解析,结果变成1970年的某个日期,直接崩掉。
标准的处理方法是在读取时指定格式,或者读取后统一转换:
df = pd.read_csv('../data/某指数日线.csv', encoding='utf-8') df['date'] = pd.to_datetime(df['date'], format='%Y%m%d') # 明确告诉pandas格式 df = df.set_index('date').sort_index() df['returns'] = df['close'].pct_change()format='%Y%m%d'是显式声明输入格式,能避免pandas猜错。sort_index()这一步同样重要,因为后续计算移动平均线、收益率、回撤都默认数据按时间递增排列,如果原始数据是倒序,整个策略信号会全部反掉。这种错很难发现,因为代码不报错,结果只是数值不对,等你对照答案PDF时才发现对不上。
3.3 行情数据常用的字段核对方法
字段名称在不同来源的数据里差异很大:“收盘价”可能叫close、Close、CLOSE、“收盘”。代码里写df['close'],但读到的是英文列名“Close”,大小写不匹配就直接KeyError。我一般会先打印df.columns.tolist(),再写一个字段映射表,把统一列名映射过去:
column_map = { '日期': 'date', '时间': 'date', '开盘': 'open', 'Open': 'open', '最高': 'high', 'High': 'high', '最低': 'low', 'Low': 'low', '收盘': 'close', 'Close': 'close', '成交量': 'volume', 'Volume': 'volume', } df = df.rename(columns=column_map) df = df[['date', 'open', 'high', 'low', 'close', 'volume']]这一步虽然在课程答案里不是考点,但在实际复现答案是必要的准备工作。做完字段映射之后,再跑习题里的策略代码,成功率会高很多。
4. 排查:这份ZIP包代码跑不通时的五类典型翻车现场
前面说的都是正常流程里会遇到的麻烦,这一章专门列一下我实际踩过的坑。每个坑都是“现象 -> 原因 -> 解决”的结构,你可以直接对照自己报的错来定位。
4.1 现象:PDF答案里的数值和代码跑出来的结果对不上
原因多半是数据版本或复权方式不一致。同一种指数,前复权、后复权和不复权的收盘价序列在早期历史段差异很大,均线交叉的位置也会跟着变。答案PDF里的输出可能是用某次运行时的数据快照算出来的,而你ZIP包里的CSV如果经过重新导出,样本范围或复权方式变了,结果自然对不上。解决:确认ZIP包里CSV的日期范围是否覆盖答案里标注的样本区间,并检查代码里有没有做复权处理。如果都没有,那结果对不上就是正常的,重点是看思路而不是纠结具体小数。
4.2 现象:读取CSV时报UnicodeDecodeError
这个错在中文Windows环境下特别常见。CSV文件用utf-8编码保存,但系统默认编码是gbk,pandas读取时用的是系统默认编码,于是直接报错。解决很简单:读取时显式指定encoding='utf-8',如果还报错,改用encoding='gbk',再不行用encoding='gb18030'试一次,这三种基本能覆盖国内所有CSV文件的编码情况。
4.3 现象:代码跑得动但画图不显示中文,全是方块
原因是matplotlib默认字体里没有中文字符。习题答案的代码里如果plt.title('沪深300收益率'),在Linux或部分macOS环境下会显示成方框。解决方式是在画图前设置中文字体:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'WenQuanYi Micro Hei', 'Noto Sans CJK SC'] plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题第一行指定中文字体,第二行必须设置,否则坐标轴上的负号会被渲染成一个方块。这个坑很小但出现频率极高,属于跑通之前必改的环境配置。
4.4 现象:pandas 2.x下代码报“AttributeError: module 'pandas' has no attribute 'Panel'”
如果ZIP包里的代码是比较老的版本,可能用到pd.Panel处理三维数据,这在pandas 0.25之后就移除了。课程答案如果编写时间较早,代码里偶尔会出现这类过时API。解决方式有两种:一是把pandas降到1.x版本(不推荐,会带来其他兼容问题),二是改代码——pandas 1.x之后三维数据应该用xarray或直接把Panel拆成一堆DataFrame。我建议直接改代码,遇到pd.Panel(...)的习题,把数据结构改成多层列的DataFrame,思路一样,只是表达上更现代。如果改起来太麻烦,就跳过那一道题,因为从学习的角度,旧API已经不值得花费太多时间。
4.5 现象:代码单独跑没问题,但整个目录按章节顺序执行时,前面的变量残留影响到后面的输出
这主要发生在把.ipynb的代码段复制进.py文件之后。Notebook里的单元格是按顺序执行的,变量会一直保留在全局命名空间;转成.py脚本一次性跑完时,如果前面的单元格定义了df,后面某个单元格又用了df,而你本意是想用另一份数据,就会在不知情的情况下用错变量。解决:在每个代码块的开头加一段“环境隔离”的习惯性写法:
# 清理旧变量,避免Notebook时代的包袱 try: del df except NameError: pass配合if __name__ == '__main__':控制代码只在主程序运行时执行,能省掉很多玄学问题。另一个更彻底的方案是每个章节的代码单独成一个.py文件,每个文件的变量互不干扰,这也是我会推荐给初学者的习惯。
5. 验证这套答案:一份能顺手跑完所有习题代码的检查脚本
写代码最重要的是能验证“我做的结果是对的”。ZIP包里的习题答案代码,每一章都是相对独立的,那就可以做一个批量检查脚本,把每个代码文件作为独立子进程跑一遍,确认不报错、关键数值落在合理范围。这个脚本看起来不复杂,但能救场——遇到几十道题的ZIP,逐个人工跑会很痛苦,写一个检查脚本一劳永逸。
#!/bin/bash # 遍历代码目录下所有 .py 文件,逐个运行,保存日志 for f in ../code/*.py; do echo "===== Running $f =====" python "$f" > "${f%.py}_run.log" 2>&1 if [ $? -eq 0 ]; then echo "PASS: $f" else echo "FAIL: $f — 查看 ${f%.py}_run.log" fi done2>&1把标准错误重定向到同一份日志,这样报错信息不会在终端里一闪而过,事后可以直接查看log文件里的Traceback定位问题。$?拿的是上一条命令的退出码,0表示正常结束。这个脚本不检查结果数值对不对,只检查代码“能不能跑通”,适合刚解压完ZIP时做第一轮摸底,把所有运行不过的文件一次性筛出来,再集中修环境问题。
数值层面的验证,仍然要回到答案PDF。我的做法是:每跑通一道题,就把输出的关键指标——比如夏普比率、年化收益、最大回撤——和PDF里答案给出的数对照,允许浮点精度的微小偏差,但如果偏差超过1%,就说明某个地方的数据口径或参数设置和原题不一致。做这类对照验证时,建议先把PDF的答案目录翻一遍,题目里标了“输出:0.8231”这类位置的,优先做数值核对,那是高置信度的验证点。
从那次把一份答案包逐题跑通、并把每个坑都记录下来之后,我养成了一个习惯:拿到任何课后的源码包,先建一次干净的虚拟环境,装好依赖,然后跑一个最小编码样例,再开始处理数据和对照答案。每跑通一道题,就在笔记里写一行“通过,结果与答案差0.0002”,证明这道题真的理解了而不是看懂了。这个习惯帮我避开了后面不少浮于表面的“假会”,希望也能帮到你。
本文还有配套的精品资源,点击获取