3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在 JoinQuant 平台中,您遇到的问题是很多量化开发者常踩的坑。核心误区在于:order_target 返回非 None 的 Order 对象,仅仅代表“委托下单成功”,并不代表“订单已经成交”。
当遇到股票跌停、停牌或流动性不足时,您的卖出委托可能会一直处于挂单状态(open)或者最终被撤单(canceled)。此时股票依然在您的真实持仓中,但您已经在自定义的 rank_pool 中将其删除了,这就导致了后续遍历真实持仓时发生 KeyError。
要确认订单是否成交,您需要检查返回的 Order 对象的 status(订单状态)或 filled(已成交数量)属性。
JoinQuant 中的订单状态 (OrderStatus) 包括:
open: 订单未完成, 无任何成交filled: 订单未完成, 部分成交canceled: 订单完成, 已撤销rejected: 订单完成, 交易所已拒绝held: 订单完成, 全部成交在回测模式下,市价单通常会立即撮合。您可以直接检查返回的 Order 对象的 status。
def sell_to_zero(context, sy):
try:
# 下达目标持仓为0的卖出指令
r = order_target(sy, 0)
print(r)
if r is not None:
# 检查订单状态是否为全部成交 (held)
if str(r.status) == 'held':
log.info('卖出('+sy+')['+get_security_info(sy).display_name+']成功,全部成交')
send_msg('卖出:'+sy+','+get_security_info(sy).display_name)
# 只有真正成交了,才从自定义字典中删除
if sy in rank_pool:
del rank_pool[sy]
else:
log.warning('卖出('+sy+')委托成功,但未全部成交,当前状态: ' + str(r.status))
# 跌停等情况未成交,不应该从 rank_pool 中删除
else:
log.error('卖出('+sy+')委托失败(可能停牌或无持仓)')
except Exception as e:
log.error('卖出异常: ' + str(e))
强烈建议不要过度依赖自定义的字典(如 rank_pool)来维护持仓状态。在量化策略中,永远应该以 context.portfolio.positions(账户真实持仓)为唯一事实来源。
如果您必须使用 rank_pool 记录额外信息,在遍历时应该先检查该股票是否真的还在持仓中,或者使用安全的字典访问方法:
# 在后续遍历持仓时,安全地访问 rank_pool
def sell_it(context):
for sy in context.portfolio.positions:
# 检查是否在持仓中且可卖数量大于0
if context.portfolio.positions[sy].closeable_amount > 0:
# 安全获取,如果不在 rank_pool 中,给一个默认值避免 KeyError
profit = rank_pool.get(sy, [0])[0]
# ... 后续逻辑 ...
如果您使用了限价单,订单可能在盘中稍后才成交。您可以利用 after_trading_end 函数,在每天收盘后,根据最终的真实持仓来清理您的 rank_pool:
def after_trading_end(context):
# 获取当前真实的持仓标的列表
current_positions = list(context.portfolio.positions.keys())
# 找出在 rank_pool 中但已经不在真实持仓中的标的,进行清理
keys_to_delete = []
for sy in rank_pool:
if sy not in current_positions:
keys_to_delete.append(sy)
for sy in keys_to_delete:
del rank_pool[sy]
log.info(f'盘后清理:{sy} 已不在持仓中,从 rank_pool 移除')
order_target 返回值不为 None 只代表报单成功。Order.status == OrderStatus.held 或 Order.filled == Order.amount。context.portfolio.positions 为准,避免自定义状态与真实账户状态脱节。