🚀 AI 一键生成 ptrade 策略代码

PTrade 策略重启后变量丢失?学习高级持久化技巧与私有变量的应用

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

问题描述

我的策略里定义了一些类实例,每次重启交易,这些对象的状态就丢失了,怎么才能让它们在重启后保持不变?

解决方案

在PTrade中,为了防止策略重启(如服务器维护、异常中断后恢复)导致自定义类实例的状态丢失,需要利用PTrade提供的持久化机制

PTrade框架会自动对全局对象 g 中的变量进行序列化(Pickle)保存。只要你的类实例是可序列化的(即不包含文件句柄、数据库连接等IO对象),将其绑定到 g 对象上即可实现状态保持。

以下为您提供两种实现方案:

方案一:利用框架自动持久化(推荐)

这是最简单的方法。PTrade会在 handle_dataafter_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("盘后状态已保存")

关键注意事项

  1. 变量命名规则
    • 如果使用方案一(自动持久化),绑定在 g 上的变量名不能以双下划线 __ 开头(例如 g.__my_obj),这类变量被视为私有变量,PTrade 不会对其进行持久化保存。
  2. IO 对象不可序列化
    • 你的类实例中不能包含无法被序列化的对象,例如:打开的文件句柄(open()返回的对象)、数据库连接、线程锁等。如果类中必须包含这些,需要在 __getstate____setstate__ 方法中特殊处理,或者在 initialize 中重新建立连接。
  3. 类定义的稳定性
    • 当策略重启恢复数据时,Python 需要能够找到该类的定义。因此,类必须定义在策略代码的顶层,不要定义在函数内部。
  4. 恢复逻辑
    • 在方案一中,PTrade 的机制是:先执行 initialize -> 再加载持久化数据覆盖 g 中的同名变量。因此,你在 initialize 中写 g.my_state = MyStrategyState() 是安全的,系统会在初始化后用保存的旧状态覆盖这个新实例。

Q&A

Q: 为什么我的类实例在重启后还是重置了?
A: 请检查以下几点:

  1. 变量名是否以 __ 开头(如 g.__data)?如果是,请改为普通名称(如 g.data)。
  2. 类中是否包含了文件对象或网络连接对象?这些会导致序列化失败,从而导致保存失败。
  3. 是否在 initialize 之后的地方(如 before_trading_start)无条件地重新赋值了该变量?恢复操作发生在 initialize 之后,如果在其他地方强行 g.my_state = NewInstance(),会覆盖掉恢复的数据。

Q: 自动持久化会影响策略运行速度吗?
A: PTrade 的自动持久化是在 handle_data 等周期结束后进行的。如果你的类实例包含的数据量非常巨大(例如存储了数百万条Tick数据),序列化和写入磁盘的过程可能会消耗时间。对于普通的状态标记、参数记录,影响可以忽略不计。如果数据量极大,建议使用方案二,仅在收盘后保存。

Q: 回测模式下支持持久化吗?
A: 回测模式主要用于验证逻辑,通常是一次性运行,持久化机制主要设计用于**交易(实盘/模拟盘)**场景,用于应对服务器重启或隔日运行。但在回测中,g 对象的传递在单次运行周期内是有效的。