研究方法 回测引擎

第 2 章

回测引擎

我们的回测引擎由团队自主研发,采用事件驱动架构。引擎完全按照现实中的情形模拟交易决策——每次只处理一根 K 线,绝不使用未来的信息。

1. 事件驱动架构

引擎的核心设计原则是:策略只能看到当前 K 线及更早的数据,永远无法看到未来的 K 线。这是由系统架构强制保证的,而不是仅靠制度约束。策略代码在物理层面上不可能读取前瞻数据。

处理循环(每根 K 线)

数据输入

OHLCV K 线到达

指标引擎

重新计算指标

策略逻辑

评估入场/出场规则

订单生成

创建待处理订单

订单执行

按下一根 K 线开盘价成交

组合更新

盈亏、权益曲线

顺序处理

每根 K 线按时间顺序逐一处理。引擎设有严格的游标——可以读取第 T 根及之前的所有 K 线,但第 T+1 根此时尚不存在。

结果可复现

只要数据文件和策略参数相同,引擎每次都会得出完全一致的结果。没有随机性,各次运行之间也没有浮点数误差。

多资产支持

引擎可以同时对多个标的运行策略,遵守投资组合层面的资金约束,并考虑跨资产相关性。

2. 信号生成

交易信号是通过将指标函数应用于价格序列而生成的。第 T 根 K 线的所有指标值,仅使用该时点可获得的数据计算,即从第 0 根到第 T 根(含)的价格。信号随后在第 T+1 根 K 线的开盘价执行。

# 简化的伪代码——说明如何禁止前瞻 for T in range(warmup_period, len(bars)): current_bar = bars[T] # 只能看到这根 K 线的 OHLCV history = bars[:T+1] # 截至并包括 T 的所有 K 线 # 指标仅基于 [0..T] 计算——无法访问未来的 K 线 sma_fast = mean(history.close[-20:]) sma_slow = mean(history.close[-50:]) rsi = rsi_func(history.close, period=14) # 信号出现在第 T 根 → 在第 T+1 根开盘时执行(下一次函数调用) if sma_fast > sma_slow and rsi < 70: queue_order(side='BUY', qty=position_size(), exec_bar=T+1)

支持的指标类型

  • ▸ 移动平均线(SMA、EMA、WMA、DEMA、TEMA、KAMA)
  • ▸ 动量指标(RSI、MACD、Stochastic、CCI、ROC)
  • ▸ 波动率指标(ATR、Bollinger Bands、Keltner Channels、HV)
  • ▸ 成交量指标(OBV、CMF、VWAP、Volume Profile)
  • ▸ 趋势指标(ADX、Parabolic SAR、Ichimoku Cloud)
  • ▸ 震荡指标(Williams %R、Ultimate Oscillator、MFI)
  • ▸ 统计指标(Z-score、线性回归斜率、相关系数)
  • ▸ 自定义复合指标(因策略而异)

信号类型

  • ▸ 做多入场/做空入场
  • ▸ 做多出场/做空出场(明确指定)
  • ▸ 止损触发(按价格或 ATR 倍数)
  • ▸ 止盈触发(固定目标或追踪式)
  • ▸ 加仓/减仓(金字塔式入场)
  • ▸ 按时间出场(固定持仓期)
  • ▸ 市场状态过滤(市场环境闸门)
  • ▸ 按风险出场(组合回撤阈值)

3. 成交模型

成交模型决定订单如何成交。我们有意采用保守假设,使结果略低于真实表现。与其失望,不如有意外之喜。

成交参数

成交价
下一根 K 线开盘价(T+1 开盘) 避免不现实的同一根 K 线成交
滑点
每笔交易 0.05%(默认) 对流动性充足的股票作保守估计
佣金——香港
往返 0.10% 香港零售券商的一般费率
佣金——美国
往返 0.05% 美国券商的一般费率
佣金——日本/欧洲
往返 0.15% 反映当地较高的成本
最小交易单位
按各交易所规则执行 不可买卖零股
卖空
历史上允许卖空时方可进行 适用交易所的价格上升规则
融券成本
年化 0.5%–5%(视策略而定) 适用于卖空仓位

为何采用下一根 K 线开盘价?

