第 1 课 用六天价格走完第一笔量化研究¶
先不写程序,也不预测真实市场。我们用纸笔把一条交易想法从观察、判断、下单一直送到账本。
从一句含糊的市场观察开始¶
小林最近留意到一只股票连续几天上涨,于是产生了一个想法。价格已经连续上升,下一天会不会还有一点惯性。这样的观察很自然,却还不能交给另一个人重复。连续上涨究竟要连续几天,观察的是每天开盘价还是收盘价,条件满足以后什么时候买,买入后又在什么时候卖,这些问题都没有答案。
为了只学习研究流程,我们先离开真实股票,使用一只名叫“纸飞机”的虚构资产。资产可以先理解为能够被买卖和持有的对象,股票、债券、基金和期货都属于不同类型的资产。下面六天价格由本书编写,不代表任何真实市场,也不能用来判断未来收益。
| 日期 | 开盘价 | 收盘价 | 收盘以后能够确认的事实 |
|---|---|---|---|
| 周一 | 10.00 元 | 10.10 元 | 只看见第一天,无法判断是否连续上涨 |
| 周二 | 10.12 元 | 10.30 元 | 周二收盘价高于周一收盘价 |
| 周三 | 10.32 元 | 10.50 元 | 周三收盘价又高于周二收盘价 |
| 周四 | 10.55 元 | 10.62 元 | 周四开盘发生在周四收盘之前 |
| 周五 | 10.58 元 | 10.70 元 | 可以用周五开盘价结束这次演示 |
| 下周一 | 10.66 元 | 10.40 元 | 这一天的信息在前面几天还不存在 |
开盘价是一段交易时间开始附近形成的第一笔价格,收盘价是一段交易时间结束附近形成的最后一笔价格。真实市场对开盘与收盘有更细的撮合规则,第 9 课会专门说明。当前只要抓住顺序即可。一日之中先有开盘,经过许多交易以后才有收盘;周三收盘以后,周四开盘还没有发生。
把观察改成别人也能执行的规则¶
我们把小林的想法改写成下面这条教学规则。
如果某一天的收盘价高于前一天,并且前一天的收盘价也高于再前一天,就认为价格已经连续上涨两天。条件在当天收盘后成立时,下一交易日开盘买入一份,再下一个交易日开盘卖出。
这条规则仍然很简单,却已经回答了四个问题。它观察收盘价,用两个连续比较来定义上涨,在下一交易日开盘买入,并在持有一个完整交易日后退出。任何人拿到相同六天价格,都应该找到相同的判断日和交易日。
现在沿着日期慢慢走。周一只有一个收盘价,无法完成两个比较。周二的 10.30 元高于周一的 10.10 元,我们只完成了第一个比较,还不能说已经连续上涨两天。周三收盘以后,10.50 元高于周二的 10.30 元,而周二又高于周一。两个比较第一次同时成立,因此周三收盘后产生买入判断。
量化研究把这种由规则产生的判断称为信号。信号只表示某个条件在某个时点成立,它不会自动让账户发生变化。“连续两天上涨”是本例的信号条件,“周三收盘后条件成立”是这条信号出现的具体时点。
信号出现以后还没有买到¶
从判断到真正持有资产,中间还要经过订单和成交。订单是一份提交给市场的买卖意愿,它至少要说明买卖对象、方向、数量和希望执行的时间。本例的订单可以写成“周四开盘买入一份纸飞机”。成交是订单在市场中真正匹配成功的结果,成交记录会告诉我们实际买到多少以及实际支付的价格。
教学表把周四开盘价 10.55 元当作成交价,并假设一份订单能够全部成交。真实市场里,这个假设未必成立。买方与卖方没有在同一价格相遇时,订单可能等待、部分成交或完全没有成交;市场快速变化时,实际价格也可能与表格中的开盘价不同。现在先采用简单假设,是为了把时间顺序和账本算清,后面再逐层补上现实限制。
买入成交以后,账户里多出一份纸飞机。这一份实际持有的资产叫作持仓。周三收盘后只有信号,持仓仍然是零;周四开盘成交以后,持仓才从零变成一份;周五开盘卖出成交以后,持仓又回到零。把三个时点写在一起,信号、订单、成交和持仓的差别会更加清楚。
| 时点 | 当时知道什么 | 采取什么动作 | 成交后的持仓 |
|---|---|---|---|
| 周三收盘后 | 连续上涨两天的条件成立 | 准备下一日买入订单 | 0 份 |
| 周四开盘 | 看见周四开盘交易时点 | 买入一份,教学成交价为 10.55 元 | 1 份 |
| 周五开盘 | 到达预先规定的退出时点 | 卖出一份,教学成交价为 10.58 元 | 0 份 |

