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 參考。