问题描述
解决方案
在PTrade中,为了防止策略重启(如服务器维护、异常中断后恢复)导致自定义类实例的状态丢失,需要利用PTrade提供的持久化机制。
PTrade框架会自动对全局对象 g 中的变量进行序列化(Pickle)保存。只要你的类实例是可序列化的(即不包含文件句柄、数据库连接等IO对象),将其绑定到 g 对象上即可实现状态保持。
以下为您提供两种实现方案:
方案一:利用框架自动持久化(推荐)
这是最简单的方法。PTrade会在 handle_data、after_trading_end 等事件结束后,自动保存 g 对象中不以双下划线 __ 开头的变量。
当策略重启时,系统会先执行 initialize,然后读取持久化文件。如果持久化文件中存在同名变量,会覆盖 initialize 中初始化的值,从而恢复之前的状态。
# 1. 定义你的类 (必须定义在全局作用域,不能在函数内部定义)
class MyStrategyState(object):
def __init__(self):
self.trade_count = 0
self.target_positions = {}
self.dynamic_param = 1.0
def update_count(self):
self.trade_count += 1
def set_target(self, code, amount):
self.target_positions[code] = amount
def initialize(context):
# 2. 将类实例绑定到全局对象 g
# 注意:不要使用以 '__' 开头的变量名,否则不会被保存
g.my_state = MyStrategyState()
g.security = '600570.SS'
set_universe(g.security)
log.info("策略初始化完成")
def handle_data(context, data):
# 3. 在盘中直接使用 g.my_state,其状态会被自动保存
# 示例逻辑:每次运行增加计数
g.my_state.update_count()
# 打印当前状态,重启后你会发现这个数字是连续的,而不是重置为0
log.info("当前交易计数: %s" % g.my_state.trade_count)
# 示例:修改类中的数据
if g.my_state.trade_count % 5 == 0:
g.my_state.dynamic_param += 0.1
log.info("参数已更新为: %s" % g.my_state.dynamic_param)
def after_trading_end(context, data):
log.info("盘后状态检查: %s" % g.my_state.target_positions)
方案二:手动控制持久化(使用 pickle)
如果你需要更精细的控制(例如只在特定时间保存,或者数据结构非常复杂),可以使用 pickle 模块手动将对象保存到研究目录的文件中。
import pickle
# 定义类
class MyStrategyState(object):
def __init__(self):
self.history_records = []
self.vip_stock = '600570.SS'
def add_record(self, record):
self.history_records.append(record)
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
# 获取研究目录路径
g.file_path = get_research_path() + 'my_strategy_state.pkl'
# 1. 尝试从文件恢复状态
try:
with open(g.file_path, 'rb') as f:
g.state_manager = pickle.load(f)
log.info("成功从文件恢复状态,历史记录长度: %s" % len(g.state_manager.history_records))
except:
# 如果文件不存在或读取失败,则创建新实例
log.info("未找到存档或读取失败,初始化新状态")
g.state_manager = MyStrategyState()
def handle_data(context, data):
# 业务逻辑
current_price = data[g.security]['close']
# 更新对象状态
g.state_manager.add_record({'time': str(context.blotter.current_dt), 'price': current_price})
# 2. 手动保存状态到文件
# 注意:频繁IO可能会影响回测速度,建议在必要时或盘后保存
# 这里为了演示放在 handle_data,实盘建议放在 after_trading_end 或特定触发条件下
try:
with open(g.file_path, 'wb') as f:
pickle.dump(g.state_manager, f, -1)
except Exception as e:
log.error("状态保存失败: %s" % e)
def after_trading_end(context, data):
# 盘后强制保存一次
with open(g.file_path, 'wb') as f:
pickle.dump(g.state_manager, f, -1)
log.info("盘后状态已保存")
关键注意事项
- 变量命名规则:
- 如果使用方案一(自动持久化),绑定在
g上的变量名不能以双下划线__开头(例如g.__my_obj),这类变量被视为私有变量,PTrade 不会对其进行持久化保存。
- 如果使用方案一(自动持久化),绑定在
- IO 对象不可序列化:
- 你的类实例中不能包含无法被序列化的对象,例如:打开的文件句柄(
open()返回的对象)、数据库连接、线程锁等。如果类中必须包含这些,需要在__getstate__和__setstate__方法中特殊处理,或者在initialize中重新建立连接。
- 你的类实例中不能包含无法被序列化的对象,例如:打开的文件句柄(
- 类定义的稳定性:
- 当策略重启恢复数据时,Python 需要能够找到该类的定义。因此,类必须定义在策略代码的顶层,不要定义在函数内部。
- 恢复逻辑:
- 在方案一中,PTrade 的机制是:先执行
initialize-> 再加载持久化数据覆盖g中的同名变量。因此,你在initialize中写g.my_state = MyStrategyState()是安全的,系统会在初始化后用保存的旧状态覆盖这个新实例。
- 在方案一中,PTrade 的机制是:先执行
Q&A
Q: 为什么我的类实例在重启后还是重置了?
A: 请检查以下几点:
- 变量名是否以
__开头(如g.__data)?如果是,请改为普通名称(如g.data)。 - 类中是否包含了文件对象或网络连接对象?这些会导致序列化失败,从而导致保存失败。
- 是否在
initialize之后的地方(如before_trading_start)无条件地重新赋值了该变量?恢复操作发生在initialize之后,如果在其他地方强行g.my_state = NewInstance(),会覆盖掉恢复的数据。
Q: 自动持久化会影响策略运行速度吗?
A: PTrade 的自动持久化是在 handle_data 等周期结束后进行的。如果你的类实例包含的数据量非常巨大(例如存储了数百万条Tick数据),序列化和写入磁盘的过程可能会消耗时间。对于普通的状态标记、参数记录,影响可以忽略不计。如果数据量极大,建议使用方案二,仅在收盘后保存。
Q: 回测模式下支持持久化吗?
A: 回测模式主要用于验证逻辑,通常是一次性运行,持久化机制主要设计用于**交易(实盘/模拟盘)**场景,用于应对服务器重启或隔日运行。但在回测中,g 对象的传递在单次运行周期内是有效的。