🚀 AI 一键生成 joinquant 策略代码

JoinQuant 模拟盘重启后对象丢失怎么办?process_initialize 详解

joinquant | 发布时间: 2025/12/30
以下内容由 EasyQuant 生成。

问题描述

为什么模拟盘重启后数据库查询对象失效了?如何解决?

解决方案

模拟盘重启后数据库查询对象失效的原因及解决方法

原因分析

在聚宽(JoinQuant)的模拟交易中,系统会在每天收盘后保存策略的状态(即“休眠”),并在第二天开盘前恢复状态(即“重启”)。这个过程依赖于 Python 的 pickle 模块对全局对象 gcontext 进行序列化(保存)反序列化(恢复)

数据库查询对象(通过 query() 函数生成的对象)包含底层的数据库连接信息或特定的内存状态,这些是**无法被序列化(Pickle)**的。

具体流程导致的问题如下:

  1. 序列化失败或忽略:当模拟盘保存状态时,系统无法保存 g 中的 query 对象。
  2. 初始化不执行:模拟盘重启时,不会再次执行 initialize(context) 函数(该函数在策略生命周期中只执行一次)。
  3. 对象丢失:因此,当第二天策略恢复运行时,g 中原本存储的 query 对象已经不存在或损坏,导致再次调用时报错(通常是 AttributeError)。

解决方案

要解决这个问题,需要利用 process_initialize(context) 函数和特殊的变量命名规则。

1. 使用 process_initialize 函数

process_initialize(context) 是一个专门用于进程启动初始化的回调函数。无论是策略第一次启动,还是每天模拟盘重启,该函数都会被执行一次。 因此,它是重建数据库连接或查询对象的最佳位置。

2. 使用双下划线 __ 开头的变量名

g 对象中,以双下划线开头(例如 g.__query)的变量不会被系统尝试序列化保存。这告诉系统:“不要把这个变量存到硬盘上,我会在每次重启时重新创建它。”

代码示例

请参考以下代码修改您的策略:

# -*- coding: utf-8 -*-
from jqdata import *

def initialize(context):
    # 这里只放可以被序列化的普通变量,如股票代码、计数器等
    g.security = '000001.XSHE'
    # 注意:不要在这里定义 query 对象,因为 initialize 重启后不会运行

def process_initialize(context):
    '''
    该函数会在每次模拟盘/回测进程重启时执行。
    用于初始化不能持久化保存的对象(如 query 对象、文件句柄等)。
    '''
    # 1. 重新创建 query 对象
    # 2. 变量名必须以两个下划线开头 (__),避免系统尝试对其进行序列化
    g.__q = query(valuation).filter(valuation.code == g.security)
    
    log.info("数据库查询对象已在 process_initialize 中重新初始化")

def handle_data(context, data):
    # 使用 g.__q 进行查询
    # 注意:这里使用的是 process_initialize 中定义的 g.__q
    df = get_fundamentals(g.__q)
    
    if df is not None and len(df) > 0:
        log.info("查询成功,市值: %s" % df['market_cap'][0])

Q&A

Q: initializeprocess_initialize 有什么区别?
A: initialize 在整个策略的生命周期(无论回测还是模拟交易)中只执行一次,用于设置初始参数。process_initialize 在每次底层进程启动时都会执行。在模拟交易中,进程每天都会重启,所以 process_initialize 每天都会运行,适合用于重建连接或重置非持久化对象。

Q: 除了 query 对象,还有哪些对象不能保存在 g 中?
A: 凡是涉及到 I/O 操作或底层连接的对象都不能被序列化保存,包括:

  • 打开的文件句柄(open(...) 返回的对象)
  • 网络连接对象(如 requests 的 session)
  • 数据库连接对象
  • 线程或进程锁对象

Q: 如果我不加双下划线 __ 会怎么样?
A: 如果在 process_initialize 中定义了 g.q(没加下划线),系统在收盘保存状态时会尝试序列化它。由于 query 对象不支持序列化,可能会导致保存状态失败,或者在第二天恢复时该变量损坏,引发不可预知的错误。加上 __ 是显式告诉系统忽略该变量的保存。