🚀 AI 一键生成 ptrade 策略代码

PTrade 策略上线必读:从回测到实盘的 8 个关键差异与调整技巧

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

问题描述

我有一个回测表现很好的策略,在上线实盘前需要注意哪些问题和修改哪些地方?

解决方案

将回测表现良好的策略迁移至PTrade实盘环境是一个关键步骤。回测环境是理想化的(无滑点、无延迟、数据完美),而实盘环境充满了不确定性。

以下是您在上线实盘前必须注意的问题及需要修改的代码清单。

一、 代码清理与环境区分

回测中常用的设置函数在实盘中是无效甚至有害的。您应当使用 is_trade() 函数来区分回测和实盘逻辑。

需要修改的地方:

  1. 移除或隔离回测专用APIset_commission(设置佣金)、set_slippage(设置滑点)、set_volume_ratio(成交比例)等函数仅在回测有效。实盘中,费率由券商决定,滑点由市场决定。
  2. 资金账号检查:建议在实盘中增加账号检查,防止误操作其他账号。
def initialize(context):
    # 设置股票池等通用逻辑
    g.security = '600570.SS'
    set_universe(g.security)
    
    # --- 仅在回测模式下执行的设置 ---
    if not is_trade():
        set_commission(commission_ratio=0.0003, min_commission=5.0)
        set_slippage(slippage=0.0)
        set_volume_ratio(volume_ratio=0.25)
    
    # --- 仅在实盘模式下执行的设置 ---
    if is_trade():
        # 可以在这里检查登录的账号是否正确
        user_name = get_user_name()
        if user_name != "您的资金账号":
            log.error("账号不匹配,策略停止运行")
            # 可以抛出异常终止策略
            # raise Exception("Account Mismatch")

二、 数据获取方式的调整

回测通常使用 data[security]['close']get_history 获取价格,但在实盘中,为了获取最新的毫秒级数据,建议使用 get_snapshot(行情快照)。

注意get_snapshot 不支持回测,必须用 is_trade() 隔开。

def handle_data(context, data):
    current_price = 0
    
    if is_trade():
        # 实盘:使用快照获取最新tick价格,更实时
        snapshot = get_snapshot(g.security)
        if snapshot:
            current_price = snapshot[g.security]['last_px']
    else:
        # 回测:使用bar数据
        current_price = data[g.security]['close']
        
    log.info("当前价格: %s" % current_price)
    # 后续交易逻辑...

三、 变量持久化(至关重要)

问题:实盘服务器可能会在盘中重启,或者策略因异常中断后重启。一旦重启,initialize 会重新运行,内存中的全局变量 g 会被重置,导致策略丢失之前的状态(如“持仓天数”、“最高价记录”等)。

解决方案:使用 pickle 模块将关键变量保存到磁盘。

import pickle
import os

def initialize(context):
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 定义需要持久化的变量名
    g.hold_days = 0
    
    # 实盘启动时尝试恢复数据
    if is_trade():
        recover_data()

def handle_data(context, data):
    # 策略逻辑...
    g.hold_days += 1
    
    # 每次关键数据变化后,保存数据
    if is_trade():
        save_data()

# --- 持久化辅助函数 ---
def save_data():
    # 获取研究目录路径
    path = get_research_path() + 'strategy_state.pkl'
    # 将需要保存的变量放入字典
    state = {'hold_days': g.hold_days}
    try:
        with open(path, 'wb') as f:
            pickle.dump(state, f)
    except Exception as e:
        log.error("数据保存失败: %s" % e)

def recover_data():
    path = get_research_path() + 'strategy_state.pkl'
    if not os.path.exists(path):
        return
    try:
        with open(path, 'rb') as f:
            state = pickle.load(f)
            # 恢复变量
            if 'hold_days' in state:
                g.hold_days = state['hold_days']
            log.info("数据恢复成功: %s" % state)
    except Exception as e:
        log.error("数据恢复失败: %s" % e)