图中的每一列回答一个不同问题。市场记录先被整理成数据,规则读取当时可见的数据形成信号,信号再经过订单与成交改变账户,最后所有变化进入账本。
为什么不能在周三收盘价买入¶
这是量化回测中最先要守住的一条时间边界。我们的信号需要使用完整的周三收盘价 10.50 元。这个价格要等周三交易结束时才能确认,在它尚未形成的时候,研究者并不知道当天最后会停在哪里。既然判断依赖周三收盘结果,就不能同时假装自己早已按这个结果完成交易。
可以把它想成考试结束铃声。铃声响起以后,你才知道自己是否答完全部题目;不能因为铃声后的事实,再回到铃声前修改答案。周三收盘后的信号最早只能影响之后仍可发生的交易。本例选择周四开盘,是因为它在时间上清楚地晚于信号。
sequenceDiagram
accTitle: 收盘信号到下一日开盘成交的时间线
accDescr: 周三交易时段先产生价格,收盘后完整价格才能用于判断,随后订单进入等待,周四开盘才假设完成成交
participant M as 周三市场
participant R as 研究规则
participant O as 待执行订单
participant A as 教学账户
M->>M: 交易持续,收盘价尚未确定
M->>R: 周三收盘后得到完整的 10.50 元
R->>O: 连续上涨条件成立,准备买入一份
Note over O: 隔夜等待,不能回到周三成交
O->>A: 周四开盘按教学价 10.55 元成交
A->>A: 持仓由 0 份变为 1 份
如果程序用周三收盘价判断,又把买入价写成同一个周三收盘价,它就获得了现实中无法同时拥有的信息与成交机会。这类错误常被称为偷看未来。更正式的偏差名称会在第 16 课出现,目前只要学会画时间线。每当一段代码计算信号,都问一次这项信息何时完整;每当它改变持仓,再问一次市场在那个时点是否仍允许交易。
先算赚了多少钱¶
周四开盘用 10.55 元买入一份,周五开盘用 10.58 元卖出一份。卖出收到的钱减去买入支付的钱,就是这次交易在忽略费用时的盈亏。
我们先用普通算式计算。
结果是每份盈利 0.03 元。盈亏以货币计价,回答账户增加或减少了多少钱。可是只看 0.03 元还不方便比较。10 元左右的资产上涨 0.03 元,与 1000 元左右的资产上涨 0.03 元,变化程度显然不同。为了把起点不同的交易放到同一尺度上,我们还需要计算相对于买入金额的变化比例。
先把手算过程分成两步。第一步求价格变化,也就是 0.03 元。第二步把这 0.03 元除以最初投入的 10.55 元,得到约 0.002844。把小数乘以 100 后,可以写成约 0.2844%。这个相对变化叫作收益率。
当这段关系已经能用数字讲清时,再把它压缩成公式。
公式中的 \(r\) 表示这一笔交易的收益率,\(P_{\text{买入}}\) 是买入价格,\(P_{\text{卖出}}\) 是卖出价格。把本例的数字放进去,就得到下面的结果。
百分号只改变写法,没有改变这笔交易本身。小数 0.002844 与百分数 0.2844% 表达的是同一比例。以后看到 1% 时,要知道它对应小数 0.01;把 1% 直接写成数字 1 会放大一百倍。
把费用放回账本¶
真实交易通常需要支付佣金、税费或其他成本,实际成交价格也可能偏离研究时采用的参考价格。为了保留手算难度,我们假设买入和卖出合计花费 0.02 元。账户原本获得 0.03 元价差,扣除 0.02 元以后,只剩 0.01 元。
扣费后的收益率约为 0.01 除以 10.55,也就是 0.0948%。一次看起来为正的微小变化,加入费用以后已经缩小很多。如果成本再高一点,这笔交易还可能变成亏损。这就是金融知识在量化流程中的作用。程序可以准确算出价差,市场规则与费用决定价差有多少能够留在账户里。
下面这张小账本把整笔交易汇总起来。
| 项目 | 金额或数量 | 它说明什么 |
|---|---|---|
| 初始现金 | 10.55 元 | 为了手算,假设刚好能买一份 |
| 周四买入 | 支出 10.55 元 | 现金降为零,持仓变成一份 |
| 周五卖出 | 收入 10.58 元 | 持仓回到零,现金变为 10.58 元 |
| 交易费用 | 支出 0.02 元 | 现金降为 10.56 元 |
| 最终盈亏 | 增加 0.01 元 | 10.56 元减去初始 10.55 元 |
记录现金、持仓、成交和费用的表叫作账本。账本让每一步变化都能沿着日期检查。若最后现金无法由前面的动作推出,说明某一笔成交、费用或数量可能漏记。第 14 课会把这种手算账本扩展为多日回测。
到这里才适合定义回测¶
现在,我们已经准备好理解回测。回测是让一套交易规则按照历史时间顺序运行,并记录当时会产生的信号、订单、成交、持仓、费用和账户变化。它与单纯画一条价格曲线不同,也与事后挑出几笔成功交易不同。可信回测必须把每一天的可见信息和账户状态连接起来。
本例只有六天,并且许多现实条件被简化,因此只能叫作最小教学回测。它能检查规则是否清楚、时间是否合理、账本是否算得通,却不能说明连续上涨策略长期有效。若要评价策略,还需要更多时期、更多交易、比较基准、不同费用和没有参与规则调整的数据。这些内容会在第 15 到第 17 课逐步加入。

