🚀 AI 一键生成 ptrade 策略代码

PTrade 策略核心:全局对象 g 的生命周期、作用域及持久化机制

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

问题描述

策略中的全局变量 g 是怎么保存和传递的?

解决方案

在 PTrade 量化交易平台中,全局对象 g 是策略编写的核心组件之一。它主要用于在不同的函数(如 initializehandle_databefore_trading_start 等)之间传递数据,并且具备**持久化(Persistence)**功能,以确保策略在隔日运行或服务器重启后能够保持状态。

以下是关于 g 变量保存和传递机制的详细说明:

1. 内存中的传递(运行时)

在策略运行期间,g 是一个全局对象实例。

  • 定义: 通常在 initialize(context) 函数中初始化 g 的属性,例如 g.security = '600570.SS'
  • 传递: 一旦在 initialize 中定义,该属性可以在策略的任何其他函数(如 handle_databefore_trading_start)中直接读取或修改。它在内存中是共享的。

2. 持久化保存机制(跨日/重启)

为了应对服务器重启、策略中断恢复以及实盘中的隔日交易,PTrade 使用 Python 的 pickle 模块g 对象进行序列化保存。

保存时机

框架会自动在以下事件执行完毕后,触发持久化操作,将 g 中的内容保存到磁盘:

  1. before_trading_start 执行后
  2. handle_data 执行后
  3. after_trading_end 执行后

恢复逻辑(关键)

当策略重新启动(例如第二天开盘,或者盘中服务器重启)时,恢复流程如下:

  1. 执行 initialize 函数: 框架首先会运行 initialize 函数,执行其中所有的代码。
  2. 加载持久化数据: 紧接着,框架会读取磁盘上保存的 pickle 文件。
  3. 覆盖变量: 如果持久化文件中包含 g 的变量,系统会用持久化文件中的值覆盖 initialize 中初始化的值

示例说明:

def initialize(context):
    # 假设这是第一次运行,g.count 被设为 0
    # 如果是重启恢复,这里先被设为 0,随后立即被持久化的值(比如 5)覆盖
    g.count = 0 

def handle_data(context, data):
    g.count += 1
    log.info(g.count)

如果策略运行了一段时间,g.count 变成了 5。此时策略停止并重启:

  1. initialize 运行,g.count 暂时变为 0。
  2. 系统检测到有持久化数据,将 g.count 恢复为 5。
  3. 下一次 handle_data 运行时,g.count 从 6 开始。

3. 哪些变量会被保存?哪些不会?

并非 g 中的所有变量都会被持久化,必须遵循以下规则:

会被保存的变量

  • 可序列化对象: Python 的基本数据类型(int, float, str, list, dict, set, tuple)以及大部分未涉及系统资源的自定义类实例。

不会被保存的变量

  1. 私有变量(以 __ 开头):
    如果在 g 中定义变量名以双下划线开头(例如 g.__temp_data),该变量不会被保存到磁盘。
    • 用途: 适用于不需要跨日保存的临时数据,或者每次重启都需要重新初始化的对象。
  2. 不可序列化对象(IO 相关):
    涉及系统资源的对象无法被 pickle 序列化,因此不能直接保存在普通的 g 变量中(除非用 __ 开头并在每次启动时重新创建)。
    • 例如: 打开的文件句柄、数据库连接对象、网络套接字、线程锁等。

4. 代码示例

以下代码展示了 g 变量的持久化特性以及如何使用私有变量避免持久化:

def initialize(context):
    # 1. 普通全局变量:会被持久化
    # 重启后,如果磁盘有旧值,这里的赋值会被覆盖
    g.trade_days_count = 0
    
    # 2. 私有全局变量:不会被持久化
    # 每次策略启动/重启,这个值都会被重置为 None,不会被磁盘数据覆盖
    g.__db_connection = None
    
    g.security = '600570.SS'
    set_universe(g.security)

def before_trading_start(context, data):
    # 每次开盘前增加计数
    g.trade_days_count += 1
    log.info("策略已运行交易日数量: %s" % g.trade_days_count)
    
    # 重新初始化不需要保存的对象
    if g.__db_connection is None:
        g.__db_connection = "模拟数据库连接对象" # 实际场景中可能是 connect()
        log.info("数据库连接已重新建立")

def handle_data(context, data):
    # 使用全局变量
    pass

总结

  1. 保存方式: 使用 pickle 序列化到磁盘。
  2. 传递方式: 内存共享 + 启动时覆盖恢复。
  3. 控制权: 使用 g.variable 进行持久化存储,使用 g.__variable 存储临时的、不需要跨日保存的数据。

Q&A

Q1: 如果我在 initialize 里修改了代码,比如把 g.x = 1 改成了 g.x = 100,重启策略后 g.x 是多少?
A1: 如果是实盘或模拟盘的中途重启(非回测),且之前已经运行过并保存了 g.x(假设旧值为 5),那么重启后 g.x 依然是 5。因为持久化的数据会覆盖代码中的初始化值。如果需要强制重置,可以改变量名或者在 before_trading_start 中强制赋值。

Q2: 为什么我的策略报错 TypeError: can't pickle ...
A2: 这通常是因为你把一个不可序列化的对象(如打开的文件、数据库连接、线程对象)赋值给了 g 的普通变量。解决方法是将该变量名改为以 __ 开头(如 g.__file_handle),并在 initializebefore_trading_start 中每次重新创建它。

Q3: 回测模式下 g 变量也会持久化吗?
A3: 在回测模式下,g 变量在内存中传递,模拟了跨日的过程,但不会像实盘那样生成物理文件用于重启恢复(因为回测是一次性跑完的)。但在逻辑上,它依然保持了跨日的状态延续。