问题描述
def sell_to_zero(context,sy):
try:
r=order_target(sy, 0)
print(r)
if r is not None:
log.info('卖出('+sy+')['+get_security_info(sy).display_name+']成功')
send_msg('卖出:'+sy+','+get_security_info(sy).display_name)
finally:
if sy in rank_pool:
del rank_pool[sy]
2019-02-27 09:30:00 - INFO - UserOrder({'order_id': 1626576144, 'security': '600895.XSHG', 'amount': 2000, 'filled': 2000, 'price': 18.83, 'status': held, 'add_time': datetime.datetime(2019, 2, 27, 9, 30), 'side': 'long', 'action': 'close', 'pindex': 0, 'style': MarketOrderStyle: _limit_price=0.0})
2019-02-27 09:30:00 - INFO - 卖出(600895.XSHG)[张江高科]成功
已经在rank_pool这个字典里面删除了
结果。。。
结果当天又发现还是可以通过在持仓里遍历出来了,我一看虽然当天是跌停,你好说在order_target里给我一个返回状态让我知道是不是卖成功了啊???
019-02-27 15:15:00 - INFO - 603338.XSHG,浙江鼎力,3447.9999999999995
2019-02-27 15:15:00 - INFO - 002157.XSHE,正邦科技,244524.0
2019-02-27 15:15:00 - INFO - 002124.XSHE,天邦股份,311080.0
2019-02-27 15:15:00 - INFO - 002414.XSHE,高德红外,687.0
2019-02-27 15:15:00 - INFO - 600312.XSHG,平高电气,893.0
2019-02-27 15:15:00 - INFO - 600604.XSHG,市北高新,218784.0
2019-02-27 15:15:00 - INFO - 600895.XSHG,张江高科,223884.0
2019-02-27 15:15:00 - INFO - 000917.XSHE,电广传媒,268272.0
2019-02-27 15:15:00 - INFO - 002776.XSHE,ST柏龙,246992.0
2019-02-27 15:15:00 - INFO - 300037.XSHE,新宙邦,2663.0
2019-02-27 15:15:00 - INFO - 601928.XSHG,凤凰传媒,743.0
File "/tmp/jqcore/jqboson/jqboson/core/strategy.py", line 376, in _wrapper
return cb(self._context.strategy_environment.strategy_context, **cb_kwargs)
File "/tmp/strategy/user_code.py", line 71, in select_stocks
sell_it(context)
File "/tmp/strategy/user_code.py", line 296, in sell_it
protfit=rank_pool[sy][0]
KeyError: '600895.XSHG'
解决方案
在 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为准,避免自定义状态与真实账户状态脱节。