在量化策略开发中,随着策略复杂度的提升,实时计算所有指标(如复杂的因子、机器学习预测值或非量价数据)往往会成为回测效率的瓶颈。看海量化回测系统 V3.2 版本引入了基于 DuckDB 的指标预计算与持久化机制,允许用户将计算好的指标写入本地数据库,并在策略中以极低的延迟读取,从而实现“一次计算,多次复用”。
前置阅读建议:
本章内容涉及策略框架的高级应用。为了更好地理解相关概念,建议您在阅读本章之前,先充分理解看海量化回测系统的基础框架。请务必先行阅读官方教程的 3.1 – 3.3 节:
- 3.1 回测框架初探:API与基本结构
https://khsci.com/khQuant/chapter12/- 3.2 策略工具箱:常用函数与技巧
https://khsci.com/khQuant/chapter13/- 3.3 提示词工程与策略生成
https://khsci.com/khQuant/prompt/只有在充分掌握了策略文件的基本结构(如
init,khHandlebar)以及常用 API(如khGet,khPrice)的使用后,您才能更顺畅地运用本章介绍的高级数据管理功能。
本章将详细介绍相关的核心函数定义及使用流程。
一、 核心工具函数详解
系统提供了两个核心函数用于数据库的读写操作:khDuckWrite(写入)和 khDuckDB(读取)。它们位于 khQuantImport 模块中,可直接调用。
1.1 khDuckWrite:写入数据
khDuckWrite 是将数据持久化到本地 DuckDB 数据库的核心函数。它具备智能的 Schema 管理能力,当写入新字段时,会自动在数据库表中添加对应列,无需手动维护表结构。
函数定义
def khDuckWrite(
stock_list: Union[str, List[str]],
period: str,
data: Union[Dict, pd.DataFrame],
fields: List[str] = None,
time_col: str = "time",
duckdb_path: str = None,
field_types: Dict = None,
insert_missing: bool = False,
update_time: bool = True
) -> Dict:
参数说明
- stock_list: 股票代码或列表(如
'000001.SZ'或['000001.SZ', '600000.SH'])。 - period: 数据周期类型,支持
'1d'(日线),'1m'(分钟线),'5m'(5分钟线),'tick'(Tick数据)。 - data: 数据源,支持两种格式:
- Dict:
{stock_code: DataFrame}字典格式。 - DataFrame: 单只股票的 DataFrame,或包含
stock_code列的多股票 DataFrame。
- Dict:
- fields: (可选) 指定要写入的字段名列表。如果为
None,则自动推断data中除时间和股票代码外的所有列。 - time_col: (可选) 数据源中的时间列名,默认为
'time'。系统会自动将其标准化为 DuckDB 的时间格式。 - duckdb_path: (可选) 指定 DuckDB 数据根目录。默认为空,自动使用系统配置的路径。
- field_types: (可选) 字段类型映射字典,如
{'ma5': 'DOUBLE'}。通常不需要指定,系统会自动推断。 - insert_missing: (可选) 是否插入数据库中不存在的时间点记录。默认为
False(仅更新已有时间点的记录)。 - update_time: (可选) 是否自动更新
update_time列,记录最后修改时间。默认为True。
返回值
返回一个字典,包含每个股票的写入结果统计:
{
'000001.SZ': {
'updated': 100, # 更新的行数
'inserted': 0, # 插入的新行数
'rows': 100, # 总处理行数
'fields': ['ma5', 'ma10'] # 写入的字段
}
}
使用示例
import pandas as pd
from khQuantImport import *
# 假设我们计算好了 000001.SZ 的 MA5 数据
df = pd.DataFrame({
'time': ['2024-01-01', '2024-01-02'],
'MA5': [10.5, 10.6]
})
# 写入数据库
khDuckWrite(
stock_list='000001.SZ',
period='1d',
data=df,
fields=['MA5']
)
1.2 khDuckDB:读取数据
khDuckDB 是通用的数据读取接口,支持跨周期、跨标的获取历史数据,并支持自动复权处理。
函数定义
def khDuckDB(
stock_list: Union[str, List[str]],
period: str,
fields: List[str] = None,
start_time: str = None,
end_time: str = None,
dividend_type: str = 'none',
duckdb_path: str = None,
return_format: str = 'dict'
) -> Dict[str, pd.DataFrame]:
参数说明
- stock_list: 股票代码或列表。
- period: 周期类型 (
'1d','1m','5m','tick')。 - fields: (可选) 需要读取的字段列表。
None表示读取所有字段。 - start_time: (可选) 开始时间,格式
'YYYYMMDD'或'YYYYMMDDHHmmss'。 - end_time: (可选) 结束时间。
- dividend_type: (可选) 复权类型,支持
'none'(不复权),'front'(前复权),'back'(后复权) 等。 - duckdb_path: (可选) 自定义数据库路径。
- return_format: (可选) 返回格式,目前仅支持
'dict'。
返回值
返回字典 {股票代码: DataFrame}。
二、 策略集成:如何在回测中使用自定义指标
在策略中使用自定义指标主要有两种方式。推荐使用 方式 A,因为它利用了内存预加载机制,效率极高。
方式 A:高性能内存读取(推荐)
这套方案通过 khAddExtraFields 在初始化阶段预加载数据,然后在 khHandlebar 中通过 khIndex 极速读取。
步骤 1:在 init 中声明字段
使用 khAddExtraFields 告诉回测引擎需要预加载哪些额外列。
def init(context, data):
# 声明需要使用 'MA_5' 和 'RSI_14' 这两个自定义指标
# 系统会在回测开始前,自动将这些列从 DuckDB 加载到内存
khAddExtraFields(data, ['MA_5', 'RSI_14'])
步骤 2:在 khHandlebar 中读取
使用 khIndex 函数获取当前时间步的指标值。此操作直接访问内存,零 IO 开销。
def khHandlebar(data):
# 获取当前股票代码
stock = "000001.SZ"
# 直接读取指标值
ma5 = khIndex(data, stock, 'MA_5')
rsi = khIndex(data, stock, 'RSI_14')
# 使用指标进行逻辑判断
if rsi > 80:
# 执行卖出逻辑...
pass
函数说明:khIndex(data, stock_code, field)
* 功能: 获取指定股票、指定字段在当前时间点的值。
* 特点: 实际上是 khPrice 的语义化别名,共享其高效的内存访问机制。如果字段不存在或值为 NaN,返回 0.0。
方式 B:灵活动态读取
如果需要在策略中获取过去一段历史区间的数据(例如“获取过去100天的 MA5 序列”),或者需要跨标的读取数据(例如“在交易 A 股时读取 B 股的数据”),可以使用 khDuckDB。
注意:
khDuckDB涉及磁盘 IO 和数据库查询,频繁调用会降低回测速度。建议仅在低频场景(如每日收盘后)使用。
def khHandlebar(data):
# 示例:获取 000001.SZ 过去 20 天的 close 和 MA_5 数据
data_map = khDuckDB(
stock_list=['000001.SZ'],
period='1d',
fields=['close', 'MA_5'],
start_time='20230101', # 示例时间,实际应根据 khGet(data, 'date') 动态计算
end_time='20230120'
)
df = data_map.get('000001.SZ')
if df is not None and not df.empty:
# 进行复杂的历史数据分析...
pass
三、 配套案例代码详解
为了方便大家上手,系统在 strategies 目录下提供了 4 个完整的代码案例,覆盖了从“数据计算写入”到“策略读取回测”的全流程。这些案例按序号 4-1 到 4-4 排列,建议按照顺序阅读和运行。
3.1 数据写入案例
这两份代码不是回测策略,而是独立运行的 Python 脚本。你需要直接在 IDE 中运行它们,将计算好的指标写入到本地 DuckDB 数据库中。
【4-1】基础入门:khDuckWrite_simple.py
- 文件路径:
strategies/【4-1写入和读取数据库指标-写入数据库案例】khDuckWrite_simple.py - 功能: 演示最基础的“读取-计算-写入”流程。
- 重要提示: 运行前请务必打开代码文件,将
duckdb_path变量修改为您本地实际的 DuckDB 数据存放目录(例如D:\stock_data),否则无法正确写入。 - 逻辑:
- 从 DuckDB 读取
000001.SZ的历史收盘价 (close)。 - 计算 5 日均线 (
ma5)。 - 使用
khDuckWrite将ma5字段写回数据库。
- 从 DuckDB 读取
- 适用场景: 适合初学者理解
khDuckDB和khDuckWrite的基本用法。
【4-2】进阶实战:khDuckWrite_macd.py
- 文件路径:
strategies/【4-2写入和读取数据库指标-写入数据库案例】khDuckWrite_macd.py - 功能: 演示批量处理股票池及多字段写入。
- 重要提示: 同样,运行前请务必修改代码中的
duckdb_path为您本地的真实路径。 - 逻辑:
- 读取
沪深300成分股列表。 - 遍历每只股票,读取历史行情。
- 计算 MACD 指标的三个分量:
DIF,DEA,MACD。 - 批量将这三个字段写入数据库。
- 读取
- 适用场景: 适合需要大规模预计算复杂因子(如 MACD, KDJ, Bollinger 等)的用户。
3.2 策略读取案例
这两份代码是真正的回测策略,需要在看海量化回测系统的回测界面中加载运行。它们演示了如何在回测中利用刚才写入的数据。
【4-3】动态读取策略:DuckDB_MACD_Strategy.py
- 文件路径:
strategies/【4-3写入和读取数据库指标-读取数据库指标的策略案例】DuckDB_MACD_Strategy.py - 核心方法: 使用
khDuckDB函数在khHandlebar中动态查询。 - 逻辑:
- 在每一天,针对每一只股票,调用
khDuckDB查询过去几天的DIF和DEA数据。 - 根据查询到的 DataFrame 判断金叉/死叉,发出买卖信号。
- 在每一天,针对每一只股票,调用
- 特点: 代码逻辑直观,适合低频交易或需要回溯长周期历史数据的场景。但在全市场回测时速度较慢(因为涉及频繁的磁盘 IO)。
【4-4】高性能策略:DirectIndex_MACD_Strategy.py(强烈推荐)
- 文件路径:
strategies/【4-4直接读取底层指标-自定义指标策略案例】DirectIndex_MACD_Strategy.py - 核心方法: 使用
khAddExtraFields+khIndex组合拳。 - 逻辑:
- Init 阶段: 调用
khAddExtraFields(data, ["DIF", "DEA", "MACD"]),通知引擎预加载这三个字段。 - Handlebar 阶段: 调用
khIndex(data, stock, "DIF")直接从内存获取当日指标值。 - 状态维护: 使用全局字典
g_yesterday_indicators记录上一日的指标值,配合当日值判断金叉/死叉。
- Init 阶段: 调用
- 特点: 极速回测。由于数据预先加载到内存,读取操作零 IO 开销,非常适合全市场高频或中频策略的回测。
3.3 推荐的学习路径
- 运行 4-1: 确保环境配置正确,成功写入简单的
ma5数据。 - 运行 4-2: 尝试计算并写入 MACD 数据(建议先用少量股票测试)。
- 回测 4-4: 加载
DirectIndex_MACD_Strategy.py,体验基于预计算数据的极速回测。 - 参考 4-3: 如果你的策略逻辑非常复杂(例如需要跨品种套利),再参考此案例学习如何灵活读取 DuckDB。
四、 最佳实践总结
- 预计算优于实时计算: 凡是逻辑复杂、输入固定的指标,都应优先考虑预计算并存入 DuckDB。
- 内存读取优于磁盘读取: 在策略主循环中,尽量使用
khIndex读取预加载的数据。khDuckDB仅用于复杂的历史回溯或跨标的查询。 - 字段命名规范: 自定义指标字段名建议具备可读性且避免与内置字段(open, close, volume 等)冲突,例如使用
MY_MACD,FACTOR_MOMENTUM等。 - 数据对齐: 写入数据时,
khDuckWrite默认会根据time列自动对齐。建议确保时间戳格式标准(YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)。
通过掌握这套自定义指标管理机制,您可以构建出更高效、更复杂的量化回测系统,将数据工程与策略逻辑解耦,专注于策略本身的价值发现。