大多数零售回测工具允许在产生信号的同一根 K 线上成交——这在现实中是不可能的。如果均线交叉发生在第 T 根 K 线收盘时,你无法按第 T 根的收盘价交易,因为信号要到该 K 线收盘后才能得知。

我们的规则: 信号在第 T 根 K 线收盘时出现 → 订单排队 → 在第 T+1 根 K 线开盘时成交。这是现实中最早可以执行的时间。
我们绝不会做的事: 按第 T 根 K 线的收盘价、最高价或最低价成交。这些价格要到 K 线收盘后才能得知——实时交易中无法按这些价格成交。

4. 仓位规模模型

仓位规模决定每笔交易分配多少资金。报告所采用的仓位模型始终在报告开头披露。不同策略会根据自身的风险特征采用不同的模型。

固定比例(占权益百分比)

最常见

最广泛使用的模型。每笔交易承担的风险为当前账户权益的固定百分比。账户增长时,仓位按比例增大;账户缩水时,仓位随之缩小。

# 公式
仓位规模 = 账户权益 × 风险% ÷ (入场价 – 止损价)
# 示例
账户:HK$100,000 | 风险:2% | 入场:HK$10 | 止损:HK$9 → 数量 = HK$2,000 ÷ HK$1 = 2,000 股

固定金额

简单策略

无论账户大小,每笔交易分配固定金额。易于理解,但无法随账户增长而调整。

# 公式
仓位规模 = 固定金额 ÷ 入场价
# 示例
固定:HK$10,000 | 入场:HK$10 → 数量 = 1,000 股

波动率调整(基于 ATR)

趋势策略

仓位规模与标的近期波动率成反比调整,波动率以平均真实波幅(ATR)衡量。波动较大的标的仓位较小,波动较小的标的仓位较大。

# 公式
仓位规模 = (账户权益 × 风险%) ÷ (ATR(14) × ATR 倍数)
# 示例
账户:HK$100,000 | 风险:2% | ATR:HK$0.50 | 倍数:2 → 数量 = HK$2,000 ÷ HK$1 = 2,000 股

凯利公式(半凯利)

进阶用法

凯利公式根据策略的历史胜率和风险回报比,以长期增长最大化为目标。我们采用半凯利(公式结果的 50%)以降低波动。

# 公式
f* = (W × R – (1–W)) ÷ R → 半凯利 = f* ÷ 2
# 示例
胜率:55% | 风险回报比:2:1 → f* = (0.55×2 – 0.45)/2 = 32.5% → 半凯利 = 权益的 16.25%

5. 投资组合层面的模拟

当策略同时交易多个标的时,引擎会执行投资组合层面的约束,避免出现可以同时持有无限数量交易的不现实情形。

资金分配

每个未平仓仓位都会占用资金。引擎会跟踪已投入的总资金,若剩余现金低于最小仓位规模,便会拒绝新的入场。除非策略明确指定,否则不使用杠杆。

最大持仓数量

每个策略都会规定可同时持有的最大仓位数量。达到上限后,新的入场信号会排队等候,待现有仓位平仓后再成交。这可以避免过度分散。

组合回撤止损

如果整体组合回撤超过可配置的阈值(默认:25%),所有仓位将被平仓,并在冷静期内暂停新的入场。这模拟了风险管理的纪律。

再平衡与偏离

对于多资产策略,引擎会定期将仓位权重调整回目标配置。再平衡频率和容差区间可按策略分别配置。

6. 支持的时间周期

引擎支持多种数据分辨率。策略适用的时间周期取决于其持仓期和信号频率。

时间周期 典型持仓期 策略类型 适用范围
日线(收盘) 5 天 – 12 个月 波段交易、持仓交易 所有市场
周线 1–6 个月 趋势跟踪、长线 所有市场
月线 3–24 个月 宏观策略、因子模型 所有市场
4 小时 1–5 天 短线波段 香港、美国(通过聚合)
1 小时 4–48 小时 日内波段 仅限香港、美国
15 分钟 1–8 小时 日内交易 仅限香港、美国
5 分钟 30 分钟 – 4 小时 超短线策略 仅限美国(历史数据有限)