跳转至

第 1 课 用六天价格走完第一笔量化研究

先不写程序,也不预测真实市场。我们用纸笔把一条交易想法从观察、判断、下单一直送到账本。

适合谁第一次接触量化的读者
前置知识完成第 0 课,会使用计算器
建议用时50 分钟
本课成果一张写清时间、动作和结果的纸上交易单

从一句含糊的市场观察开始

小林最近留意到一只股票连续几天上涨,于是产生了一个想法。价格已经连续上升,下一天会不会还有一点惯性。这样的观察很自然,却还不能交给另一个人重复。连续上涨究竟要连续几天,观察的是每天开盘价还是收盘价,条件满足以后什么时候买,买入后又在什么时候卖,这些问题都没有答案。

为了只学习研究流程,我们先离开真实股票,使用一只名叫“纸飞机”的虚构资产。资产可以先理解为能够被买卖和持有的对象,股票、债券、基金和期货都属于不同类型的资产。下面六天价格由本书编写,不代表任何真实市场,也不能用来判断未来收益。

日期 开盘价 收盘价 收盘以后能够确认的事实
周一 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 元卖出一份。卖出收到的钱减去买入支付的钱,就是这次交易在忽略费用时的盈亏。

我们先用普通算式计算。

\[ 10.58-10.55=0.03\text{ 元} \]

结果是每份盈利 0.03 元。盈亏以货币计价,回答账户增加或减少了多少钱。可是只看 0.03 元还不方便比较。10 元左右的资产上涨 0.03 元,与 1000 元左右的资产上涨 0.03 元,变化程度显然不同。为了把起点不同的交易放到同一尺度上,我们还需要计算相对于买入金额的变化比例。

先把手算过程分成两步。第一步求价格变化,也就是 0.03 元。第二步把这 0.03 元除以最初投入的 10.55 元,得到约 0.002844。把小数乘以 100 后,可以写成约 0.2844%。这个相对变化叫作收益率。

当这段关系已经能用数字讲清时,再把它压缩成公式。

\[ r=\frac{P_{\text{卖出}}-P_{\text{买入}}}{P_{\text{买入}}} \]

公式中的 \(r\) 表示这一笔交易的收益率,\(P_{\text{买入}}\) 是买入价格,\(P_{\text{卖出}}\) 是卖出价格。把本例的数字放进去,就得到下面的结果。

\[ r=\frac{10.58-10.55}{10.55}\approx 0.002844=0.2844\% \]

百分号只改变写法,没有改变这笔交易本身。小数 0.002844 与百分数 0.2844% 表达的是同一比例。以后看到 1% 时,要知道它对应小数 0.01;把 1% 直接写成数字 1 会放大一百倍。

把费用放回账本

真实交易通常需要支付佣金、税费或其他成本,实际成交价格也可能偏离研究时采用的参考价格。为了保留手算难度,我们假设买入和卖出合计花费 0.02 元。账户原本获得 0.03 元价差,扣除 0.02 元以后,只剩 0.01 元。

\[ 0.03-0.02=0.01\text{ 元} \]

扣费后的收益率约为 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。最后一行把结果显示在屏幕上。

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 开放思考

为什么手工编写一组普通教学价格,比专门从历史中挑一段成功走势更适合第一次练习?

评价要点教学目标是检查规则、时间和账本。事后挑选成功片段会把结果偏好带入例子,让读者误以为一笔盈利能够支持策略,也不利于暴露失败情况。
离开本页前
请不用看表格复述这条时间线。周三收盘后,连续上涨条件才完整成立;周四开盘订单成交以后,持仓才从零变成一份;周五卖出并扣除费用以后,结果才进入账本。能够清楚区分信号、订单、成交与持仓,就已经抓住了后面所有回测课程的骨架。