10. 過程數據(proc.*)與動態回測(設計說明)
本章說明 qteasy 中過程數據(process data)的定義、訪問方式,以及回測時靜態分支與動態分支的選擇與一致性約定,供實現與擴展時參考。使用層面的說明可在《策略如何聲明與使用數據》與 API 文檔中配合閱讀。
10.1. 1. 背景与目标
1.1 過程數據的含義
部分策略需要依賴隨回測或實盤執行路徑而變化的數據,例如:
當前/歷史持倉、可用現金;
歷史成交數量、成交價格、交易成本;
由持倉與價格推導的市值、總資產等。
這類數據無法在回測開始前一次性預生成,只能由 Backtester(回測)或 Trader(實盤)在運行過程中維護,並在策略生成信號的每一步按“當前可見範圍”提供給策略。我們將其統稱爲過程數據。
1.2 設計目標
統一入口:過程數據與靜態歷史數據一樣,通過
Strategy.get_data()獲取,降低學習成本。無前視:策略在生成第 k 步信號時,不能看到第 k 步的成交結果,只能使用已完成步的歷史。
回測/實盤一致:同一套策略與
get_data('proc.xxx')調用方式,在回測與實盤中均可使用;當需要過程數據時走動態執行路徑,否則可與原有靜態路徑在結果上保持一致。
10.2. 2. 过程数据的统一定义(proc.*)
2.1 命名與曝光方式
所有過程數據對策略以
proc.<field_name>的形式曝光,例如proc.own_cash、proc.trade_records。過程數據不需要在策略的
__init__中通過data_types聲明,由 Backtester / Trader 在運行時注入,策略只需在realize()中按需調用get_data('proc.xxx', ...)。
2.2 已實現的內建字段
當前版本已實現並可在策略中使用的過程數據字段包括:
類別 |
字段名 |
含義 |
|---|---|---|
賬戶標量 |
|
當前步開始時賬戶總現金 |
|
當前步開始時可用於下單的現金 |
|
|
當前步開始時總資產市值(持倉估值 + 現金) |
|
持倉向量 |
|
當前步開始時各標的總持倉數量 |
|
當前步開始時各標的可賣出數量 |
|
|
當前步開始時各標的持倉市值(由內部 price 與持倉計算) |
|
成交結果 |
|
各步各標的的實際成交數量(正買負賣) |
|
各步各標的的交易成本 |
|
|
各步各標的的成交價格 |
上述字段在回測與實盤中的時間語義與可見性約束見第 4 節。
2.3 後續可擴展字段(可選)
設計上可繼續擴展的字段包括:proc.realized_pnl、proc.unrealized_pnl、proc.last_trade_price、proc.last_trade_volume 等,具體以實現與文檔爲準。
10.3. 3. 访问接口:Strategy.get_data() 与 proc.*
3.1 靜態數據(無 proc. 前綴)
單源:
self.get_data('close_E_d');多源:self.get_data('close_E_d', 'high_E_d')。靜態數據不支持
lag/window參數;若傳入則拋出英文ValueError。
3.2 過程數據(proc. 前綴)
調用示例:
self.get_data('proc.own_cash'):截至當前步的現金序列;self.get_data('proc.own_cash', lag=0):最近一步的現金;self.get_data('proc.own_cash', lag='1d'):按時間回溯 1 天對應的步;self.get_data('proc.own_cash', window='5d'):過去 5 天內的窗口切片。
約束:
一次調用僅允許一個
proc.*字段;多字段或與靜態數據混合調用時拋出英文ValueError。lag與window不可同時指定;支持lag爲整數(步)或字符串(如'1d'、'8h'),window爲字符串(如'5d'、'8h')。
返回值:統一爲
np.ndarray,形狀與數據類型以 API 文檔爲準。
10.4. 4. 回测分支选择与过程数据协作
4.1 靜態分支與動態分支
靜態分支(
_backtest_static_operator):一次性對所有時間步調用run_strategies生成全部信號,再用 Numba 向量化函數backtest_batch_steps等完成回測。適用於不依賴過程數據的策略。動態分支(
_backtest_dynamic_operator):按時間步循環,每步生成信號 → 解析並模擬成交 → 更新持倉與現金,再進入下一步。過程數據由 Backtester 維護並在每步前注入 Operator,供策略通過get_data('proc.xxx')訪問。
Backtester 在運行前通過 Operator.check_dynamic_data() 決定走哪條分支。
4.2 check_dynamic_data() 的判定邏輯(當前實現)
以下任一爲真則返回 True,走動態分支:
op_type == 『stepwise』:Operator 顯式配置爲逐步模式。
策略源碼中使用 proc.*:通過
_strategies_use_proc_data()檢查各策略realize()的源碼是否包含'proc.'或"proc.",若包含則視爲依賴過程數據。
因此,只要在 realize() 中調用了 get_data(『proc.xxx』),無需任何聲明即可自動走動態分支。過程數據僅通過 proc.* 訪問,不再支持通過 DataType 聲明(舊式 op_* 類型已移除)。
4.3 無前視保證
策略在第 k 步生成信號時:
賬戶/持倉類(如
own_cashes、own_amounts)最多可見到索引[0..k](即當前步開始時的狀態);成交類(如
trade_records、trade_cost)最多可見到[0..k-1],不包含本步尚未發生的成交。
實現上由 Operator 的
_current_signal_index與 Strategy 的_get_process_data_single()按上述範圍切片,Backtester 在每步生成信號前更新索引,保證無前視。
4.4 Backtester 與 Operator 的注入關係
Backtester(動態分支):在
_backtest_dynamic_operator入口處將own_cashes、available_cashes、own_amounts_array、available_amounts_array、trade_records_array、trade_cost_array、trade_price_array、trade_price_data等以_process_data_sources注入 Operator,並將_process_time_index設爲與op_signal_index對齊的時間軸。Operator:在
run_strategy(step_index)中,在每次調用stg.generate()前根據group_timing_table與group_merge_type計算並更新_current_signal_index,供 Strategy 截取“當前可見”的過程數據。
4.5 實盤(Trader)中的過程數據
當 operator.check_dynamic_data() 爲 True 時,Trader 在 _run_strategy() 中會:
將當前賬戶現金、持倉、可用數量及當前價格等組裝爲單步的
_process_data_sources與_process_time_index(實盤單次運行視爲一步);策略在該步內調用
get_data('proc.own_cash')等即可獲得當前賬戶/持倉視圖;成交歷史在該步內爲空,與“尚未發生本步成交”的語義一致。
10.5. 5. 动态/静态路径一致性约定
當策略不使用過程數據時:應走靜態分支;若因其他原因走動態分支,回測結果應與靜態分支在數值上完全一致(相同配置與數據下)。
當策略使用過程數據時:必須走動態分支,否則
_process_data_sources未注入會觸發 RuntimeError。測試約定:使用
StaticSignalStg(純靜態)與ProcAwareButStaticLogicStg(調用 proc 但不用於信號)在相同配置下回測,對own_cashes、own_amounts_array、trade_records_array等做數值一致性斷言,見tests/test_process_data_api.py的 B 組。
10.6. 6. 测试与文档索引
專門測試:
tests/test_process_data_api.pyA 組:
check_dynamic_data()在純靜態 / 使用 proc.* 策略下的行爲;B 組:靜態策略與“調用 proc 但邏輯等價”策略的回測數組一致性;
C 組:
get_data對靜態多源、lag/window 拒絕、proc 單字段與混合調用的報錯行爲;D 組:
proc.trade_records無前視驗證;E 組:基於 process data 的真實動態策略路徑正確性。
項目記憶:
.cursor/rules/process-data-and-dynamic-backtest.mdc(實現與約定摘要)。策略數據總覽: 策略如何聲明與使用數據;回測入口與模式:回測、實盤與優化。