☰
量化教材课后习题三件套:答案、代码与CSV数据的实战指南
2026/10/13 13:21:22 网站建设 项目流程

简介:《量化投资:以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_col="date") 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_col=0) 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"], encoding="utf-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 python=3.10 -y conda activate quant pip install pandas numpy matplotlib scipy statsmodels

Python版本我一般推荐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 openpyxl

openpyxl不是必须,但有的习题会要求把结果导出到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_col="date")["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_col=0) # 样本协方差 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_col="date") 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日的信号挪到t+1日执行,代表“今天收盘后根据信号决定明天开盘或收盘的交易”,避免偷看当日数据。如果不做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保存的CSV,GBK编码非常普遍。

解决:在read_csv里显式指定编码参数,读取时自动解码:

df = pd.read_csv("prices.csv", encoding="gbk")

如果gbk还报错,可以试gb18030,这是GBK的超集,兼容性更好。两个都不行,用errors="ignore"参数跳过无法解码的字符,但要注意跳过的是哪些数据,最好打印出来确认不涉及价格列。

5.3 import statsmodels时直接崩溃

现象:环境刚建好,运行工具库导入语句,控制台抛出错误,提醒找不到某个DLL或编译后的原生扩展模块。

原因:statsmodels或scipy在较新的Python版本上,预编译包覆盖不全,pip在部分平台直接安装了源码版本,缺少编译环境导致失败。

解决:不建议在这个报错上花时间调编译器。直接新建一个conda环境指定Python 3.10,再重新安装依赖库。3.10的wheel覆盖最全,几个核心数据科学库都不需要本地编译。装完再导入一次,基本能过。

5.4 计算结果和答案差100倍

现象:算出来的年化波动率是0.0023,PDF答案写的是23%,或者反过来,自己算出来23,答案让写成0.23。

原因:收益率和比率类指标的百分比口径没有统一。有的题答案用小数表示,有的用百分数,配套代码里也可能有不同的约定。

解决:在对比答案之前,先明确当前数据里收益率是小数还是百分数。写一个极简单的检查:打印最近一日的收益率,看看数值级。0.02表示2%,2.0表示200%,后者明显异常。确认口径后,在统一的位置做换算,不要时乘100时不乘100。我会在代码开头定义乘数常量并写在注释里,避免来回改。

5.5 日期列被读成字符串,时间运算全部失效

现象:滚动平均、收益率计算等一切跟时间有关的运算结果全不对,或者pct_change后全是NaN。

原因:read_csv没有指定parse_dates,date列被自动识别成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翻完,代码跑完一遍就丢到角落,结果过了两周再写策略时,该踩的坑一个没少踩。后来换成“答案即测试、数据可刷新、代码模块化”这套流程,同样一份资源,解决的问题数量上升了不止一个量级。希望你拿到这套课后习题资源时,别只把它当答案翻,把它当一套可以反复运行的落地工具来用,进步会快很多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询