⚠ 交易存在重大亏损风险。本网站的所有内容仅供教育用途,不构成财务建议。
第 7 课,共 13 课 · 免费

向量化回测:信号、仓位、用位移避免前视偏差、交易成本与资金曲线

在 pandas 中为一条移动平均线规则构建完整的向量化回测,包括仓位位移、每笔交易成本、资金曲线以及与基准的比较。

作者: Moustafa Hijazi内容审核日期: 2026-10-30阅读约 7 分钟自学课程

你将学到什么

  • 向量化回测的五列结构:信号、仓位、资产收益率、成本和策略收益率。
  • 为什么对仓位列执行 shift(1) 这一行代码,就是区分诚实测试与前视幻想的关键。
  • 如何根据仓位变化收取按比例计算的交易成本,并构建以欧元计的资金曲线。
  • 向量化测试能模拟什么、不能模拟什么,以及何时需要第 8 课的事件驱动引擎。

讲解

向量化回测用列运算一次性计算整段历史,而不是逐根 K 线循环。课程《系统化交易与回测》中的电子表格回测就是这种结构;pandas 只需几毫秒就能跑完几十年的数据。它适用于基于收盘价操作的“做多或空仓”或“始终持仓”类规则;K 线内部的止损和特定的成交价格则交给第 8 课处理。

第 1 步,信号。signal = (close > sma(close, 50)).astype(int) 在收盘价高于其 50 日均线时为 1,否则为 0。它描述的是你在第 t 根 K 线收盘时已知的信息。

第 2 步,仓位。你不能依据一个尚未看到的收盘价进行交易;最早能持有第 t 根 K 线信号的时间是第 t + 1 根 K 线,因此 position = signal.shift(1).fillna(0)。现在仓位列的第 t 行表示“在第 t 根 K 线期间持有的敞口”,它是在第 t − 1 根 K 线结束时决定的。省略这次位移,策略就会用第 t 根 K 线的收盘价赚取第 t 根 K 线的收益,这就是前视偏差:使用了决策时并不具备的信息。

第 3 步,资产收益率。ret = close.pct_change().fillna(0)(第 5 课)。由于第 t 根 K 线期间的仓位是在第 t − 1 根 K 线收盘时确定的,position * ret 就是第 t 根 K 线的策略毛收益率。隐含的假设是在第 t − 1 根 K 线的收盘价成交;第 8 课将处理在下一根 K 线开盘价成交的情形。

第 4 步,成本。佣金、点差和滑点被建模为每单位仓位变化的一个比例费率:turnover = position.diff().abs().fillna(0) 在从空仓转为做多或反向操作时为 1,在多空反转时为 2。cost = turnover * c,其中 c 是以小数表示的单边成本(0.1% 即 0.001)。成本计入仓位发生变化的那根 K 线的收益率列中。

第 5 步,策略收益率与资金。strat = position * ret - cost,以及 equity = capital * (1 + strat).cumprod()。买入并持有基准为 capital * (1 + ret).cumprod()。务必用相同的指标比较两者(第 9 课);仅仅用现金稀释买入并持有,会让回撤和收益同等降低,这并不是能力。

写明假设条件:敞口是按日再投资的资金比例,在收盘价成交,成本按比例计算,现金不计利息,股息仅通过复权数据体现,不计税费。这样,测试回答的只是一个很窄的问题:扣除成本后,这条择时规则相对于持有该资产是否带来了任何增益?

相信输出结果之前的检查。(1) 把成本设为零、把信号设为常数 1;资金必须与买入并持有完全相等。(2) 用 turnover.sum() / 2 统计往返交易次数,并检查是否合理。(3) 确保指标预热期的 NaN 行对应的仓位为零,而不是产生一笔虚假交易。

实例演算

假设示例:八个以欧元计的收盘价,100、101、103、102、104、103、101、100;收盘价严格高于其 3 日 SMA 时做多,否则空仓;单边成本 0.1%;资金 €10,000;不计利息、股息和税费。

K 线 收盘价 SMA3 信号 仓位 收益率 换手 策略 资金 €
0 100 – 0 0 0 0 0 10,000.00
1 101 – 0 0 +1.000% 0 0 10,000.00
2 103 101.33 1 0 +1.980% 0 0 10,000.00
3 102 102.00 0 1 −0.971% 1 −1.071% 9,892.91
4 104 103.00 1 0 +1.961% 1 −0.100% 9,883.02
5 103 103.00 0 1 −0.962% 1 −1.062% 9,778.11
6 101 102.67 0 0 −1.942% 1 −0.100% 9,768.33
7 100 101.33 0 0 −0.990% 0 0 9,768.33