四、 交易与风控细节

  1. 避免市价单(Market Order)

    • 回测中市价单通常按收盘价或开盘价成交。
    • 实盘中,流动性不足时市价单可能导致成交价严重偏离。
    • 建议:使用限价单 order(..., limit_price=...),或者使用带有保护限价的市价委托 order_market(科创板/创业板必须注意价格笼子)。
  2. 防止重复下单

    • 实盘中,网络延迟可能导致订单状态更新滞后。如果策略逻辑是“检测到无持仓则买入”,在订单已发出但未成交(在途)的几秒内,策略可能会重复发出买单。
    • 建议:检查 get_open_orders()(未完成订单)。
    def handle_data(context, data):
        # 检查是否有未完成的订单
        open_orders = get_open_orders(g.security)
        if open_orders:
            log.info("存在未完成订单,跳过本次信号")
            return
    
        # 获取当前持仓
        position = get_position(g.security)
        
        # 只有在无持仓且无未完成订单时才买入
        if position.amount == 0:
            order(g.security, 100)
    
  3. 涨跌停处理

    • 回测通常会自动处理涨跌停无法成交的情况。
    • 实盘中,如果在涨停价买入或跌停价卖出,订单会一直挂在交易所。
    • 建议:使用 check_limit(security) 检查涨跌停状态,避免无效报单。
  4. 异常捕获

    • 实盘中可能出现网络超时、接口报错等偶发问题。
    • 建议:使用 try...except 包裹核心逻辑,防止策略直接崩溃停止。
    def handle_data(context, data):
        try:
            # 核心策略逻辑
            pass
        except Exception as e:
            log.error("策略运行异常: %s" % e)
            # 可选:发送邮件通知
            # if is_trade():
            #     set_email_info(...)
    

五、 PTrade 特有参数设置

initialize 中,针对实盘环境,建议设置以下参数以避免服务器重启带来的副作用:

def initialize(context):
    # ... 其他初始化代码 ...
    
    if is_trade():
        # not_restart_trade="1": 交易时间段若服务器重启,不自动重新拉起本交易(防止重复执行初始化逻辑导致状态重置)
        # server_restart_not_do_before="1": 服务器重启导致重拉交易时,不重复执行before_trading_start
        set_parameters(not_restart_trade="1", server_restart_not_do_before="1")

六、 总结清单

在点击“开始交易”之前,请核对以下清单:

  1. [ ] API隔离:是否移除了 set_commission 等回测专用函数?
  2. [ ] 数据源:是否在实盘中使用了 get_snapshot 获取最新价格?
  3. [ ] 持久化:是否实现了 pickle 保存和恢复 g 变量,以应对服务器重启?
  4. [ ] 防抖动:是否处理了未完成订单(Open Orders),防止重复下单?
  5. [ ] 价格保护:下单价格是否合理?是否处理了涨跌停?
  6. [ ] 容错:是否加入了 try...except 异常捕获?
  7. [ ] 参数设置:是否配置了 not_restart_trade

Q&A: 常见问题解答

Q: 为什么我的实盘策略在9:30之前就报错了?
A: 实盘中 before_trading_start 可能会在9:00左右运行。如果在该函数中调用了需要开盘后才有的数据(如实时行情),可能会报错。建议在 before_trading_start 中只做静态数据初始化,或加上时间判断。

Q: 回测收益很高,实盘为什么不成交?
A: 常见原因包括:1. 价格设置不合理(如买单价格低于卖一价);2. 股票处于涨跌停状态;3. 委托数量不满足最小交易单位(如不足100股);4. 资金不足(回测可能未严格计算滑点和费率)。

Q: 如何在盘中查看策略的运行日志?
A: 在PTrade客户端的“交易”界面,找到运行中的策略,点击“日志”选项卡。建议在代码中多使用 log.info() 打印关键变量(如信号触发时的价格、持仓量),便于排查问题。

Q: get_snapshotdata 里的数据有什么区别?
A: data (在 handle_data 中) 通常是基于Bar(分钟线)的数据,或者是推送过来的切片数据。get_snapshot 是主动向柜台/行情服务器请求当前的最新快照。在实盘高频或对价格敏感的策略中,get_snapshot 的时效性通常更好。