第1周跟练:亲手写出收益、净值和回撤
给能借助AI运行Python、但还不能独立解释代码的你。按首月计划的5个单元执行,每次约2小时;卡住就停在当前检查点。先预测,再运行,再换输入解释。
本周只走完这一条流程
价格 → 每期收益 → 含初始本金的净值 → 历史峰值与回撤
单元1做环境和单期收益;单元2做循环与净值;单元3做回撤和输入检查;单元4做调试;单元5独立验收。教程中的代码是跟练参考,验收时要关闭。学习顺序对应首月第1周计划。
先自己尝试20分钟,再让AI解释一小段。每次求助写下“我预测什么、实际什么、检查了什么”,而不是只让AI重写整个文件。
环境配置 · Windows主线
先检查已有环境,能运行就继续。第一次可给配置留30分钟;卡住时用本页底部浏览器练习区先学计算,稍后再补本地文件与测试。
检查Python
打开Windows终端或PowerShell,输入下面第一条。能显示Python 3.10或更高版本就可用于本教程;若python不可用,再试第二条。
powershellpython --version py --version两条都失败时,打开Python官方下载页,按Windows入口安装Python Install Manager与稳定版运行时。安装管理器后,可用
py install default安装默认稳定版;已有旧启动器却不认识install时,按官方说明使用pymanager install default。完成后重新打开终端检查版本。已有可用版本无需重装。检查点:看到实际版本号;安装管理器、运行时和编辑器扩展是不同东西。
打开一个真正的学习文件夹
安装或打开VS Code,在扩展栏安装Microsoft发布的Python扩展。下载上面的跟练包,右键ZIP选择“全部解压”,放到自己新建的学习目录。
在VS Code中选“文件 → 打开文件夹”,打开直接包含
01_hello.py与week1.py的目录;不要停在ZIP预览里。如果出现同名内层文件夹,进入内层。检查点:左侧能看见4个练习脚本、test_week1.py、README.md、hand-check.csv、bug-notes.md,共8个文件。
新建终端,确认当前目录
菜单“终端 → 新建终端”,选择PowerShell。以下是终端命令,不要写进.py文件;代码块没有提示符,不需要输入PS或>>>。
powershellGet-Location Get-ChildItem -Name检查点:列出的文件包含week1.py。如果没有,重新打开正确文件夹并新建终端。
创建项目环境并固定运行路径
在当前文件夹创建.venv。若第1步只有py可用,把第一行的python改成py。后续都使用同一条明确路径;已有.venv时直接验证它。
powershellpython -m venv .venv .\.venv\Scripts\python.exe -c "import sys; print(sys.executable)"虚拟环境给本项目一个独立的解释器入口。无需激活,也无需修改PowerShell执行策略。第1周不用pip安装任何包,math与unittest随Python提供。
检查点:输出路径落在当前学习文件夹的
.venv\Scripts\python.exe;不是另一个项目。让编辑器也选中同一个解释器
按Ctrl+Shift+P,输入“Python: Select Interpreter”,选择本文件夹的.venv;若未列出,用“Enter interpreter path”定位
.venv\Scripts\python.exe。本教程仍用下方明确的终端命令运行,便于排查。powershell.\.venv\Scripts\python.exe .\01_hello.py检查点:终端出现“百分数收益: 10.00%”。首次安装和选择界面可能随版本变化,以官方入口为准。
如果你用macOS / Linux
有可用Python 3时,在解压后的学习目录执行下方命令。后文Windows解释器路径替换为./.venv/bin/python,文件参数.\week1.py替换为week1.py。缺少Python或venv时按对应系统官方文档安装,不覆盖系统Python。
python3 --version
python3 -m venv .venv
./.venv/bin/python 01_hello.py先认清这6组词
- 编辑器 / 解释器
- VS Code负责编辑文件;Python解释器负责执行代码。装好编辑器或扩展,不代表装好了Python。
- 终端 / .py文件
- 终端输入运行命令;.py文件保存Python代码。看到 >>> 表示进入了Python交互解释器,此时先输入exit()再运行终端命令。
- 变量 / 列表 / 索引
- 变量给值起名字;列表按顺序存多个值;索引从0开始。prices[1]是第二个价格,prices[-1]是最后一个价格。
- 函数 / 参数 / 返回值
- def定义一段可重复调用的代码;参数接收输入;return把结果交还给调用者。print只负责显示,不能替代return。
- 小数 / 百分数
- 计算时0.1代表10%,-0.1代表-10%;只在显示时用:.2%。不要把数字10当作10%的收益。
- 断言 / 容差
- assert检查条件,不满足就报AssertionError。abs(a-b)<1e-10表示允许很小的浮点误差;1e-10是0.0000000001。
单元1 · 先运行一段你能解释的代码
完成环境检查后,打开01_hello.py。先读每行注释,再输入或修改代码;按Ctrl+S保存。价格是教学例子,不是实时行情。
跟着做
在终端运行下方命令。先用纸算110/100−1,再看输出。随后只把new_price改成90,保存并重跑;最后恢复110。
.\.venv\Scripts\python.exe .\01_hello.py# # 后面的文字是注释,用来说明意思,不参与计算。
old_price = 100
new_price = 110
# = 是赋值;/ 是除法;先除再减,得到小数形式的收益率。
daily_return = new_price / old_price - 1
print("小数收益:", daily_return)
# f 表示把变量放进文字;:.2% 表示显示为保留两位小数的百分数。
print(f"百分数收益: {daily_return:.2%}")
# 试一试:只把 new_price 改成 90,保存后重新运行。
应看到什么
原始代码百分数收益为10.00%;改成90后为-10.00%。小数输出可能是0.10000000000000009,属于浮点表示;显示格式化不改变计算结果。
留下的证据:留下两个输入的输出,以及一句话解释:收益率表示相对上一期价格的变化。
单元2 · 从一个收益扩展到三期收益
先读q-001和q-011,再打开02_returns.py。此中间版只练循环,假设价格有效;不要用它直接处理脏数据。完整输入检查在单元3的week1.py。
跟着做
先看def内的缩进,再看函数下面的调用。运行后,在returns.append(r)下一行临时加print(i, previous, current, r),保持相同缩进,观察每次循环;看懂后删掉临时打印。末尾固定测试单独调用函数,不受上方prices换数据影响。
.\.venv\Scripts\python.exe .\02_returns.py# 这一版先假设输入正确;完整检查在 week1.py 中补上。
def simple_returns(prices):
# def 定义函数。prices 是调用时传进来的价格列表。
returns = []
# 索引从0开始。range(1, len(prices))不会包含右端点。
for i in range(1, len(prices)):
previous = prices[i - 1]
current = prices[i]
r = current / previous - 1
returns.append(r) # 把本期收益添加到列表末尾。
# return 要放在循环外;否则只会算完一期就结束。
return returns
prices = [100, 110, 99, 108.9]
returns = simple_returns(prices) # 调用函数,接住它返回的列表。
for r in returns:
print(f"{r:.2%}")
assert len(returns) == len(prices) - 1 # 4个价格只有3个区间。
# 单独用固定小例子检查函数;上面的prices可以自由修改。
assert abs(simple_returns([100, 110])[0] - 0.1) < 1e-10
应看到什么
按顺序输出10.00%、-10.00%、10.00%。四个价格有三个相邻区间,所以返回列表长度是3。i依次为1、2、3,不会取4。
留下的证据:记录三个区间的旧价、新价和收益。能用自己的话解释append、len、range和return。
单元2续 · 用连乘得到净值
收益列表中的0.1是10%。先在纸上写初始净值1,再按每期收益连乘;不要把三个收益直接相加。
跟着做
运行03_equity.py,与下方手算表逐行核对。把returns暂时改成[0.1,-0.1],确认最终不是1;然后恢复。该中间版同样假设输入有效。
.\.venv\Scripts\python.exe .\03_equity.py# 净值以1开始:1代表初始本金,1.10代表本金的1.10倍。
def equity_curve(returns):
equity = [1.0]
for r in returns:
# [-1]取列表最后一个值;新净值=上一期净值×(1+本期收益)。
next_value = equity[-1] * (1 + r)
equity.append(next_value)
return equity
returns = [0.1, -0.1, 0.1]
equity = equity_curve(returns)
for value in equity:
print(f"{value:.4f}")
print(f"累计收益: {equity[-1] - 1:.2%}")
assert len(equity) == len(returns) + 1
# 固定测试单独调用函数,不把上面的自由练习输入锁死。
assert abs(equity_curve([0.1, -0.1, 0.1])[-1] - 1.089) < 1e-10
# 这版同样假设输入正确;下一步会拒绝无效收益。
应看到什么
依次输出1.0000、1.1000、0.9900、1.0890;累计收益8.90%。先涨10%再跌10%的两期净值为0.99,累计亏1%。
留下的证据:手写一张价格、收益和净值表;算完后才查看下载包中的hand-check.csv。
单元3 · 加入回撤与输入检查
读q-015后再展开完整代码。先找三个主函数,再看check_number这个输入检查助手;不要试图一次记住所有语法。
跟着做
运行week1.py。在max_drawdown中,紧接worst = max(worst, drawdown)下一行临时加print(value, peak, drawdown, worst),保持相同缩进。再在main函数内末尾加print(max_drawdown([1,0.8,0.9])),检查首段亏损;最后移除临时打印并运行9项测试。
.\.venv\Scripts\python.exe .\week1.py展开week1.py完整注释代码
"""第1周完整参考:只用标准库。输入为由普通数字组成的列表。
价格和净值必须有限且大于0;本教程收益必须有限且大于-1。
这是正价格、无外部现金流的教学约定,不覆盖破产、杠杆或申赎。
"""
import math # 标准库,随Python提供,不用pip安装。
def check_number(value, name):
# 排除文字、布尔值、NaN(非数值)和无穷大。
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"{name}必须是数字")
if not math.isfinite(value):
raise ValueError(f"{name}必须是有限数字")
def simple_returns(prices):
if len(prices) < 2:
raise ValueError("至少需要2个价格")
for price in prices:
check_number(price, "价格")
if price <= 0:
raise ValueError("价格必须大于0")
returns = []
for i in range(1, len(prices)):
r = prices[i] / prices[i - 1] - 1
returns.append(r)
return returns
def equity_curve(returns):
equity = [1.0] # 保留初始本金,否则会漏掉第一期亏损。
for r in returns:
check_number(r, "收益")
if r <= -1:
raise ValueError("本教程约定收益必须大于-1")
value = equity[-1] * (1 + r)
check_number(value, "净值")
if value <= 0:
raise ValueError("净值必须大于0,请检查输入或数值范围")
equity.append(value)
return equity
def max_drawdown(equity):
# 调用者要传入完整净值路径,包括初始本金。
if not equity:
raise ValueError("净值列表不能为空")
for value in equity:
check_number(value, "净值")
if value <= 0:
raise ValueError("净值必须大于0")
peak = equity[0] # peak是“截至目前”的最高净值。
worst = 0.0
for value in equity:
peak = max(peak, value)
drawdown = 1 - value / peak # 返回非负损失比例。
worst = max(worst, drawdown)
return worst
def main():
prices = [100, 110, 99, 108.9]
returns = simple_returns(prices)
equity = equity_curve(returns)
print("每期收益:", [f"{r:.2%}" for r in returns])
print("净值路径:", [round(value, 4) for value in equity])
print(f"累计收益: {equity[-1] - 1:.2%}")
print(f"最大回撤: {max_drawdown(equity):.2%}")
# 断言=不满足条件时就报错;静默通过不等于没运行。
assert abs(equity[-1] - 1.089) < 1e-10
assert abs(max_drawdown([1, 0.8, 0.9]) - 0.2) < 1e-10
print("内置检查通过;还要运行 test_week1.py 的9项测试。")
# 直接运行此文件时执行main;被测试文件import时不自动演示。
if __name__ == "__main__":
main()
应看到什么
原示例累计收益8.90%,最大回撤10.00%。[1,0.8,0.9]的最大回撤20%,最终收益-10%;最终亏损和历史最大回撤不是同一件事。
留下的证据:自己的week1.py、手算表和测试输出。本教程使用正价格、无外部现金流;收益大于-1,净值大于0。输入为普通数字列表。
先手算,再和这张表核对
本例价格100、110、99、108.9。收益和回撤以百分数显示;代码内部使用小数。第0行没有上一期,收益留空。
手机可左右滑动表格;较长的代码块也可横向滚动。
| 期数 | 价格 | 本期收益 | 净值 | 历史峰值 | 当期回撤 |
|---|---|---|---|---|---|
| 0 | 100 | — | 1.000 | 1.000 | 0% |
| 1 | 110 | 10% | 1.100 | 1.100 | 0% |
| 2 | 99 | -10% | 0.990 | 1.100 | 10% |
| 3 | 108.9 | 10% | 1.089 | 1.100 | 1% |
最大回撤是整列回撤的最大值10%,不是最后一行的1%;累计收益是1.089−1=8.9%,也不是10%。只看最终结果会漏掉过程。
单元4 · 主动制造错误,练习定位
先跑基线测试
.\.venv\Scripts\python.exe -m unittest -v应看到9个测试,最后是Ran 9 tests和OK。测试覆盖复利、首段亏损、历史新高、非法输入等;它们验证指定行为,不能替你证明已掌握。
实验A:故意把连乘写错
先把week1.py另存一份备份。在equity_curve中,只把
value = equity[-1] * (1 + r)改成value = equity[-1] + r。预测输出,再运行测试;复利测试应失败。恢复后再次运行全部测试。实验B:丢掉历史峰值
只把
peak = max(peak, value)改成peak = value。用[1,0.8,0.9]手算,再运行测试。错误版本会把各期回撤算成0。记录原因,恢复后确认9项全部通过。用一个断点看清循环
打开week1.py,在
drawdown = 1 - value / peak这一行左侧点击红点。按F5;若首次询问,选择Python Debugger与当前Python文件。程序停住时看Variables中的value、peak、worst,按F10逐步执行,再按F5继续。若当前版本菜单不同,可先在循环内print这些变量观察同一过程。
每次只改一处,在bug-notes.md写“改动 → 预测 → 实际 → 哪个测试发现 → 根因 → 修复证据”。读报错先看最后一行类型,再向上找自己文件的行号;不要只截图一句“报错了”。
常见报错:按症状找,不要乱改
先保留原始报错,核对正在运行的文件,再一次只改一个原因。文件路径里的空格或中文本身不代表错误。
python / py 不是可识别的命令
先定位:还没有执行到Python文件,是终端找不到解释器。
怎么修:先试python --version,再试py --version。若都失败,按官方安装入口完成安装并重开终端;不要反复改业务代码。
修后检查:版本检查能输出Python 3.x,再创建.venv;之后检查sys.executable是否指向当前学习文件夹。
can't open file / No such file / 找不到路径
先定位:终端目录不对、没解压、文件名不一致,或保存成了week1.py.txt。
怎么修:运行Get-Location和Get-ChildItem -Name;用VS Code打开实际含.py文件的文件夹。Windows资源管理器打开“文件扩展名”显示。
修后检查:列表能看到week1.py和test_week1.py,再运行命令;不要把示例文件名换成整个ZIP名。
Activate.ps1 无法加载 / 禁止运行脚本
先定位:这是PowerShell激活脚本限制,不代表Python坏了。
怎么修:本教程不需要激活,直接运行.\.venv\Scripts\python.exe .\week1.py。无需修改系统执行策略。
修后检查:用同一路径执行-c "import sys; print(sys.executable)",确认解释器后再跑文件。
SyntaxError / invalid character
先定位:常见原因是中文引号、中文逗号、漏冒号,或者把终端命令粘进Python代码。
怎么修:看报错箭头附近及上一行;使用英文半角引号、括号、逗号。def、for、if后检查冒号。>>>中先exit()再执行终端命令。
修后检查:保存后重新运行;先修第一个语法错误,再处理后续错误。
IndentationError / TabError
先定位:缩进层级不一致,或Tab与空格混用。
怎么修:同一层使用4个空格。for内部计算要缩进,return returns应退回到函数内、循环外。可在VS Code状态栏将缩进转换为空格。
修后检查:用4个价格运行,确认返回3个收益;不只看错误是否消失。
NameError: name ... is not defined
先定位:变量拼写不一致、函数尚未定义,或只复制了调用而没复制定义。
怎么修:检查prices/price、returns/return等拼写。定义放在调用之前;浏览器里一次粘贴完整单文件,不依赖上一次运行留下的变量。
修后检查:从文件第一行到最后一行整体执行,确认新开运行也能成功。
TypeError / 'NoneType' ...
先定位:例如价格写成字符串,或函数末尾只有print没有return,调用方拿到None。
怎么修:数字写100,不写'100';检查return位置与返回对象。不要为消除报错随意把所有东西转成文字。
修后检查:临时打印type(prices[0])或函数返回值,修复后删掉临时打印再运行测试。
ZeroDivisionError / ValueError
先定位:中间版遇到0可能除零;完整参考会主动拒绝非法输入,并说明原因。
怎么修:[100,0]、单个价格、NaN本就不符合本周约定。先检查输入;验证坏输入时,应期待ValueError,而不是删掉检查。
修后检查:合法输入算对,非法输入按约定报错。unittest中的assertRaises会把预期异常记为通过。
AssertionError / FAILED
先定位:程序能运行,但某个检查发现结果不符合预期。
怎么修:先看测试名、实际值和预期值;用最小输入手算。检查是否把连乘改成相加、漏初始1或把peak设成当天净值。
修后检查:修复公式后重跑全部测试,不能把断言删掉或把预期答案改成当前错误输出。
ModuleNotFoundError: No module named 'week1'
先定位:测试文件没找到同目录的week1.py。
怎么修:确认解压后两个文件在同一文件夹,且文件名未改。回该目录运行-m unittest -v。math和unittest是标准库,不需要pip install。
修后检查:输出应是Ran 9 tests和OK;Ran 0 tests不算通过。
没有输出 / 修改后还是旧结果
先定位:只定义了函数没有调用、忘记print、未保存文件,或运行了另一个同名文件。
怎么修:先Ctrl+S,再核对终端命令里的路径。在调用处加一条print('running my file')定位;完成后删除。
修后检查:刻意把new_price改成90,确认输出随之改变;浏览器编辑内容要另存,刷新可能丢失。
0.10000000000000009 / 断言精确相等失败
先定位:浮点数不能精确表示所有十进制小数。
怎么修:用abs(actual-expected)<1e-10核对本周的小数,显示时用:.2%;不要在每一步先round再计算。
修后检查:用格式化输出看百分数,并用原始数值的容差断言确认计算。
临时替代:直接在浏览器跟练
先运行下面的单期收益。要练后续步骤,把对应的完整单文件代码替换进编辑框,再运行;不要粘贴PowerShell命令。本区不读取本机文件,9项跨文件测试请在本地完成。
刷新或离开可能丢失编辑,请先复制到自己的文件保存。首次运行要联网加载Python,下载失败时可用本地环境继续。
在你的浏览器内运行。首次需联网下载 Python;支持标准库和 Pyodide 提供的 NumPy、pandas 等包。行情抓取、系统文件访问和部分第三方库请在本地环境运行。输出自检仅比对结果关键词,不代替逻辑审查。
单元5 · 关闭参考,再证明自己会了
先确认你能从头运行、解释三个函数、修复两种人为错误,并保存手算表。然后关闭本教程和参考代码,另开一个空文件,按首月页面完成45分钟独立验收;不会的部分如实记录,不复制week1.py充当自己的答案。
仍按原标准:至少8/10分,关键项都拿2分,再隔24–48小时换数据复测。跟练顺利只代表完成练习;复制完整答案并跑过9个测试不等于独立掌握。
第1周独立验收与复测通过后,进入第2周数据清洗跟练教程 →。
官方参考与版本说明
安装入口与资料核验于2026-10-20;本页示例使用基础语法与标准库,练习包要求Python 3.10+。安装界面会变化,遇到差异优先查官方说明。