🚀 AI 一键生成 joinquant 策略代码

JoinQuant 模拟交易:initialize, process_initialize, after_code_changed 有何区别?

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

问题描述

在模拟交易中,initialize, process_initialize 和 after_code_changed 这几个函数有什么区别和用法?

解决方案

在聚宽(JoinQuant)的模拟交易环境中,策略进程通常会在每天收盘后暂停(序列化保存状态),并在第二天开盘前重启(反序列化恢复状态)。

由于这种机制,initializeprocess_initializeafter_code_changed执行时机主要用途上有明显的区别。

以下是详细的对比和用法说明:

1. 函数详解

initialize(context)

  • 定义:初始化函数。
  • 执行时机
    • 回测:在回测开始时执行一次。
    • 模拟交易:仅在创建模拟交易的第一次启动时执行一次。后续每天重启时不会再执行。
  • 用途
    • 设置基准(set_benchmark)、手续费(set_order_cost)、滑点(set_slippage)。
    • 开启真实价格模式(set_option('use_real_price', True))。
    • 定义全局变量 g 的初始值(这些变量会被系统自动保存到磁盘,第二天自动恢复)。
    • 设置定时运行函数(run_daily 等)。

process_initialize(context)

  • 定义:进程启动初始化函数。
  • 执行时机
    • 回测:在 initialize 之后执行一次。
    • 模拟交易每次进程启动时都会执行(包括创建时的第一次启动,以及每天早上的重启)。
  • 用途
    • 重建无法被序列化(保存)的对象
    • 聚宽系统在保存模拟交易状态时,使用 pickle 序列化 g 对象。但是,某些对象(如 query 查询对象、打开的文件句柄、数据库连接、网络连接等)是无法被序列化的。
    • 如果将 query 对象直接赋值给 g.q,第二天重启时会报错或丢失。因此,必须在 process_initialize 中重新创建这些对象。
    • 注意:为了避免系统尝试保存这些临时对象,通常建议变量名以双下划线开头(如 g.__q),系统会自动忽略以 __ 开头的变量的序列化。

after_code_changed(context)

  • 定义:代码变更回调函数。
  • 执行时机
    • 模拟交易:当模拟交易重启(例如第二天早上或手动重启),且系统检测到策略代码与上一次运行时不同时执行。
  • 用途
    • 更新全局变量的值
    • 在模拟交易中,g 变量的状态是从磁盘恢复的(即“昨天”的状态)。如果你修改了代码中的 initialize(例如把 g.stock = '000001.XSHE' 改为 '000002.XSHE'),由于 initialize 不会重跑,g.stock 恢复后仍然是旧值 '000001.XSHE'
    • 必须在 after_code_changed 中显式重新赋值,才能让新的代码逻辑生效。

2. 对比总结表

特性 initialize process_initialize after_code_changed
执行频率 仅 1 次 (生命周期开始时) 每天/每次重启都执行 仅在代码修改后的重启时执行
执行顺序 最先执行 initialize 或恢复状态后执行 在恢复状态后,process_initialize 之前
主要用途 基础设置、定义可保存的全局变量 重建不可保存的对象 (如 query) 更新已保存的全局变量
典型场景 设置基准、手续费、初始股票池 初始化 g.__query 对象 修改持仓逻辑、更换标的池

3. 模拟交易启动流程图解

当模拟交易启动(或重启)时,系统的处理逻辑如下:

  1. 加载代码:编译并运行策略代码。
  2. 恢复状态:从磁盘读取并恢复 gcontext 对象(如果是第一次运行,则跳过此步,直接运行 initialize)。
  3. 检查代码变更
    • 如果代码发生了变化,运行 after_code_changed(context)
  4. 进程初始化:运行 process_initialize(context)
  5. 开始交易:进入 before_trading_start -> handle_data 等日常循环。

4. 代码示例

以下代码展示了三个函数的典型配合方式:

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

def initialize(context):
    # 1. 基础设置(只运行一次)
    set_benchmark('000300.XSHG')
    set_option('use_real_price', True)
    
    # 2. 定义可持久化的全局变量
    g.security = '000001.XSHE'  # 平安银行
    g.days = 0
    
    log.info("initialize 运行:初始化完成")

def process_initialize(context):
    # 3. 重建不可持久化的对象(每次重启都会运行)
    # query对象不能被保存,必须每次重启时重新生成
    # 使用双下划线开头,告诉系统不要尝试保存这个变量
    g.__q = query(valuation).filter(valuation.code == g.security)
    
    log.info("process_initialize 运行:Query对象已重建")

def after_code_changed(context):
    # 4. 处理代码修改(仅在修改代码后重启时运行)
    # 假设我们将代码中的 g.security 改为了 '000002.XSHE'
    # 系统恢复的 g.security 还是 '000001.XSHE',必须在这里强制更新
    g.security = '000002.XSHE' 
    
    # 如果有新的定时任务需要添加,也可以在这里处理
    # unschedule_all()
    # run_daily(func, '10:00')
    
    log.info("after_code_changed 运行:全局变量已更新为万科A")

def handle_data(context, data):
    # 使用在 process_initialize 中初始化的 query 对象
    df = get_fundamentals(g.__q)
    log.info("当前操作标的: %s" % g.security)

5. 常见误区

  1. 误区:修改了 initialize 中的变量,重启模拟盘就会生效。
    • 事实:不会生效。因为 initialize 不会重跑,系统会使用昨天保存的 g 变量覆盖你的新代码逻辑。必须使用 after_code_changed 更新。
  2. 误区:在 initialize 中定义 g.q = query(...)
    • 事实:模拟盘第二天会报错。因为 query 对象包含数据库连接引用,无法被保存到磁盘。必须在 process_initialize 中定义,并建议命名为 g.__q

Q&A

Q: 如果我在模拟交易中修改了 run_daily 的定时时间,需要怎么做?
A: 你需要在 after_code_changed 中先调用 unschedule_all() 取消旧的定时任务,然后重新调用 run_daily 注册新的任务。因为定时任务的注册信息也是保存在 context 中被持久化的。

Q: process_initialize 在回测中会执行吗?
A: 会执行。在回测中,它会在 initialize 执行完毕后紧接着执行一次。虽然回测通常不涉及“重启”,但为了保证代码在回测和模拟中的兼容性,建议始终使用它来初始化 query 等对象。

Q: 为什么建议 process_initialize 中使用的变量名以 __ 开头?
A: 聚宽系统在收盘后会将 g 对象序列化保存到硬盘。以 __ 开头的变量会被系统视为“临时变量”而忽略保存。这既避免了保存不可序列化对象(如数据库连接)导致的报错,也减少了保存文件的大小,提高了系统稳定性。