AI 给出一条回测曲线,你怎样检查它?先别盯着累计收益,拿出交易流水:这条曲线能否由现金和持仓重新算出来?
回测(backtest)按写好的规则和成交假设,用过去的数据重建模拟账户。本文用五天合成价格检查回测机制,不是验证真实历史策略;从信号到净值都能手算,不需要写代码。
读完你能做什么
- 分清信号、目标、订单与成交各自发生在什么时候。
- 用现金、股数、价格和费用复算账户权益。
- 完成一份账本核对表,并定位第一处差异。
阅读准备:完整课程前置为 G38 策略实现核验。本篇已给出完整规则、价格和预期结果,会加减乘除即可练习,不需要编程或安装应用。
先约定:我们究竟在模拟什么
这是一份原创合成练习,使用不存在的资产 X 和五个虚构交易日 D1—D5。它只用于检查回测机制,不对应真实市场策略,也不是投资推荐。
账户初始有1,000美元现金,没有持仓;只允许持有 X 或现金,不借钱,可以买入 X,卖出数量不能超过已有持仓。
每个交易日收盘后,看当天收盘价:严格大于11美元,下一步目标就是持有20股;小于或等于11美元,目标为零股。注意“严格大于”意味着正好11美元时目标也为零;如果已有持仓,就在收盘后发出卖单,等下一交易日开盘成交。
订单数量等于目标股数减去现有股数。已经持有20股,目标仍为20股时,不下单、不收费;别把“继续持有”误写成“每天再买20股”。
成交与资金按下面的规则处理:
- 收盘后产生的非零订单,在下一交易日开盘按表中价格全部成交。
- 每个有成交的订单收取1美元费用。买入前同时检查成交金额和费用;现金不足就整笔拒绝,不部分买入。
- 拒绝或没有成交时不收成交费用;拒绝也要保留订单状态,不能把没有持仓误记成没有出现过买入意图。
主例中间没有缺失价格。这些是方便手算的教学规则,不代表任何真实券商的撮合、收费或拒单政策。
主例假设成交价不偏离指定开盘价,也就是不计滑点(slippage)。不计税费、现金利息或外部出入金,也不处理结算等待;分红、拆股等公司行动在后面另举例说明。初始1,000美元是教学子账户,不是家庭全部资产;20股也只是固定演算数量,不是仓位建议。
| 交易日 | 开盘价 | 收盘价 |
|---|---|---|
| D1 | 10 美元 | 10 美元 |
| D2 | 10 美元 | 12 美元 |
| D3 | 13 美元 | 11 美元 |
| D4 | 10 美元 | 10 美元 |
| D5 | 10 美元 | 10 美元 |
一条时间线里,有四种不同的东西
信号是对已知数据的判断,目标是希望达到的持仓,订单是改变持仓的指令。只有成交,才实际改变现金与股数。
D2 收盘价12美元,目标变为20股。当前持仓为零,所以生成买入20股的订单,等次日成交。D2 收盘时账户仍是1,000美元现金,不能取得买入之前的涨幅。
权益(equity)等于现金加持仓市值。本例每天按收盘价估值,不必卖出股票也能计算权益;真正卖出时,再按成交价和费用更新现金。
现实订单也需要区分指令与结果。市场订单不能保证成交在你刚看到的价格,限价订单则要满足价格条件,未必成交。我们的“下一开盘价全部成交”是教学假设,不能由此推导真实市场必然如此。[1]
第一笔买入:钱减少了,股票才增加
D3 开盘价是13美元。按规则买入20股,需要支付260美元,再付1美元费用。现金从1,000美元变成739美元,持仓从零变成20股。
刚成交后按13美元估值,股票价值260美元,加上现金,权益999美元。少的1美元是买入费用。若系统仍报1,000美元,查是否漏费;若少了2美元,查是否重复扣费或另有已列明的成本。
到 D3 收盘,X 跌到11美元。20股的市值变成220美元,现金仍是739美元,所以权益为959美元。股票还没卖出,但账户已经承受了价格变化,不能因为“浮亏没实现”就把净值停在买入时。
D3 收盘价正好是11美元,不满足“严格大于十一”。因此,收盘后目标改为零股,生成卖出20股的订单。卖出还要等到 D4 开盘,不能直接把 D3 收盘的11美元当作已锁定的退出价格。
第二笔成交:为什么最后只剩938美元
D4 开盘,价格已经变成10美元。卖出20股收到200美元,扣掉1美元费用,现金净增加199美元。原有739美元现金加上它,得到938美元。持仓归零。
D4 和 D5 的收盘价都没有超过11美元,目标维持零股,没有新订单,也没有新费用。期末权益就是938美元。期间没有出入金,所以收益率可以直接用期末权益除以期初权益,再减一,得到负6.2%。
再用另一条路核对:买入价13美元,卖出价10美元,每股亏3美元,20股共亏60美元;两笔成交各收1美元,总费用2美元。价格损失与费用合计62美元,正好等于初始1,000美元减去期末938美元。
交易价格损失加费用,应该解释账户的总变动。复杂账户再加入分红、利息和外部资金流,仍要这样对账。
| 收盘时点 | 现金 | 持股数 | 持仓市值 | 账户权益 | 收盘后的行动 |
|---|---|---|---|---|---|
| D1 | 1,000 | 0 | 0 | 1,000 | 无订单 |
| D2 | 1,000 | 0 | 0 | 1,000 | 等待下一开盘买 20 股 |
| D3 | 739 | 20 | 220 | 959 | 等待下一开盘卖 20 股 |
| D4 | 938 | 0 | 0 | 938 | 无订单 |
| D5 | 938 | 0 | 0 | 938 | 无订单 |
表中金额均为美元,权益按收盘价估值,没有假设每天卖出持仓。把权益列连起来就是曲线;任何一点,都应能倒查到对应流水。
把成交提前一天,错误也可能看起来很漂亮
假设一个错误实现,在看到当天最终收盘价之后,直接使用那个收盘价买入或卖出。它会在 D2 以12美元买入20股,花掉241美元;又在 D3 以11美元卖出,收到扣费后219美元。
错误时序下的期末现金是978美元,表面收益为负2.2%,比正确时序少亏40美元。这不是策略变好了,而是实现使用了与既定规则不一致的成交时点。
主例要求先知道完整收盘价,再在下一开盘交易;不能既依赖最终收盘判断,又假装已经在同一个价格完成执行。
时序错误也可能让结果变差,不能按曲线好不好看来判定。检查应依据数据何时可得、规则怎样写、成交怎样模拟。下一开盘执行只能解决其中一部分问题,仍需排查股票池、预处理等环节的泄漏。[2]
两个边界检查:买不起,以及没有成交
如果有人把目标数量改成100股,而 D3 开盘仍为13美元,买入金额加费用就是1,301美元,超过账户的1,000美元。按照我们事先约定的规则,整笔订单被拒绝,没有成交,也不收费。这里不能悄悄产生负现金,再把它当成没有杠杆的账户。
其他系统可能缩量、部分成交或允许借款,但必须事先写清。不要让 AI 为了跑完回测,临时决定现金不足时怎么办。
另看一个部分成交的例子,它独立于主账本。假设只在13美元成交了12股,订单首次成交时收取一次1美元费用。此时现金应为843美元,持仓为12股,而不是20股。
剩下八股仍未成交;是否保留或撤销余单需要另写规则,在这个一次性订单费的分支中,同一订单后续成交不重复收费。我们不把这个分支混回上面的全额成交结果。
样本末没有下一开盘价,订单就保持未成交,不能凭空变成持仓。
中途缺价、订单有效期和新旧信号冲突尚未定义。遇到这些情况,要先补规格,不能擅自延后成交。
分红和拆股:不要凭空创造收益
主账本没有公司行动。下面两个独立快照只演示算术,不计入938美元的期末结果。
第一个快照:账户有20股,每股10美元,持仓价值200美元。如果发生一拆二,数量变为40股;在仅为解释拆股算术而设定的5美元参考价下,总持仓仍是200美元。不能只把股数翻倍却仍按10美元估值。
真实市场价格还会受其他因素影响,订单是否需要调整也要按系统规则处理。
第二个快照:假设这20股均符合领取资格,每股派发五角,且10美元现金已经到账。本例暂不处理除息日、登记日与支付日的时差。
若只为隔离分红算术,假定参考价由10美元降为9.5美元,那么股票价值190美元,加上10美元现金,合计仍为200美元。这不是预测除息日一定如此涨跌,而是说明现金和价格要在同一口径中处理。
如果已经使用包含分红再投资效果的总回报序列计算账户收益,又把同一分红现金额外加进去,就可能重复计算。价格调整、持股数量和现金流水必须一起核对。具体引擎的实现方式可以不同,不能把某个工具的默认设置当成所有系统的通用事实。[3]
不写代码,怎样检查 AI 交来的结果
先写下三个检查点:D2 收盘零持仓;D3 买入后现金739美元、收盘权益959美元;D4 卖出后现金938美元。再让工具提供信号、订单、成交、费用、现金和持仓,逐项对照。
比较时从第一处差异开始。若 D2 就有持仓,先查时序;若 D3 现金不是739美元,先查数量、成交价和费用;若现金正确但权益错误,查股数和估值价格。不要直接让 AI “把最后结果改成938”,那样可能只是修饰答案,没有修复过程。
工具不给流水,就要求导出或在表格里重建。关键记录不可见,便不能说已核验实现;这个五日例子也只检查有限机制,不证明长历史和多标的情形都正确。
操作可以分六步:
- 保存输入和规则。
- 独立写下预期结果。
- 将材料交给工具运行。
- 找到第一处差异。
- 查明原因后重新运行。
- 保存核对与修订记录。
把本文完整规则和价格 CSV 一并交给工具,再粘贴下列请求。对方不会自动看到本文,也未必支持全部操作。
请使用给定价格表和本文完整规则,先复述你理解的规则,再逐日列出:日期、收盘信号、目标股数、收盘后订单、下一开盘成交、费用、现金、持股数和收盘权益。不要提前使用收盘价;不得自行改变现金不足或缺数据的政策。先给逐日账本,再算期末收益。标明无法执行或无法导出的部分。
在核对表右侧填入实际现金、股数和权益,各自用“实际减预期”求差,从首个非零差额查起。注明实际值来自独立手算、表格还是某个工具版本。手算只能验证自己的账本推演,不能登记为已核验工具;空白表示尚未检查。
保存价格、规则版本、假设、预期表、实际表及修订记录。无论程序逐事件更新,还是批量计算,都必须符合这些时点与账本约束。
练习:先定位差异,再改变一个条件
先作答,再看核对要点。下面的错误记录和费用变式都与主账本分开。
- D2 收盘已经出现买入信号,为什么权益仍是1,000美元?
- 一份待查报告前两天与主账本一致,但D3收盘写现金740美元、20股、权益960美元。第一处差异是什么?若股数和成交价均正确,应先检查哪项流水?
- 只把每个成交订单的费用由1美元改为2美元,其他条件不变。先算D3现金与收盘权益,再算D4现金和期末收益。交易日和股数是否改变?
参考答案 · 展开核对
答案要点
第一题,D2只产生订单,D3才成交,账户不能取得持有之前的涨幅。
第二题,D3现金比预期739美元多1美元,权益也相应多1美元。先查是否漏掉买入费;需要费用流水才能确认原因,不能只凭末值强改账本。
第三题,D3现金为 1000 − 20 × 13 − 2 = 738 美元,收盘权益为958美元。D4卖出后现金为 738 + 20 × 10 − 2 = 936 美元,期末收益−6.4%。现金仍够用,交易日和股数不变;这次差异来自费用假设改变,并非必然是程序错误。
带走的成果:一份完成核对的账本
用手算、表格或工具逐日填完核对表,并保存这三项结果:
- 每天的现金、股数和收盘权益,以及实际值减预期值的差额。空白不算已核对。
- 一条对账说明:价差损失60美元、费用2美元,解释了账户从1,000降至938美元。
- 一条对D3现金错误的检查记录,以及每单2美元费用变式的独立计算。不要把变式填入主账本的预期列。
标明核对值来自手算还是工具;未运行工具就不写“工具已通过”。交给别人复查时,一并保留价格、规则和结果表。对方应能沿着交易记录得到同一个期末值,而不只是看到你写了“通过”。
小结:先查过程,再评价曲线
- 沿时间向前查。数据先可得,才能产生信号和订单;成交后才改变现金与持仓。不同步骤不能借用彼此尚未发生的结果。
- 从权益向后对账。每个点都应回到现金、股数、估值与费用。发现差异先找首次偏离,不要只修最后一个数字。
- 把实现与价值分开。漏费是实现问题,改费率是研究假设变化;两者都可能改变曲线。账本一致后,才有基础继续评价成本、风险与未来适用性。
下一步:G69 夏普比率怎么用将讨论怎样评价这类收益序列。
参考资料
-
Investor.gov:Types of Orders:订单与价格条件,不为本例撮合假设背书。
-
QuantConnect:Custom Data / Avoid Look-Ahead Bias:数据可得时间与前视风险。
-
QuantConnect:US Equity Corporate Actions:公司行动处理参考;具体实现并非所有券商的统一规则。
编写说明
- 本文为 C4-S1 合成机制练习,与 C2 的真实 ETF 教学规格分开。
- 合成材料、账本与核算。AI 起草,尚无人类金融签审或真人试读。未使用或声称验证 CoTrading 回测功能。