在 UTC 10:14,一套简单的突破策略在价格突破 20 分钟高点后买入了某种加密货币永续合约。回测显示成交价为 $64,210。这个数字确实出现在数据中,信号的时间戳也正确,模拟订单还在下一根 bar 到达。我们差点就把结果称为干净利落。
随后我们对照了交易品种 ID。信号来自 BTCUSDT 永续合约,成交却来自 BTCUSD 反向合约。两者的交易价格都接近 $64,210,也都是流动性充足的市场。但它们属于不同的订单簿,价格、最小变动价位、深度和费率安排各不相同。回测拼出了一个交易者根本无法进入的市场。
策略本身没什么特别之处:收盘价高于滚动高点时入场,收盘价低于滚动低点时离场,每个仓位按固定美元风险确定规模。只有当我们不再问“BTC 值多少钱?”而是提出更精确的问题时,问题才浮现出来。我们问的是:“在那个时刻,这笔订单在这个确切的合约里能买到什么?”
策略运行之前,图表就已经是综合行情了
我们的研究行情源让跨交易所比较变得轻松。它会统一交易品种名称,并显示合并后的价格序列。这对观察市场很方便,却不适合作为执行依据。综合行情可能显示各交易所中的最优价格,但策略只能向一个交易场所下单。
突破发生时,合并行情的 bar 最高价是 $64,205。所选合约自己的最新成交价已经涨到 $64,218。与此同时,反向合约的一条缓存报价仍显示卖一为 $64,210。模拟器把一个序列中的突破信号和另一个序列中更有利的卖一价拼在了一起。每个数值都是真的,但组合起来却是虚构的。
| 输入项 | 回测使用的数据 | 订单所需的数据 |
|---|---|---|
| 突破高点 | 合并的 BTC 价格 | 所选合约的价格历史 |
| 入场参考价 | 缓存的反向合约卖一价 | 交易品种的最新卖一价 |
| 成交数量 | 请求的仓位规模 | 各价格档位上的可成交数量 |
市场数据表中的一行看起来可能很完整,字段却描述着不同的市场。交易品种别名并不能证明两个品种共用同一本订单簿。信号生成和执行流程都必须保留合约 ID、交易场所、报价币种和时间戳。
我们对照订单簿重放了这笔订单
我们用所选永续合约自己的最优卖价,以及决策时可用的订单簿更新,重新模拟了入场。报价是最新的,但最优价位上只有 0.18 BTC。模拟订单数量为 0.50 BTC。剩余的 0.32 BTC 得以更高的卖价成交。
调整后的入场价每 BTC 比记录的成交价高 $21,尚未计入费用。单笔交易的差别不大,但在整个测试中,策略的大部分毛利润来自少数几次快速突破。用正确的订单簿重放这些入场后,毛收益减少了 31%;计入 taker 费用和冲击成本后,收益进一步降低。
我们还发现,有两次最优买卖报价是在信号决策时间之后才到达的。按最近时间戳进行匹配的回测,可能会悄悄选中未来的更新。现在我们改为向过去匹配:报价必须在订单发出时已经被观测到,而且我们会记录报价的时效。如果最新可用报价太旧,模拟器就会将订单标记为无法成交,或应用明确的过时数据规则。
怎样的价格才可交易
bar 收盘价有助于计算信号,但它无法证明特定订单能以该价格成交。要判断订单能否成交,价格必须属于确切的交易品种和交易场所,在订单到达时可用,并且对手方数量足以满足请求的规模。如果数据无法回答这些问题,诚实的输出应该是一个范围或缺失的成交,而不是编造一个精确价格。
我们保留了合并行情序列用于了解市场背景,并将它从执行流程中移出。现在信号依据所选合约自身的历史数据;订单则根据该合约的订单簿进行重放,成交计算完成后再计入费用和数量。流程多了一点衔接工作,权益曲线也没那么好看了。
那次突破确实发生过。只是回测给它安排了一个比策略所选市场更好的市场。
← 全部文章


