
简介《量化投资以Python为工具》配套压缩包面向正在系统学习该书的读者既适合初学者对照校验也适合进阶者回顾重点汇集了各章节课后习题答案、配套Python源码与CSV市场数据可帮助读者从理论推导走向策略实现。包体共78个文件包含37个csv数据文件、34个py代码脚本、5个txt说明文档、1个pdf答案汇总及1个accdb数据库压缩后约91.84MB内容覆盖数据读取、指标计算、策略回测等典型环节。目前已有1659人浏览学习资源按章节组织可直接定位所需内容。通过阅读pdf答案可深入理解风险与回报衡量、组合优化、时间序列分析等知识点运行py脚本可复现书中的量化模型并结合csv历史行情数据验证策略稳健性txt路径说明梳理了文件结构便于快速定位章节内容。整份资料兼顾答案对照、代码实践与数据验证是学习量化投资不可多得的综合素材。1. 一套课后习题资源的真实价值答案、代码、数据三件套怎么用量化投资教材的课后习题卡人的从来不是公式本身而是“我对了没有”。例题打开能看懂合上书做习题第3题算出来的数字和答案差一位小数第7题的图死活画不出来更尴尬的是你搞不清是自己算错了还是用了不同的数据口径。这套课后习题资源把三样东西打包在一起按章节整理的答案PDF、能直接跑出结果的Python代码、代码依赖的CSV数据。它解决的正是这种“对口径、验过程”的核心痛点。适合刚学完Python基础、正拿量化教材啃指标计算与回测的自学者也适合想快速复现书中案例的从业者——把答案当结果基准而不是标准答案去背。2. 拆开这份课后习题资源答案、代码、CSV分别要怎么看拿到资源后最推荐的顺序是先看CSV再跑代码最后才翻答案PDF。这个顺序和平时习惯恰好相反。多数人第一件事就是打开PDF对答案结果发现自己算的和答案对不上回头查代码时才发现数据列名跟代码里写的不一致——问题根本不在计算而在数据没对齐。2.1 答案PDF计算题的“结果基准”和文字题的“采分点”答案PDF按章节组织这是最直观的部分。计算题直接给最终数字比如年化波动率、夏普比率、最大回撤、最优权重向量文字题给的是要点式解答比如“简述均值方差模型的前提假设”。我的建议是把这些数字当作回归测试的期望值而不是记忆的对象。怎么验证答案数值是否和自己的实现一致写一个最小脚本先把教材例题里最常出现的年化波动率跑出来import pandas as pd import numpy as np df pd.read_csv(prices.csv, parse_dates[date], index_coldate) close df[close] daily_ret close.pct_change().dropna() annual_vol daily_ret.std() * np.sqrt(252) print(f年化波动率: {annual_vol:.4f}) print(f年化收益率: {daily_ret.mean() * 252:.4f})这段代码先按date列建立时间索引pct_change()计算每日收益率并删掉首日NaN。numpy的std默认自由度是0也就是除以N而不是N-1这个细节在答案对不上时值得先怀疑一下。年化因子用sqrt(252)是A股一年约252个交易日的惯例有的教材写250两者对结果的影响约0.4%通常不影响判断。如果打印结果和PDF答案一致说明你的数据口径和答案口径一致可以做下一题如果差10倍几乎可以肯定是百分比和原始小数没有统一如果只差一位小数先检查标准差用的是总体标准差还是样本标准差。文字题没有统一答案但我一般这么用先自己写再对照PDF看采分点有没有漏。比如问“指数加权移动平均相比简单移动平均的优点”采分点通常是“降低对旧数据的依赖”“对近期价格反应更快”“需要额外选择衰减参数”。这类题目的价值在于查漏不在于背条目。PDF里的文字答案我见过有的写得比较简略只有结论没有推导需要自己补。2.2 配套代码从单文件脚本到函数封装的演进路径这套教材的配套代码有个特点前半部分习题多是单文件脚本数据读取、计算、打印全在一个文件里顺序执行后半部分涉及组合优化和回测代码开始出现函数甚至类。建议把代码从头到尾跑一遍按输出和PDF答案一一对应。单文件脚本的典型结构长这样# 计算均值方差最优权重 import pandas as pd import numpy as np returns pd.read_csv(returns.csv, index_col0) mean_ret returns.mean() cov returns.cov() weights np.linalg.solve(cov, mean_ret) weights weights / weights.sum() print(weights)这段代码用了线性方程求解而不是显式求逆矩阵。np.linalg.solve在数值稳定性上优于先np.linalg.inv再乘原因是协方差矩阵做逆运算会把微小误差放大。想验证这一点可以让两种写法都跑一遍对比结果在小数点后第几位开始分叉。习题答案给的权重通常四舍五入到小数点后三位和打印结果一致就说明解法方向正确。到了回测部分代码模式会变成“信号生成—持仓调整—组合收益—累计净值”四个阶段。这种结构比单文件脚本更适合改造成自己的策略代码原因是信号函数和回测逻辑被分开换一个信号只需要改一个函数不需要动主流程。这本教材的代码我大致翻过早期习题文件里没有函数后期明显做了封装这点和真实项目里的演进路径很像值得跟着走一遍。2.3 CSV数据列名、单位、日期格式决定你能不能跑通CSV是这套资源里最容易出问题的地方。别看它是纯文本列名拼写、数字格式、日期编码任何一个对不上代码跑出来就是错的结果。拿到任何CSV文件第一件事不是读而是先看基本信息import pandas as pd df pd.read_csv(prices.csv) print(df.info()) print(---- 前5行 ----) print(df.head(5)) print(---- 列名 ----) print(df.columns.tolist())df.info()会告诉你三件关键事有多少行、哪些列有非空值、每列的数据类型。列全部显示为object而不是float64说明数字很可能被读成了字符串要么是CSV里本来就有引号或千分位逗号要么是某些行混入了异常字符。此时可以加参数修复df pd.read_csv( prices.csv, thousands,, na_values[, NA, null], encodingutf-8, )千分位逗号在海外教材的数据里很常见国内导出的数据则很少用但Excel另存为CSV时会自动加逗号。na_values参数把空字符、字符串NA和null统一识别为NaN避免后续计算时把这些值当成文本参与运算。encoding参数需要根据文件实际情况来前面代码块里先不写等读到报错再回来补。CSV数据常见三种结构需要先判断是哪一类。第一类是宽表每列一只股票的收盘价列名是股票代码或简称行是日期这是计算协方差最顺手的格式。第二类是长表一列date、一列symbol、一列close这种格式用pivot函数转为宽表后才能做矩阵运算。第三类是单列时间序列比如指数收盘价、基金净值直接做指标计算。教材习题里三种都会出现拿到文件先确定类别再决定要不要转格式。有个细节容易被忽略日期列的顺序。CSV里有的按升序排列从旧到新有的按降序排列从新到旧。收益率计算对顺序不敏感但是移动平均、滚动波动率这类带窗口的运算对顺序极其敏感。跑之前先看head和tail确认时间顺序再决定是否需要sort_index。3. 本地跑通这套代码环境准备与最小可运行命令配套代码能不能跑一半取决于数据是否完整另一半取决于环境版本。很多报错不是代码错了而是Python版本太新、库版本太旧、或者两者互不兼容。这一章按我自己的经验给出一个最小环境方案能让绝大多数习题代码直接跑起来。3.1 用conda建独立环境版本锁定省掉一半玄学报错我见过太多人在系统Python里直接pip install然后被一堆依赖冲突折腾到放弃。量化的库依赖关系比较碎numpy版本会影响pandas行为pandas版本又会影响statsmodels的兼容性。最稳妥的办法是用conda单独建一个环境把版本锁死在一个经过验证的组合上。conda create -n quant python3.10 -y conda activate quant pip install pandas numpy matplotlib scipy statsmodelsPython版本我一般推荐3.10而不是最新的3.12或3.13。原因是statsmodels和部分旧代码在3.11之后遇到过预编译wheel缺失的问题pip install可能会当场编译源码耗时不说还可能失败。Python 3.10的兼容面很广教材配套代码基本不需要改动就能跑。装完库之后建议顺手装jupyter和scikit-learn。sklearn在组合优化部分可能会用到协方差收缩估计jupyter适合做习题里的交互式探索。一条命令pip install jupyter scikit-learn openpyxlopenpyxl不是必须但有的习题会要求把结果导出到Excel配套代码里可能有to_excel调用少这个库会报ImportError。3.2 先跑数据自检脚本确认CSV没被破坏环境装好后不要直接跑习题代码先跑一个自检脚本。这个脚本不涉及任何计算逻辑只检查数据文件是否存在、是否为空、列名是否符合预期。这一步能隔离“数据问题”和“代码问题”排查时少走一半弯路。# check_data.py import pandas as pd from pathlib import Path DATA_DIR Path(data) required [prices.csv, returns.csv, portfolios.csv] for fname in required: path DATA_DIR / fname if not path.exists(): print(f[缺失] {fname}) continue df pd.read_csv(path) print(f[OK] {fname}: shape{df.shape}, cols{list(df.columns)}) assert not df.empty, f{fname} 是空的这个脚本的作用是快速确认三件事文件在不在、有没有内容、列名是什么。shape里的行数可以拿来和答案PDF里的时间范围做对照比如习题答案说样本区间是2015年到2020年那日线数据应该有大约1400行左右如果只有几百行说明数据被截断过。列名检查则是为了提前发现“代码里写的close列CSV里实际叫Close或收盘价”这种错位。我一般还会在这个脚本里顺手输出每列的最小值和最大值用来判断有没有明显的单位异常。比如价格列出现负数或者收益率列绝对值超过1都要停下来看原始数据别让脏数据污染后续计算。3.3 用相对路径替换绝对路径换台机器不用改代码教材配套代码里数据读取经常写绝对路径比如C:/Users/xxx/Desktop/quant/prices.csv。这种代码在作者机器上能跑换一台电脑就报FileNotFoundError。拿到代码后第一件事就是把所有绝对路径改成相对路径。别偷懒这个改动一劳永逸。from pathlib import Path import pandas as pd BASE_DIR Path(__file__).resolve().parent.parent DATA_DIR BASE_DIR / data result_dir BASE_DIR / output df pd.read_csv(DATA_DIR / prices.csv, parse_dates[date])这里的关键是__file__。在Python脚本里__file__表示当前文件的完整路径resolve()把路径中的相对部分和符号链接解析成绝对路径parent取上级目录。如果代码文件放在项目根目录下的scripts文件夹里data文件夹在根目录下就需要取两次parent才能回到根目录再拼data。这个层级关系取决于你的目录结构如果代码和数据在同一级目录直接写Path(file).parent就够了。一个常见前提是如果习题代码不是.py脚本而是Jupyter Notebook__file__会失效。在Jupyter里我一般这样处理from pathlib import Path import os NOTEBOOK_DIR Path(os.getcwd()) DATA_DIR NOTEBOOK_DIR / data df pd.read_csv(DATA_DIR / prices.csv, parse_dates[date])os.getcwd()在Jupyter里返回的是当前Notebook所在目录但注意如果你用终端启动jupyter的位置不同这个值也会变。更可靠的替代方案是用Path.cwd()但不管哪种方式都比硬编码绝对路径强。改完路径后建议把代码里的中文注释统一检查一遍有些旧代码文件编码是GBK在新环境里打开会乱码但不影响运行。如果看到控制台输出的中文全是乱码不太影响结果想解决就在代码开头加一行系统编码声明。这一步属于锦上添花。4. 把课后习题数据化三类典型题的正确打开方式教材课后习题的题型看起来五花八门实际上归纳下来就三类指标计算题、组合优化题、回测题。每一类都有自己最容易踩的口径陷阱这一章按题型逐个拆解并把配套代码的常见写法、参数含义说清楚。4.1 指标计算题先统一收益率口径再谈波动率和夏普第一类题是算各类风险收益指标比如年化波动率、夏普比率、最大回撤、VaR。这类题卡人的不是公式而是收益率口径不统一。用简单收益率算出来的波动率和用对数收益率算出来的单资产日频上看着差不多但累积到年化后的差异会超过直觉。import pandas as pd import numpy as np close pd.read_csv(prices.csv, parse_dates[date], index_coldate)[close] # 对数收益率 log_ret np.log(close / close.shift(1)).dropna() # 简单收益率 simple_ret close.pct_change().dropna() vol_log log_ret.std() * np.sqrt(252) vol_simple simple_ret.std() * np.sqrt(252) print(f对数收益率波动率: {vol_log:.4f}) print(f简单收益率波动率: {vol_simple:.4f})两者差异在日频数据上通常只有万分之几但答案PDF如果精确到小数点后四位就可能差在最后一位。我的建议是优先用对数收益率计算波动率理论上是连续复利口径数学性质更好也是大多数金融文献的默认选择。如果答案对不上先试着换成简单收益率看是否一致。夏普比率更要注意“无风险利率怎么处理”。常见的陷阱是把年化无风险利率直接除以252加到每日收益率上或者不减去直接算超额收益。还要注意夏普有两种约定俗成的写法一种是用年化收益率减去年化无风险利率再除以年化波动率另一种是先算日度超额收益再年化。两种方法在数学上等价但中间过程的小数位数会带来尾差。最大回撤的算法倒不难难点在于起点和终点的定义。教材答案可能会从第一笔数据开始算也可能跳过前面一小段不稳定区间。我会把最大回撤实现为滚动窗口的最大回撤函数方便切换窗口。4.2 组合优化题协方差矩阵的估计决定权重答案对不上先查它第二类题是均值方差组合优化常见的是给定几只资产的收益率序列求最小方差组合或给定目标收益的最优权重。这类题的答案对不上十有八九出在协方差矩阵的估计方法上。import pandas as pd import numpy as np returns pd.read_csv(returns.csv, index_col0) # 样本协方差 cov_sample returns.cov() # 收缩估计 from sklearn.covariance import LedoitWolf cov_lw LedoitWolf().fit(returns).covariance_ weights_sample np.linalg.solve(cov_sample, returns.mean()) weights_sample weights_sample / weights_sample.sum() weights_lw np.linalg.solve(cov_lw, returns.mean()) weights_lw weights_lw / weights_lw.sum() print(样本协方差权重:, weights_sample.round(3)) print(收缩协方差权重:, weights_lw.round(3))样本协方差在没有正则化的情况下对极端值非常敏感。当样本数量观测天数远小于资产数量时协方差矩阵会变得病态求逆之后权重出现极端正负值这在实盘里完全不可用。LedoitWolf收缩估计把协方差矩阵向一个结构化目标收缩降低估计方差得到的权重通常更“正常”。习题答案如果用样本协方差那权重会比较“极端”如果用了收缩估计权重会更均匀。我的经验是教材配套的CSV通常资产数量没有超过5个样本量足够答案大概率用的是样本协方差。如果算出来的权重和答案完全对不上把两个协方差矩阵前几个数字打印出来对比一下就能判断作者用的哪种估计。另一个常规陷阱是协方差矩阵的年度化处理。有的答案把日频协方差乘以252再拿去做优化有的直接用日频协方差权重结果其实完全一样因为矩阵同时乘以一个常数线性方程组的解不变。但如果你中间做了别的运算这个差别就会渗透进来。4.3 回测题用向量化替代逐行循环顺便避开未来函数第三类题是简单策略回测比如双均线交叉、动量排名、布林带突破。教材给的最初版本通常是用for循环逐行遍历这是最容易理解的方式但也是最容易出未来函数的方式。典型的错误是把第t日的信号先用到了第t日的收益上——这在实盘中根本不可能发生因为信号是收盘后才确定的。import pandas as pd df pd.read_csv(prices.csv, parse_dates[date], index_coldate) close df[close] # 双均线指标 ma_short close.rolling(20).mean() ma_long close.rolling(60).mean() # 信号短均线在长均线上方为1否则为0 signal (ma_short ma_long).astype(int) # 关键信号延迟一天防止未来函数 signal signal.shift(1).fillna(0) daily_ret close.pct_change().fillna(0) strategy_ret signal * daily_ret cum_ret (1 strategy_ret).cumprod() print(f期末净值: {cum_ret[-1]:.4f})shift(1)是这个脚本里最值钱的一行。它把t日的信号挪到t1日执行代表“今天收盘后根据信号决定明天开盘或收盘的交易”避免偷看当日数据。如果不做shift回测结果通常会更好看实盘却打不出那个结果这就是业内常说的未来函数。配套代码里如果发现信号没有延迟建议改成上面这样再跑。滚动窗口的参数20和60不是固定的。教材习题给出的参数通常在题目里写明了配套代码里也可能直接写死。跑通后可以手动改这两个数字观察净值的敏感度这本身就是一个不错的练习题扩展。同步要考虑的是fillna(0)对前期无信号期的处理直接持仓还是空仓两种逻辑下净值曲线起点不同后面结果差异会累积。还有一种常见的向量化坑收益率序列里出现了NaN而信号没有对齐。两个Series做乘法时pandas按索引对齐但如果一个索引是DatetimeIndex另一个是RangeIndex结果全变NaN。确保两者都来自同一个DataFrame、同一个索引是回测代码不出莫名其妙结果的前提条件。5. 避坑与排查这套答案和代码最常见的五个坑配套资源用起来不会一路顺风下面五个问题是我个人经历里出现频率最高的。每条都按“现象→原因→解决”来写直接对照处理就行。5.1 PDF打开是乱码或空白页现象答案PDF用系统自带的阅读器打开文字全是乱码或者只有图没有字翻几页以后干脆白屏。原因这类PDF通常是从扫描件转换或使用非嵌入字体生成阅读器缺少对应字体库时文字映射会失败。解决优先换阅读器不要折腾系统字体。把PDF导入浏览器自带阅读器或专业PDF软件多数乱码问题会消失。如果仍然无效用命令行工具把PDF转成txt或md虽然公式会丢但文字答案能看。注意公式类是PDF里最容易在转换过程中损坏的内容只能当作保底方案。5.2 pd.read_csv直接报解码错误现象用pandas读取CSV时报错比如UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xd5 in position 0代码一行没跑就崩了。原因CSV文件实际编码是GBK或GB2312而pandas默认按utf-8读取。国内数据导出和旧版Excel保存的CSVGBK编码非常普遍。解决在read_csv里显式指定编码参数读取时自动解码df pd.read_csv(prices.csv, encodinggbk)如果gbk还报错可以试gb18030这是GBK的超集兼容性更好。两个都不行用errorsignore参数跳过无法解码的字符但要注意跳过的是哪些数据最好打印出来确认不涉及价格列。5.3 import statsmodels时直接崩溃现象环境刚建好运行工具库导入语句控制台抛出错误提醒找不到某个DLL或编译后的原生扩展模块。原因statsmodels或scipy在较新的Python版本上预编译包覆盖不全pip在部分平台直接安装了源码版本缺少编译环境导致失败。解决不建议在这个报错上花时间调编译器。直接新建一个conda环境指定Python 3.10再重新安装依赖库。3.10的wheel覆盖最全几个核心数据科学库都不需要本地编译。装完再导入一次基本能过。5.4 计算结果和答案差100倍现象算出来的年化波动率是0.0023PDF答案写的是23%或者反过来自己算出来23答案让写成0.23。原因收益率和比率类指标的百分比口径没有统一。有的题答案用小数表示有的用百分数配套代码里也可能有不同的约定。解决在对比答案之前先明确当前数据里收益率是小数还是百分数。写一个极简单的检查打印最近一日的收益率看看数值级。0.02表示2%2.0表示200%后者明显异常。确认口径后在统一的位置做换算不要时乘100时不乘100。我会在代码开头定义乘数常量并写在注释里避免来回改。5.5 日期列被读成字符串时间运算全部失效现象滚动平均、收益率计算等一切跟时间有关的运算结果全不对或者pct_change后全是NaN。原因read_csv没有指定parse_datesdate列被自动识别成object类型字符串pandas不知道它是时间无法做时间索引对齐和窗口计算。解决统一用parse_dates参数并在读取后设为索引df pd.read_csv(prices.csv, parse_dates[date]) df df.set_index(date).sort_index()parse_dates接受列名列表pandas会在读入时自动转换类型。set_index把日期列设为索引sort_index保证时间升序这两个动作加起来能规避大部分时间序列运算的坑。设置索引后df.index就能直接用shift、rolling等时间窗口方法。6. 进阶用法把习题答案变成回归测试和日期更新脚本跑通一次答案是及格能复用才是赚钱。我拿到这套资源之后做的事是把答案里的数字转成断言让代码每次改动后自动验证有没有跑偏。具体做法是提取PDF答案中的关键数值写进测试用例里作为期望值然后随便改动算法实现跑一遍马上知道结果有没有被破坏。例如年化波动率答案0.231就断言计算结果与它的误差不超过0.001否则报错。这一步把静态答案变成了活的回归测试后续所有代码重构都变得安全。第二个进阶是把固定CSV替换成可更新数据。教材附的CSV是某个时间点的静态数据用来做题足够但如果你想把这套代码用到当下行情里就得把数据源换成能拉到最新数据的接口。常见的做法是把read_csv封装成一个load_data函数内部先检查本地缓存是否最新过期就拉取新的写入CSV再返回DataFrame。这样原有代码的所有下游逻辑都不需要改就实现了数据自动化。第三个进阶是重新组织代码结构。我会把指标计算、信号生成、回测框架分别拆成模块再写一个主脚本调用。这样换策略只改信号函数算新的指标只新增一个函数不碰主流程。这也是从“做习题”到“做项目”之间最重要的跨越。以前我拿到这种资源图省事直接把答案PDF翻完代码跑完一遍就丢到角落结果过了两周再写策略时该踩的坑一个没少踩。后来换成“答案即测试、数据可刷新、代码模块化”这套流程同样一份资源解决的问题数量上升了不止一个量级。希望你拿到这套课后习题资源时别只把它当答案翻把它当一套可以反复运行的落地工具来用进步会快很多。希望帮到你。本文还有配套的精品资源点击获取