问题描述
解决方案
在PTrade中,order_target(调仓到指定数量)接口在实盘交易中可能会因为柜台回报延迟(持仓未及时刷新)或存在未成交的在途订单(Open Orders)而导致重复下单。
为了避免这种情况,最稳健的方法是手动计算“有效持仓”。
核心逻辑
有效持仓 = 当前已持仓位 + 当前未成交的挂单数量
下单数量 = 目标持仓数量 - 有效持仓
代码实现
以下是一个完整的策略示例,展示了如何安全地将仓位调整到1000股:
def initialize(context):
# 设置我们要操作的股票,例如恒生电子
g.security = '600570.SS'
# 设置目标持仓数量
g.target_amount = 1000
set_universe(g.security)
def handle_data(context, data):
# 获取当前账户对该股票的持仓信息
position = get_position(g.security)
# 当前实际持仓数量
current_amount = position.amount
# 获取该股票当前所有未成交的订单(挂单)
open_orders = get_open_orders(g.security)
# 计算在途数量(未成交部分的净额)
# PTrade中,买单amount为正,卖单amount为负
# 未成交数量 = 订单总数量 - 已成交数量
pending_amount = 0
if open_orders:
for _order in open_orders:
# _order.amount 是原始委托数量(买正卖负)
# _order.filled 是已成交数量(买正卖负)
# 两者相减即为剩余未成交的数量
pending_amount += (_order.amount - _order.filled)
# 计算有效持仓(当前持仓 + 在途未成交)
effective_amount = current_amount + pending_amount
# 计算需要调整的差额
diff = g.target_amount - effective_amount
# 只有当差额大于等于100股(一手)时才进行下单,避免碎股报错
# 注意:如果是卖出且需清仓,可能需要特殊处理,但这里假设是调整到1000股
if abs(diff) >= 100:
log.info("执行调仓: 当前持仓 %s, 在途未成交 %s, 目标 %s, 需下单 %s" % (
current_amount, pending_amount, g.target_amount, diff))
# 下单,diff为正则是买入,为负则是卖出
order(g.security, diff)
elif diff == 0:
# 刚好满足,无需操作
pass
else:
log.info("差额 %s 不足一手,忽略下单" % diff)
代码详解
get_position(g.security): 获取当前已经确认的持仓。在实盘中,这个数据可能比实际成交慢几秒钟。get_open_orders(g.security): 获取当前发出去但还没有完全成交的订单。这是防止重复下单的关键。pending_amount计算:- 如果之前发了一个“买入500股”的指令,但还没成交,
get_position里是看不到这500股的。 - 如果不加
pending_amount,策略会以为还缺500股,再次下单,导致最终买入1000股(重复购买)。 - 通过累加
_order.amount - _order.filled,我们将这部分“在途”的股票也算作已持有。
- 如果之前发了一个“买入500股”的指令,但还没成交,
order(g.security, diff): 使用计算出的差额进行下单。如果diff是正数,order会买入;如果是负数,order会卖出。
常见问题 (Q&A)
Q: 为什么不直接使用 order_target 接口?
A: PTrade文档明确指出,在交易场景中,由于柜台持仓同步有时滞(一般6秒左右),如果策略运行频率较高(如每分钟或Tick级),order_target 可能会在持仓未更新时连续触发,导致重复下单。此外,order_target 内部逻辑可能不会自动扣除“在途未成交”的订单。
Q: 这段代码能处理卖出吗?
A: 可以。如果你的当前持仓是 1500 股,目标是 1000 股,diff 计算结果为 -500。调用 order(g.security, -500) 会执行卖出操作。
Q: 如果我有未成交的订单,策略会撤单吗?
A: 上述代码不会撤单,它会把未成交的订单视为“即将成交”并计算在内,从而减少新下单的数量。如果你希望策略更激进(例如价格变化了,想撤销旧单发新单),你需要在下单前先遍历 open_orders 执行 cancel_order。
Q: handle_data 是多久运行一次?
A: 取决于你在回测或实盘设置的频率。如果是日线,每天一次;如果是分钟线,每分钟一次。上述逻辑在分钟级别下运行是安全的。