问题描述
解决方案
在 ptrade 平台中,将回测完成的策略(无论是低代码生成还是手写代码)应用到模拟盘或实盘,并不是简单的“一键运行”。实盘环境面临着服务器重启、网络延迟、数据缺失等复杂情况。以下是将策略从回测迁移到模拟盘/实盘的核心步骤和注意事项:
1. 变量持久化处理(至关重要)
实盘和模拟盘可能会遇到券商环境重启或升级。如果策略依赖历史状态(如持仓天数、分批建仓状态),必须进行持久化处理。
- 框架自动持久化:ptrade 框架会在
before_trading_start、handle_data、after_trading_end后自动使用pickle保存全局变量g中的内容。 - 重启恢复机制:环境重启后,框架会先执行
initialize,然后用持久化的信息覆盖g中的同名变量。 - 私有变量与不可序列化对象:如果
g中有不能被序列化的对象(如打开的文件、实例化的类),必须在initialize中以__开头命名(如g.__test_class),这样框架持久化时会跳过它。
2. 配置实盘防重复执行参数
在实盘/模拟盘中,服务器重启可能导致函数被重复调用,从而引发重复下单。必须在 initialize 中使用 set_parameters 进行防护:
def initialize(context):
# 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")
3. 增加异常处理机制
实盘中可能会遇到行情数据缺失或接口超时,必须使用 try...except 保护核心逻辑,防止策略因报错而直接终止。
try:
# 尝试获取数据或下单
current_price = data[security]['close']
except Exception as e:
log.error("获取价格异常: %s" % e)
4. 注意委托价格的精度
实盘对价格精度要求严格,否则会导致废单:
- 股票:小数点后 2 位
- 可转债、ETF、LOF:小数点后 3 位
- 股指期货:小数点后 1 位
在调用order()或order_target()传入limit_price时,务必使用round(price, 2)等方法处理精度。
5. 谨慎使用 order_target 系列函数
在模拟盘/实盘中,柜台返回持仓数据通常有延迟(如 6 秒左右)。如果在延迟期间连续调用 order_target,由于持仓未更新,会导致重复下单。建议:
- 使用
order()按绝对数量下单。 - 或者在策略内部自行维护一个字典记录已委托数量,避免依赖柜台的实时持仓。
6. 启用主推事件(进阶)
为了更快地获取订单和成交状态,可以在策略中加入 on_order_response 和 on_trade_response 函数,这比轮询 get_orders() 效率更高。
模拟盘部署模板示例
import pickle
from collections import defaultdict
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
# 1. 实盘防重启重复执行配置
set_parameters(not_restart_trade="1", server_restart_not_do_before="1")
# 2. 自定义持久化(可选,框架默认也会保存g变量)
try:
with open(get_research_path() + 'my_status.pkl', 'rb') as f:
g.my_status = pickle.load(f)
except:
g.my_status = defaultdict(int)
def before_trading_start(context, data):
# 盘前逻辑
pass
def handle_data(context, data):
try:
current_price = data[g.security]['close']
cash = context.portfolio.cash
# 简单的买入逻辑
if current_price > 30 and g.my_status[g.security] == 0:
# 注意价格精度
order(g.security, 100, limit_price=round(current_price * 1.01, 2))
g.my_status[g.security] = 1
except Exception as e:
log.error("交易逻辑发生异常: %s" % e)
def after_trading_end(context, data):
# 盘后保存自定义状态
with open(get_research_path() + 'my_status.pkl', 'wb') as f:
pickle.dump(g.my_status, f, -1)
总结:低代码生成的回测代码通常只包含最基础的 initialize 和 handle_data。要上模拟盘,你需要将生成的代码复制到交易模块,并根据上述指南加入防重启配置、异常处理和精度控制,确保策略在真实环境下的健壮性。