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_data、prepare_data_buffer等从 DataSource 拉取并缓存所需全量历史;再通过create_data_windows用rolling_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.py中check_and_prepare_backtest_data;qt_operator.py中prepare_data_buffer、create_data_windows(对buffered_data.values做rolling_window并与运行 schedule 对齐);ulc决定窗口是否含当根。按步注入:
qt_operator.py中run_strategy(step_index)每步对策略调用update_running_data_window(..., window_index=step_index);strategy.py中update_running_data_window将对应数据窗口赋给策略属性,get_data()读取的即为该步窗口。
更多实现细节见《架构与设计》各章与 API 参考。