第 3 课 程序怎样记住价格、文字和判断结果¶
这一课只解决一个基础问题。人能凭上下文认出纸上的 10.20 是价格,程序需要更明确的名字、类型和单位,才能在后面正确计算。
纸上的 10.20 为什么还不够¶
假设你在一张纸上只写了 10.20。第二天再看时,这个数字可能是开盘价、收盘价、手续费,也可能是持仓数量。人会根据记忆猜测上下文,Python 不会猜。程序必须知道它正在处理什么内容,这个内容叫什么名字,允许参加哪些运算。
Python 直接写出的数字、文字和真假判断都叫值。10.20 属于小数,"周一" 保存一段文字。条件成立时可以用真假值 True 表示。值可以直接参与运算,不过一段量化程序会反复引用价格、费率和现金,长期只写裸数字很容易混淆,所以我们要给值取名字。
阅读这行代码时,从右向左理解会更自然。右边的 10.20 是准备保存的值,单个等号表示“把右边结果交给左边名字”,左边的 close_price 是以后取回这个值时使用的名字。这个有名字、可以在后续代码中继续使用的位置叫变量。
flowchart LR
accTitle: 一行赋值代码完成了什么
accDescr: Python 先计算等号右边的值,再把结果交给左边变量名,后面的代码通过变量名取回当前值
a[右边的值<br/>10.20] --> b[执行赋值<br/>把结果保存起来]
b --> c[左边的变量名<br/>close_price]
c --> d[后续计算<br/>继续读取当前值]
变量可以暂时理解成贴有名称的记录位置。这个比喻只帮助入门,程序内部还会管理对象和内存地址,本课暂时不用展开。当前最重要的习惯是让名字说明含义。看到 close_price,读者能猜到它保存收盘价;看到 x,几天以后连作者本人也可能忘记它代表什么。
Python 怎样区分数字和文字¶
下面两行在屏幕上都出现 10.20,Python 却把它们当成两类内容。第一行没有引号,保存的是可以参加数学运算的小数。第二行带有成对引号,引号告诉 Python 中间内容是一段文字。
这种类别叫数据类型。类型决定一个值能够做什么。数值加 1 会得到新的数值,文字加另一段文字会把两段内容接在一起。下面代码先做数学加法,再做文字拼接。
运行后会看到两行结果。第一行是 11.2,因为 10.20 与 1 进行了数学相加。第二行是 10.20 元,因为两段文字按顺序连接。若写成 message + 1,Python 无法决定应该做数学运算还是文字拼接,于是会停止并给出 TypeError。这类报错在保护数据含义,它提醒我们先确认类型。
入门阶段先掌握四种类型就足够覆盖大部分示例。
| 类型 | Python 名称 | 示例 | 量化中的常见用途 |
|---|---|---|---|
| 整数 | int |
5 |
天数、数量、行号 |
| 浮点数 | float |
10.20 |
价格、收益率、费率 |
| 文字 | str |
"周一" |
日期标签、资产名称、字段名 |
| 布尔值 | bool |
True |
某个条件是否成立 |
布尔值只有 True 和 False 两种结果,第 5 课会让它决定程序走哪条路。现在可以用 type() 查看任何值的类型。type(10.20) 会得到浮点数,type("10.20") 会得到文字。遇到“这个数字为什么不能相加”的问题时,先打印类型往往比盯着代码猜测更有效。
一段表达式怎样产生新值¶
变量保存旧值,运算会产生新值。假设某只虚构资产昨天收于 10 元,今天收于 10.50 元,我们先用减法得到价格变化,再用变化除以原价格得到收益率。开始写代码以前,先在纸上算一遍。价格增加 0.50 元,0.50 除以 10 等于 0.05,所以收益率为 5%。
yesterday_close = 10.00
today_close = 10.50
price_change = today_close - yesterday_close
return_rate = price_change / yesterday_close
print(price_change)
print(return_rate)
前两行保存原始输入,中间两行使用输入计算新值,最后两行只负责显示结果。运行后会依次看到 0.5 和 0.05。小数 0.05 与百分数 5% 表达同一个比例,量化程序通常用小数参加计算,因为乘除更直接;写报告时再把它格式化成人习惯阅读的百分数。
这行代码会显示 变化率为 5.0%。开头的 f 允许文字中插入变量,花括号标出插入位置,.1% 要求按百分数显示并保留一位小数。格式化只改变显示方式,变量 return_rate 内部仍然保存 0.05,后续计算不会因此乘上一百。
收益率也可以直接写成 today_close / yesterday_close - 1。它与先算变化再除以原价格得到相同结果。初学阶段保留 price_change 这个中间变量更容易检查,因为你可以把 0.5 与纸面结果比较,发现错误时也知道问题出在减法还是除法。
变量为什么会随着程序更新¶
量化程序需要描述现金和持仓随交易变化,所以变量允许重新赋值。假设账户原有 1000 元,买入占用 120 元,第二行代码先读取旧现金,完成减法,再把结果保存回同一个变量名。
结果为 880。数学等式 cash = cash - 120 不可能成立,Python 的单个等号表达的是赋值动作,因此这一行没有矛盾。判断两边当前值是否相等时要写双等号 ==,例如 cash == 880 会得到 True。赋值与比较只差一个等号,却承担完全不同的任务。
为了看清账户变化,可以把更新拆成几步。下面例子先扣买入金额,再扣手续费,每一步以后都打印现金。若最终结果与手算不符,从第一处出现差异的位置向前检查即可。
cash = 1000.0
buy_amount = 300.0
fee = 0.3
cash = cash - buy_amount
print("买入后现金", cash)
cash = cash - fee
print("扣费后现金", cash)
运行后会先显示 700.0,再显示 699.7。同一个变量名只保留当前状态,旧的 1000 和 700 不会自动形成历史记录。完整回测需要把每天的现金、持仓和净值逐行保存到表格,这样才能回到某个日期解释变化。第 14 课会正式建立这种账本,本课只需要理解状态更新。
变量名和单位共同保护含义¶
Python 的变量名可以包含字母、数字和下划线,不能以数字开头。它还区分大小写,所以 price 与 Price 会被当成两个名字。社区通常使用小写单词和下划线,例如 close_price、fee_rate 和 holding_days,这套写法叫蛇形命名。
清楚的名字仍然不能自动携带单位。数值 5 可能表示五元、五天、五股或 5%。研究者要在变量名、表格列名和说明文字中明确单位。下面三个名字分别告诉读者天数、比例和人民币金额,代码复查时更容易发现数量级错误。
fee_rate 的 0.0005 表示 0.05%。若误写成 0.05,程序会按 5% 扣费,两者相差一百倍。电脑会精确执行错误输入,所以“代码能运行”只能证明语法没有阻止它,不能证明金融含义正确。每个比例都应说明使用小数还是百分数,金额应说明货币,证券数量还要说明按股、按手还是按合约。
可以用 assert 设置一道简单检查。它会判断后面的条件,条件为假时立即停止,避免明显错误继续传到更远的计算。
第一条把教学费率限制在 0% 到 1% 之间,第二条要求初始现金不为负。这里的范围只适用于本课示例,真实市场要根据品种和费用定义重新设置。assert 的价值来自提前写下预期,它不能替研究者决定什么范围合理。
文字数字怎样安全地变成数值¶
从 CSV 文件或网页读入的数据有时会被当成文字。"10.20" 看起来像价格,类型仍然是 str。确认内容确实是合法数字以后,可以用 float() 转成小数;数量字段需要整数时,可以用 int(),但要先确认是否允许小数。
结果为 10.5。转换可能失败,例如 float("停牌") 会报错,因为“停牌”不是数值。此时应保留原始值并按数据规则处理,不能随手改成零。零价格是一条真实数值,会让后续收益率出现巨大变化;缺失或停牌则是在说明当时没有正常可用价格,两者含义不同。
小数为什么偶尔多出很长的尾巴¶
电脑使用二进制保存大多数浮点数,有些十进制小数无法被有限位二进制精确表示。运行 print(0.1 + 0.2) 时,某些环境会显示 0.30000000000000004。末尾差异来自表示方式,不代表 Python 把加法规则算错了。
研究一般收益率时,这种误差通常远小于行情本身的价格精度。需要精确处理结算金额、最小报价单位或货币分时,可以把金额换成整数最小单位,或者使用十进制类型并遵守交易所和账户的舍入规则。比较两个浮点计算结果时,也常检查差距是否足够小。
abs() 取绝对值,1e-9 表示十亿分之一。若差距小于这个容许范围,判断结果为 True。容许范围应根据问题选择,价格最小跳动和资金规模都会影响合理精度,不能把一个固定数字复制到所有项目。
把本课知识连成一笔完整计算¶
现在用一段小程序把类型、变量、运算、格式化和检查接起来。输入是两天虚构收盘价与费率,目标是计算未扣费收益和扣除一次费用后的近似净收益。代码出现以前已经知道纸面答案,价格从 20 元涨到 21 元,毛收益为 5%,费率 0.05%,近似净收益为 4.95%。
old_close = 20.0
new_close = 21.0
fee_rate = 0.0005
assert old_close > 0
assert new_close > 0
assert 0 <= fee_rate < 0.01
gross_return = new_close / old_close - 1
net_return = gross_return - fee_rate
print(f"毛收益 {gross_return:.2%}")
print(f"近似净收益 {net_return:.2%}")
前三行只保存输入,三条检查拦住明显异常,中间两行计算结果,最后两行负责显示。输出应为“毛收益 5.00%”和“近似净收益 4.95%”。这里为了理解变量,只扣了一次比例费用,没有考虑买卖两端、最低费用、价差和滑点,因此它还不是完整交易成本模型。读者能说出这个限制,比得到漂亮数字更重要。
容易弄错的地方¶
| 容易混淆的写法 | Python 会怎样理解 | 应该怎样检查 |
|---|---|---|
把 "10.5" 当成价格直接相加 |
引号让内容成为文字 | 先用 type() 查看,确认来源后再转换 |
把 = 与 == 混用 |
单等号保存,双等号比较 | 用一句自然语言翻译当前代码 |
| 把 0.05 读成 0.05% | 程序中的 0.05 通常表示 5% | 在变量名或说明中写明比例口径 |
用 p、x1 保存多种价格 |
代码能运行,含义会迅速丢失 | 使用 open_price、close_price 一类名称 |
| 浮点尾数不整齐便直接四舍五入原数据 | 显示问题和业务精度被混在一起 | 先确定最小价格单位与结算规则 |
一分钟自测¶
price = "12" 里的 price 能直接除以 2 吗?如果不能,安全的下一步是什么?
查看答案
它保存的是文字,直接除法会产生类型错误。先确认文字确实表示合法价格,再用 `float(price)` 转成数值。若原内容是“停牌”或空白,应按数据缺失规则处理,不能强行变成零。本课术语¶
| 词语 | 现在应该怎样理解 |
|---|---|
| 值 | 程序正在处理的一项具体内容 |
| 变量 | 保存当前值并供后续代码引用的名字 |
| 数据类型 | 值所属的类别,它决定可以进行哪些操作 |
| 浮点数 | Python 中常用来表示价格和收益率的小数类型 |
| 布尔值 | 只表示条件成立或不成立的真假类型 |
| 赋值 | 先计算等号右边,再把结果保存到左边名字 |
| 表达式 | 由值、变量和运算符组成并能产生结果的代码 |
| 格式化 | 改变数值展示方式而不改变内部值 |
课后练习¶
1 判断类型¶
"周五"、20、20.5、False 分别属于什么类型?
提示
观察引号、小数点和真假单词。答案
它们依次是文字 `str`、整数 `int`、浮点数 `float` 和布尔值 `bool`。2 手算变化率¶
价格从 50 元变为 52 元,变化率是多少?请同时写出小数和百分数。
提示
先算变化 2 元,再除以原来的 50 元。答案
`(52 - 50) / 50 = 0.04`,小数形式为 0.04,百分数形式为 4%。3 找出错误¶
total = "100" + 20 为什么不能运行?在什么前提下可以修改为数值相加?
提示
检查等号右边两项的数据类型。答案
`"100"` 是文字,`20` 是整数。确认文字确实表示数值以后,可以写 `float("100") + 20`,结果为 120。4 动手修改¶
把完整示例中的费率改成 0.1%,先写出小数形式,再预测新的近似净收益。
提示
0.1% 需要先除以 100,毛收益仍为 5%。答案
费率写成 `0.001`,近似净收益为 `0.05 - 0.001 = 0.049`,显示为 4.90%。5 开放思考¶
为什么量化代码值得使用 close_price 这样的长名字,并同时在说明中写清单位?
评价要点
回答应提到几个月后重新阅读、多人复查、开盘与收盘混用、比例数量级错误,以及代码能运行但金融含义仍可能错误。下一课会把一个价格扩展成一组有顺序的行情,并解释列表、字典、索引和表格之间的关系。继续阅读第 4 课 怎样保存一组行情。