问题描述
handle_data(context, data) API文档说明为:该函数每个单位时间会调用一次, 如果按天回测,则每天调用一次,如果按分钟,则每分钟调用一次。
参数 context: Context对象, 存放有当前的账户/标的持仓信息
data: 一个字典(dict), key是股票代码, value是当时的SecurityUnitData 对象. 存放前一个单位时间(按天回测, 是前一天, 按分钟回测, 则是前一分钟) 的数据. 注意:
请问这里传入data参数是干什么用的啊?怎么理解如下这句话呢?
为了加速, data 里面的数据是按需获取的, 每次 handle_data 被调用时, data 是空的 dict, 当你使用data[security],时该 security 的数据才会被获取.data 只在这一个时间点有效, 请不要存起来到下一个 handle_data 再用。
解决方案
在 JoinQuant(聚宽)量化平台中,handle_data(context, data) 是策略运行的核心函数。关于 data 参数的作用以及文档中那段话的理解,可以从以下几个方面来详细剖析:
1. data 参数是干什么用的?
简单来说,data 参数的作用是获取标的(如股票、基金等)在“前一个单位时间”的行情切片数据。
- 按天回测:
data里面装的是昨天的行情数据(开盘价、收盘价、最高价、最低价、成交量等)。 - 按分钟回测:
data里面装的是上一分钟的行情数据。
你可以通过 data['股票代码'] 获取到一个 SecurityUnitData 对象,然后调用它的属性,例如 data['000001.XSHE'].close 获取平安银行上一单位时间的收盘价。
2. 怎么理解“按需获取”和“空 dict”?
这其实是聚宽底层引擎的一种性能优化机制(延迟加载 / Lazy Load)。
- 为什么是空的? 假设全市场有 4000 多只股票,如果每次调用
handle_data时,系统都把这 4000 多只股票的数据全部查出来打包成一个巨大的字典传给你,那回测速度会慢得令人发指,而且极其消耗内存。 - 什么是按需获取? 当
handle_data刚被触发时,传给你的data确实是一个空壳子。只有当你的代码执行到data['000001.XSHE']这一句时,系统才会瞬间去底层数据库里把平安银行的数据拉取出来返回给你。你没查的股票,系统就不会去拉取数据,从而大大“加速”了回测过程。
3. 怎么理解“只在这一个时间点有效,请不要存起来”?
这是新手最容易踩的坑。量化回测是一个时间不断向前推进的过程。
- 时间切片概念:
data对象代表的是当前这一刻你能看到的历史切片。当时间推移到下一天(或下一分钟),系统会生成一个新的data对象传给handle_data。 - 错误做法:如果你把今天的
data存到了全局变量里(比如g.my_data = data),到了明天,你再去访问g.my_data['000001.XSHE'].close,你拿到的依然是昨天的数据,而不是最新的数据。更严重的是,聚宽底层可能会在时间推进后销毁旧的内存引用,导致你访问报错。
💡 代码示例对比
❌ 错误的做法(跨时间点缓存 data):
def initialize(context):
g.cached_data = []
g.stock = '000001.XSHE'
def handle_data(context, data):
# 错误!把 data 对象存起来了
g.cached_data.append(data)
if len(g.cached_data) > 1:
# 试图用昨天存的 data 和今天的 data 做对比,这是极其危险且错误的!
# 且不说底层引用可能失效,如果发生除权除息,旧 data 的价格没有动态复权,会导致逻辑完全错误。
if g.cached_data[-1][g.stock].close > g.cached_data[-2][g.stock].close:
order(g.stock, 100)
✅ 正确的做法(使用 history 或 attribute_history 获取历史序列):
def initialize(context):
g.stock = '000001.XSHE'
def handle_data(context, data):
# 正确用法 1:只获取当前时间点的前一单位时间数据
current_close = data[g.stock].close
# 正确用法 2:如果需要对比过去几天的数据,请使用 history 或 attribute_history API
# 获取过去 2 天的收盘价(已处理好复权)
hist = attribute_history(g.stock, 2, '1d', ['close'])
if hist['close'][-1] > hist['close'][-2]:
order(g.stock, 100)
总结:把 data 当作一个“阅后即焚”的查询工具,只在当前的 handle_data 作用域内使用它。如果需要历史时间序列,请使用 history 或 attribute_history 等专门的数据获取 API。