问题描述
解决方案
在 PTrade 量化交易平台中,全局对象 g 是策略编写的核心组件之一。它主要用于在不同的函数(如 initialize、handle_data、before_trading_start 等)之间传递数据,并且具备**持久化(Persistence)**功能,以确保策略在隔日运行或服务器重启后能够保持状态。
以下是关于 g 变量保存和传递机制的详细说明:
1. 内存中的传递(运行时)
在策略运行期间,g 是一个全局对象实例。
- 定义: 通常在
initialize(context)函数中初始化g的属性,例如g.security = '600570.SS'。 - 传递: 一旦在
initialize中定义,该属性可以在策略的任何其他函数(如handle_data、before_trading_start)中直接读取或修改。它在内存中是共享的。
2. 持久化保存机制(跨日/重启)
为了应对服务器重启、策略中断恢复以及实盘中的隔日交易,PTrade 使用 Python 的 pickle 模块 对 g 对象进行序列化保存。
保存时机
框架会自动在以下事件执行完毕后,触发持久化操作,将 g 中的内容保存到磁盘:
before_trading_start执行后handle_data执行后after_trading_end执行后
恢复逻辑(关键)
当策略重新启动(例如第二天开盘,或者盘中服务器重启)时,恢复流程如下:
- 执行
initialize函数: 框架首先会运行initialize函数,执行其中所有的代码。 - 加载持久化数据: 紧接着,框架会读取磁盘上保存的
pickle文件。 - 覆盖变量: 如果持久化文件中包含
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。此时策略停止并重启:
initialize运行,g.count暂时变为 0。- 系统检测到有持久化数据,将
g.count恢复为 5。 - 下一次
handle_data运行时,g.count从 6 开始。
3. 哪些变量会被保存?哪些不会?
并非 g 中的所有变量都会被持久化,必须遵循以下规则:
会被保存的变量
- 可序列化对象: Python 的基本数据类型(int, float, str, list, dict, set, tuple)以及大部分未涉及系统资源的自定义类实例。
不会被保存的变量
- 私有变量(以
__开头):
如果在g中定义变量名以双下划线开头(例如g.__temp_data),该变量不会被保存到磁盘。- 用途: 适用于不需要跨日保存的临时数据,或者每次重启都需要重新初始化的对象。
- 不可序列化对象(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
总结
- 保存方式: 使用
pickle序列化到磁盘。 - 传递方式: 内存共享 + 启动时覆盖恢复。
- 控制权: 使用
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),并在 initialize 或 before_trading_start 中每次重新创建它。
Q3: 回测模式下 g 变量也会持久化吗?
A3: 在回测模式下,g 变量在内存中传递,模拟了跨日的过程,但不会像实盘那样生成物理文件用于重启恢复(因为回测是一次性跑完的)。但在逻辑上,它依然保持了跨日的状态延续。