第 3 根 K 线:第 2 根 K 线的信号为 1(103 > 101.33),因此第 3 根 K 线期间的仓位为 1。资产从 103 跌到 102(−0.971%),换手 1 产生 0.1% 的成本,策略收益率为 −1.071%,资金为 10,000 × (1 − 0.01071) = €9,892.91。第 3 根 K 线的收盘价等于 SMA,因此严格规则给出 0,仓位在第 4 根 K 线期间平掉(成本 0.1%,无敞口)。最终资金 €9,768.33(−2.32%),而买入并持有恰好为 €10,000。四次单边交易的成本合计 €39.36。

再看前视版本,用 signal 代替经过位移的 position:策略在第 2 和第 4 根 K 线期间“持仓”,而这两个上涨日只有在收盘之后才能识别出来,最终资金为 €10,356.65(+3.57%)。少了一个 shift(1),−2.32% 就变成了 +3.57%。

常见错误

位移了错误的对象,或者位移了两次位移价格而不是仓位,或者同时位移信号和收益率,要么会重新引入前视偏差,要么会把入场推迟一天。请坚持一种约定:指标和信号在第 t 根 K 线的收盘价上计算;position = signal.shift(1);收益率就是普通的 pct_change()。

练习活动

在纸上使用同样的八个收盘价和规则完成:(a) 在单边成本为 0.2% 的情况下重新计算最终资金;(b) 说明往返交易的次数;(c) 一位同事提议用收盘价填充 SMA 最前面的 NaN 值,以便“从第一天就能开始交易”;简要解释为什么这是个问题。

显示答案

(a) 只有四根有换手的 K 线发生变化。第 3 根:−1.171% → €9,882.91;第 4 根:−0.2% → €9,863.15;第 5 根:−1.162% → €9,748.58;第 6 根:−0.2% → €9,729.09。最终亏损 2.71%,而成本为 0.1% 时为 2.32%;成本费率翻倍额外增加了 €39.24 的拖累。

(b) 换手合计为 4 次单边变化,因此是 2 次往返交易(第 3 根 K 线期间入场、第 4 根离场;第 5 根入场、第 6 根离场)。

(c) 用收盘价填充 SMA,会根据一个尚不存在的指标产生交易,因此这些交易的逻辑与被测试的规则不同;请在预热期保持零仓位,并注明排除了多少根 K 线。

局限性:八根 K 线只能证明运作机制,而不能证明规则有效;按比例计算的成本和按收盘价成交会低估流动性差的品种的滑点。

要点回顾

  • 向量化回测由五列组成:信号、仓位(经过位移)、资产收益率、基于换手的成本、策略收益率;资金是累积乘积。
  • position = signal.shift(1) 没有商量余地;可以通过去掉它并观察结果如何膨胀来进行检验。
  • 根据 position.diff().abs() 收取成本,并始终在相同假设下与买入并持有进行比较。
  • 向量化测试适用于收盘到收盘的规则;止损、仓位规模和真实成交需要第 8 课的事件驱动引擎。

下一步

第 8 课构建一个事件驱动回测:一个逐根 K 线运行的循环,包含 ATR 止损、仓位规模以及以 R 倍数衡量的交易清单。

自测

回答问题后核对答案。你可以随意重试多次。

1. 在向量化回测中,为什么仓位列定义为 signal.shift(1) 而不是 signal?

2. 一个策略连续三根 K 线做多,资产收益率分别为 +1.0%、−2.0% 和 +0.5%,在第一根 K 线开始时入场,在第三根 K 线结束时离场,单边成本为 0.1%。从 €10,000 起步,期末资金是多少(成本计入入场 K 线和离场 K 线)?

3. 你把成本设为零,并强制每根 K 线的信号都为 1。策略资金曲线最终比买入并持有低 4%。这说明了什么?

供您自行复习的自动自测。您的答案在浏览器中核对,我们不会查看或评估。这不是考试,也不是资格证书。

您的进度

“已完成”仅表示您将本课标记为已学完,并不是对您理解程度的测试。 学习进度和笔记仅保存在此设备的此浏览器中,而非账户中。