问题描述
解决方案
在聚宽(JoinQuant)的模拟交易中,策略进程会在每天收盘后暂停(休眠),并在第二天开盘前重启。为了保证策略状态的连续性,系统会将全局对象 g 进行序列化(Pickle)保存到磁盘,并在重启时恢复。
针对您遇到的两个问题,以下是详细的解决方案和原理说明:
一、 模拟交易重启后变量丢失怎么办?
如果第二天重启后发现变量丢失,通常是因为变量没有正确地存储在全局对象 g 中,或者该变量不符合持久化保存的规则。
1. 必须使用 g 对象存储
只有挂载在 g 对象下的变量(例如 g.my_variable)才会被系统自动保存。定义在 initialize 函数之外的全局变量,或者仅仅作为局部变量定义的,重启后都会重置或丢失。
2. 避开命名陷阱:不要以 __ 开头
聚宽系统规定,g 对象中以双下划线 __ 开头的变量(例如 g.__temp)不会被序列化保存。
- 错误做法:
g.__count = 0(重启后会丢失) - 正确做法:
g.count = 0(重启后会保留)
3. 确保数据类型可序列化
只有可被 pickle 序列化的对象才能被保存。
- 支持:基础类型(int, float, str, list, dict)、pandas DataFrame 等。
- 不支持:数据库连接对象(如
query()返回的对象)、文件句柄、网络连接对象等。
解决方案:使用 process_initialize
对于不能保存的对象(如 query 对象)或以 __ 开头的临时变量,应在 process_initialize(context) 函数中进行初始化。该函数在每次进程重启时(包括每天早上)都会执行。
def initialize(context):
# 1. 需要持久保存的变量,直接放在 g 下,不要用 __ 开头
g.security = '000001.XSHE'
g.counter = 0
def process_initialize(context):
# 2. 不能被保存的对象(如 query),在每次重启时重新创建
# 3. 不需要保存的临时变量,可以使用 __ 开头
g.__q = query(valuation).filter(valuation.code == g.security)
4. 注意内存限制
序列化后的状态文件大小不能超过 30MB。如果 g 中存储了过大的 DataFrame 或列表,可能导致保存失败,从而造成数据丢失。请定期清理 g 中不必要的历史数据。
二、 更新代码后如何修改全局变量?
在模拟交易运行过程中,如果您修改了代码并点击了“替换代码”或“编译运行”,initialize 函数不会再次执行。因此,直接修改 initialize 中的 g.security = '新股票' 是无效的。
解决方案:使用 after_code_changed
系统提供了一个专用回调函数 after_code_changed(context)。当模拟交易检测到代码更新并重新加载后,会立即执行该函数。您应该在这里修改全局变量。
代码示例
假设您想把操作标的从平安银行换成万科A:
def initialize(context):
# 这是旧代码中的设置,模拟盘重启不会再运行这里
g.security = '000001.XSHE'
# 在代码中添加或修改此函数
def after_code_changed(context):
# 修改全局变量
g.security = '000002.XSHE'
# 如果有其他配置需要更新,也可以写在这里
log.info("代码已更新,操作标的变更为: " + g.security)
# 可选:修改完后,如果不需要每次更新都重置,下次更新代码时可以注释掉这行
总结
- 防丢失:变量挂在
g下,不要用__开头,确保数据可序列化且不超过 30MB。 - 非持久化数据:在
process_initialize中初始化query对象或临时变量。 - 热更新变量:在
after_code_changed中修改g的值,不要依赖initialize。
Q&A
Q: 为什么我的 query() 对象在第二天报错说不存在?
A: query() 返回的是数据库连接相关的对象,不支持序列化保存。模拟盘重启后,g 中的普通变量恢复了,但 query 对象丢失。请在 process_initialize(context) 中重新赋值 g.__q = query(...)。
Q: process_initialize 和 initialize 有什么区别?
A: initialize 只在策略创建的第一天执行一次;process_initialize 在策略启动以及每天早上模拟盘进程重启时都会执行。
Q: 我在 after_code_changed 里修改了变量,下次重启还需要保留这个函数吗?
A: after_code_changed 仅在代码发生变化并重新加载的那一刻运行。如果变量修改是一次性的(例如切换股票),修改成功后,下次更新代码时可以删除或注释掉函数内的逻辑,否则每次更新代码都会重置该变量。