9. 设计初衷与独特优势

本章集中说明 qteasy 的设计初衷独特优势:为何以高速向量化回测为目标却摒弃“全时间轴向量化”,如何通过“向量化 + 数据隔离”兼顾速度与防未来函数,以及 use_latest_data_cycle 等可配置项对长期回测的影响。与 总体架构与设计思路策略中的数据 形成呼应。

9.1. 1. 为何不做全时间轴向量化

1.1 未来函数风险

若在整个时间轴上一次性计算指标(例如全历史 EMA),得到的历史序列与“按当时可见的历史窗口逐段计算”的结果不一致:全轴 EMA 会用到后续价格,实盘无法复现,导致回测与实盘偏离。因此,qteasy 不在时间维上做“整段一次性”的指标计算,而是按步只给策略当前步可见的数据窗口。

1.2 状态与规则无法表达

若在时间维上完全向量化,则难以正确实现:

  • T+1 交割交易费用最小交易单位(MOQ) 等约束;

  • 是否允许使用“交易当时”的最新数据(当根 K 线)的建模选择。

这些细节在长期回测中会产生成倍的收益差异(例如十年回测中,“是否使用当根 K 线数据”的微小差异会显著改变累计收益)。qteasy 将“是否用当根数据”显式做成 use_latest_data_cycle 配置项,而非隐式假设。

9.2. 2. 事件驱动的替代与取舍

常见做法是采用事件驱动:每个交易事件逐标的取数、逐笔计算指标,从而彻底避免未来函数并贴近实盘,但速度较慢。qteasy 的取舍是:保留“按时间步、按当时可见数据”的正确性,但在单步内用向量化 + Numba 加速,在保证正确性的前提下兼顾速度。

9.3. 3. qteasy 的做法:向量化 + 数据隔离

3.1 回测主体向量化

回测循环与交易计算(买卖量、费用、持仓、交割)已用 NumPy + Numba 实现:时间维顺序步进,标的维在单步内向量化。参见 回测引擎与性能(设计视角)

3.2 数据提前打包装配与按步注入

  • 准备阶段:回测前通过 check_and_prepare_backtest_dataprepare_data_buffer 等从 DataSource 拉取并缓存所需全量历史;再通过 create_data_windowsrolling_window 生成按运行节奏对齐的数据窗口流(每个 step 对应一个固定长度的历史窗口)。

  • 每步运行前:引擎调用 update_running_data_window,把当前 step 对应的数据窗口写入策略属性;策略在 realize() 内通过 get_data(dtype_id) 取到的仅是该步可见的窗口,在此窗口上计算 EMA 等指标,与实盘“当时仅能看到的历史”一致,从机制上避免未来函数。

数据流是“声明 → 引擎按步注入 → get_data 引用”,策略代码不包含“读表”或“拉取”逻辑。详见 策略如何声明与使用数据

3.3 use_latest_data_cycle(ulc)的含义与影响

use_latest_data_cycle 控制数据窗口是否包含当根 K 线

  • True:窗口可含当前 bar(如收盘价),对应“交易时是否允许用当时最新价”的建模。

  • False:窗口严格小于当前时刻,仅用“过去已收盘”的数据。

该开关对长期回测结果影响显著,qteasy 将其作为可配置项在策略声明与运行配置中暴露,文档与 API 中应明确写出并指向配置说明。

9.4. 4. 独特优势小结

  • 高速 + 防未来函数:在保证“每步仅用当时可见数据”的前提下,实现向量化 + Numba 的快速回测,兼顾速度与回测可信度。

  • 精细交易建模:T+1、费用、交割、MOQ 以及“是否用当根数据”等均可建模,长期回测对这类细节敏感,qteasy 支持显式配置。

  • 简洁接口:用户只需在 realize()get_data() 取数并计算信号,数据窗口与时间对齐由框架负责,降低误用未来数据的可能。

9.5. 5. 代码依据摘要

  • 数据准备与窗口:history.pycheck_and_prepare_backtest_dataqt_operator.pyprepare_data_buffercreate_data_windows(对 buffered_data.valuesrolling_window 并与运行 schedule 对齐);ulc 决定窗口是否含当根。

  • 按步注入:qt_operator.pyrun_strategy(step_index) 每步对策略调用 update_running_data_window(..., window_index=step_index)strategy.pyupdate_running_data_window 将对应数据窗口赋给策略属性,get_data() 读取的即为该步窗口。

更多实现细节见《架构与设计》各章与 API 参考。