问题描述
回测采用引擎瞬时成交,持仓更新即时;实盘依赖柜台数据同步,可能导致重复下单。
解决方案
ptrade 回测与实盘撮合机制差异解析
在 ptrade 量化交易平台中,回测环境和实盘(交易)环境的撮合机制存在本质区别:
- 回测环境:采用引擎瞬时撮合,订单一旦生成,持仓(
Position)和资金(Cash)会立即更新。 - 实盘环境:依赖券商柜台的数据同步。持仓信息的同步通常存在时滞(一般在6秒左右,部分配置甚至只在日终同步)。
为什么会导致重复下单?
当使用目标数量/市值下单接口(如 order_target 或 order_target_value)时,系统会读取当前的持仓量来计算需要买卖的差额。如果在实盘中,第一笔订单刚下达,柜台持仓尚未更新(处于6秒延迟期内),策略逻辑再次触发 order_target,系统会认为持仓依然为0(或旧值),从而再次下达相同的订单,造成重复下单。
解决方案与最佳实践
为了在实盘中安全地使用目标下单逻辑,建议采用以下几种解决方案:
方案一:使用全局变量 g 进行状态标记(适用于低频策略)
对于日线级别或低频触发的策略,可以使用全局变量 g 来记录当日是否已经下过单,避免在同一周期内反复触发。
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
# 初始化下单标记
g.order_placed = False
def before_trading_start(context, data):
# 每天盘前重置标记
g.order_placed = False
def handle_data(context, data):
if not g.order_placed:
# 仅在未下单时执行目标数量委托
order_target(g.security, 1000)
g.order_placed = True
log.info(f"已对 {g.security} 下达目标委托,锁定当日下单状态。")
方案二:手动计算在途订单(适用于高频/复杂策略)
如果策略需要频繁调整仓位,建议放弃使用 order_target,改为使用基础的 order 接口,并结合 get_position 和 get_open_orders 手动计算真实仓位(实际持仓 + 在途未成交订单)。
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)
def handle_data(context, data):
target_amount = 1000 # 目标持仓量
# 1. 获取当前已确认的持仓
current_position = get_position(g.security).amount
# 2. 获取当前在途(未成交)的订单数量
open_orders = get_open_orders(g.security)
pending_amount = sum([order.amount - order.filled for order in open_orders])
# 3. 计算真实的差额
# 真实差额 = 目标数量 - (当前持仓 + 在途数量)
diff_amount = target_amount - (current_position + pending_amount)
# 4. 只有当差额达到一定阈值(如100股)时才下单
if abs(diff_amount) >= 100:
order(g.security, diff_amount)
log.info(f"目标:{target_amount}, 持仓:{current_position}, 在途:{pending_amount}, 补单:{diff_amount}")
方案三:利用主推事件 on_order_response 维护本地账本
对于对速度要求极高的 tick 级策略,可以通过 on_order_response 和 on_trade_response 实时维护一个本地的虚拟持仓账本,完全绕过柜台查询的延迟。
总结与避坑指南
- 慎用 Target 接口:在实盘交易中,尽量避免在
handle_data(尤其是分钟或 tick 级别)中无条件地连续调用order_target和order_target_value。 - 了解券商配置:实盘前务必与券商确认柜台的持仓同步机制(是实时、延迟几秒,还是日终同步)。
- 撤单重报逻辑:如果第一笔委托未完全成交,再次调用
order_target前,必须先调用cancel_order撤销前序订单,否则引擎不会计算在途委托,必然导致重复下单。