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