数学把 0.03 元变成可比较的收益率,计算机把同一规则稳定应用到许多日期,金融说明信号何时能够成交以及费用怎样进入账户。三方面最终都要在同一本账本中接受检查。
这也是为什么量化可以理解为三门知识共同完成的研究。数学负责描述变化、风险和证据,计算机负责重复执行与保存结果,金融负责解释市场、订单和账户。少掉任何一块,最后的数字仍可能很精确,却未必具有正确含义。
用网页把时间线再走一次¶
价格侦探实验会逐日揭开数据。先写下自己的判断,再打开下一天,你会更直观地感受“当时可见”与“后来知道”的差别。实验使用虚构价格,不联网,也不会保存输入内容。
完成实验时,先不要追求猜对下一天。真正的任务是让自己的判断只使用已经揭开的数据,并且在新价格出现以后保留原记录。研究过程允许判断失败,却不能在看到答案后悄悄改写过去。
最后再看这段代码¶
前面已经用中文和手算完成了信号判断,所以代码现在只负责把同一个判断写得更精确。输入是周一、周二和周三的三个收盘价。任务是比较周三是否高于周二,同时比较周二是否高于周一。运行以后应该看到 True,它表示两个条件同时成立。
close = [10.10, 10.30, 10.50]
up_on_wednesday = close[2] > close[1]
up_on_tuesday = close[1] > close[0]
signal = up_on_wednesday and up_on_tuesday
print(signal)
第一行把三个收盘价按日期顺序放进一组数据,方括号表示这一组内容。Python 从数字 0 开始编号,所以 close[0] 是周一的 10.10,close[1] 是周二的 10.30,close[2] 是周三的 10.50。列表和编号会在第 4 课完整学习,这里只需把它们看成三格按顺序摆放的抽屉。
第二段先完成两个比较。close[2] > close[1] 对应 10.50 大于 10.30,close[1] > close[0] 对应 10.30 大于 10.10。每个比较都会得到“成立”或“不成立”。单词 and 要求左右两项都成立,才把 signal 保存为 True。最后一行把结果显示在屏幕上。
这段代码没有下单,也没有计算收益。变量名叫作 signal,只说明它保存的是规则判断。周四买入、周五卖出、费用和账本都还没有写进程序。初学者阅读代码时,要警惕变量名带来的错觉。判断每段程序完成到哪一步,应当查看它读取的输入与产生的输出,不能只看名称。
一笔盈利为什么说明不了策略可靠¶
六天案例得到 0.01 元扣费后盈利,但这项结果几乎没有统计证据。我们只观察了一次信号,也只选择了一个价格片段。换一组日期、稍微提高费用或让实际成交价格变差,结论都可能改变。策略可靠需要说明它在什么条件下可能成立,也要主动寻找会让它失败的环境。
完整研究会继续追问数据是否足够、规则是否在看过结果后反复修改、交易成本是否符合市场、不同年份的表现是否一致,以及未参与调试的数据能否支持同一方向。量化研究使用透明步骤减少自我欺骗,也会明确拒绝把一次盈利包装成规律。
容易弄错的地方¶
| 容易产生的误解 | 回到本例怎样检查 |
|---|---|
| 信号出现就等于已经买到 | 周三收盘后的持仓仍是零,周四成交后才变成一份 |
| 使用周三收盘信息也能按周三开盘买入 | 开盘发生在收盘之前,时间线无法成立 |
| 盈利 0.03 元已经考虑成本 | 0.03 元只是卖出价减买入价,费用还要单独扣除 |
| 收益率 0.2844% 应写成数字 0.2844 | 百分数换成小数要除以一百,应写成约 0.002844 |
| 一次盈利能够证明规则有效 | 一次结果可能来自偶然,还没有经过更多时期与独立数据检查 |
一分钟自测¶
周三收盘后出现信号,周四开盘提交买入订单。如果订单没有成交,账户是否已经拥有一份纸飞机?
查看答案
没有。信号是判断,订单是买卖意愿,只有成交记录才能让现金和持仓发生实际变化。本课术语¶
| 词语 | 先这样理解 |
|---|---|
| 资产 | 能够被买卖或持有的对象 |
| 开盘价与收盘价 | 一段交易时间开始与结束附近形成的价格记录 |
| 信号 | 规则使用当时可见信息作出的判断 |
| 订单 | 向市场表达买卖对象、方向、数量和条件的指令 |
| 成交 | 订单在市场中实际匹配完成的结果 |
| 持仓 | 账户成交后实际持有的数量 |
| 收益率 | 盈亏相对于最初投入金额的比例 |
| 回测 | 让规则按历史时间顺序运行并逐步记账的实验 |
第一部分综合作业¶
请自己编写一组至少六天的开盘价和收盘价,不要从真实股票中挑选一段漂亮走势。沿用本课的连续上涨规则,先逐日写出当时能够看见的信息,再标出信号出现日、最早买入日、卖出日和每个时点的持仓。最后手算价差、收益率与一项自定费用。
完成后请另一位读者只看你的规则和表格重新计算。两人的日期不同,说明规则仍有含糊处;日期相同而收益不同,通常要检查买卖价格、数量、费用或百分数换算。这个作业会成为毕业项目中的第一张交易时间线。
课后练习¶
1 辨认流程¶
信号、订单和成交分别回答什么问题?
提示
依次考虑规则判断、向市场表达意愿、账户实际变化。答案
信号说明条件是否成立,订单说明准备怎样买卖,成交说明实际买卖了多少以及价格是多少。2 手算收益¶
若买入价为 20.00 元,卖出价为 20.40 元,忽略费用时每份盈亏和收益率分别是多少?
提示
先求价格差,再除以买入价。答案
每份盈利 0.40 元,收益率为 0.40 除以 20.00,也就是 0.02 或 2%。3 寻找时间错误¶
一条策略使用周五完整收盘价判断,却把买入成交记在周五开盘。问题出在哪里?
提示
画出同一天开盘与收盘的发生顺序。答案
周五开盘早于周五收盘,买入时还不知道完整收盘价,程序使用了当时尚未出现的信息。4 动手修改¶
把代码里的周三收盘价从 10.50 改成 10.20,先预测输出,再运行检查。
提示
重新比较周三的 10.20 与周二的 10.30。答案
输出会变成 `False`,因为周三不再高于周二,两个条件没有同时成立。5 开放思考¶
为什么手工编写一组普通教学价格,比专门从历史中挑一段成功走势更适合第一次练习?
评价要点
教学目标是检查规则、时间和账本。事后挑选成功片段会把结果偏好带入例子,让读者误以为一笔盈利能够支持策略,也不利于暴露失败情况。请不用看表格复述这条时间线。周三收盘后,连续上涨条件才完整成立;周四开盘订单成交以后,持仓才从零变成一份;周五卖出并扣除费用以后,结果才进入账本。能够清楚区分信号、订单、成交与持仓,就已经抓住了后面所有回测课程的骨架。