谨以此文献给2019年9月3日所有参与ni2001-ni1911交易的朋友!
2019年9月3日,ni2001以148000开盘,较前一交易日结算价136680上涨8.28%。ni1911以139450开盘,较前一交易日结算价136960上涨1.82%。ni2001与ni1911的价差以罕见的8550开盘,数分钟之后,回归至0以下。
或许如此极端行情不会再出现,这一日必将在所有价差交易者回忆中留下浓墨重彩的一笔。
ni2001 | ni1911 |
---|---|
![]() |
![]() |
2019年10月23日更新:
不适宜在回调函数中执行复杂算法,参考OnRtnOrder。同时增加to_dict_raw方法,需要更新AlgoPlus/src/AlgoPlus/utils/base_field.py
2019年10月27日更新:
修正因状态重置不正确导致的不能平仓的问题。
技术要点
1、update_buy_spread_open(买开)、update_sell_spread_close(卖平)、update_sell_spread_open(卖开)、update_buy_spread_close(买平)方法调用get_order_price_l1获取报单价格。get_order_price_l1方法实现信号逻辑,满足信号条件则返回报单价格,否则返回None。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def get_order_price_l1(self, direction, offset_flag): """ 获取腿一(不活跃合约)报单价格。 :param direction: b"0"表示买,其他(b"1")表示卖,注意是bytes类型 :param offset_flag: b"0"表示开,其他(b"1")表示平,注意是bytes类型 :return: 根据买开、卖平、卖开、卖平类型,判断是否满足交易条件,如果满足,返回订单委托价格。否则,返回None。 """ order_price = None try: if direction == b'0': if self.md_a.BidPrice1 - self.md_b.BidPrice1 < self.parameter_field.BuyOpenSpread if offset_flag == b'0' else self.parameter_field.BuyCloseSpread: order_price = self.md_a.BidPrice1 + self.parameter_field.APriceTick else: if self.md_a.AskPrice1 - self.md_b.AskPrice1 > self.parameter_field.SellOpenSpread if offset_flag == b'0' else self.parameter_field.SellCloseSpread: order_price = self.md_a.AskPrice1 - self.parameter_field.APriceTick finally: return order_price |
2、不适宜在回调函数里做比较耗时的操作,所以将收到的通知转为字典后放入列表中,等待后续处理:
1 2 3 4 5 6 7 8 9 |
def OnRtnOrder(self, pOrder): """ 当收到订单状态变化时,可以在本方法中获得通知。不适宜在回调函数里做比较耗时的操作。可参考OnRtnOrder的做法。 根据pOrder.OrderStatus的取值调用适应的交易算法。 :param pOrder: AlgoPlus.CTP.ApiStruct中OrderField的实例。 :return: """ if self.is_my_order(pOrder.OrderRef): self.local_rtn_order_list.append(pOrder.to_dict_raw()) |
3、根据订单状态的变化调用不同的交易逻辑,on_leg1_traded方法实现腿一成交逻辑,on_leg2_traded方法实现腿二成交逻辑,on_leg1_action方法实现腿一撤单逻辑,on_leg2_action方法实现腿二撤单逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
def process_rtn_order(self): """ 从上次订单ID位置开始处理订单数据。 :return: """ last_rtn_order_id = len(self.local_rtn_order_list) for rtn_order in self.local_rtn_order_list[self.last_rtn_order_id:last_rtn_order_id]: local_order_info = self.local_order_dict[rtn_order["OrderRef"]] if local_order_info.OrderSysID == b'': local_order_info.OrderSysID = rtn_order["OrderSysID"] # 未成交 if local_order_info.OrderStatus == b'' and rtn_order["OrderStatus"] == b'3': local_order_info.OrderStatus = b'3' # 未成交状态 # 全部成交 elif rtn_order["OrderStatus"] == b'0': local_order_info.OrderStatus = b'0' # 全部成交状态 if rtn_order["InstrumentID"] == self.parameter_field.AInstrumentID: self.on_leg1_traded(rtn_order) elif rtn_order["InstrumentID"] == self.parameter_field.BInstrumentID: self.on_leg2_traded(rtn_order) # 部分成交 elif rtn_order["OrderStatus"] == b'1': local_order_info.OrderStatus = b'1' # 部分成交状态 if rtn_order["InstrumentID"] == self.parameter_field.AInstrumentID: self.on_leg1_traded(rtn_order) elif rtn_order["InstrumentID"] == self.parameter_field.BInstrumentID: self.on_leg2_traded(rtn_order) # 撤单成功 elif rtn_order["OrderStatus"] == b'5': local_order_info.OrderStatus = b'5' # 已撤单状态 if rtn_order["InstrumentID"] == self.parameter_field.AInstrumentID: self.with_draw_num += 1 self.on_leg1_action(rtn_order) elif rtn_order["InstrumentID"] == self.parameter_field.BInstrumentID: self.on_leg2_action(rtn_order) # 委托失败 elif rtn_order["OrderSubmitStatus"] == b'4': local_order_info.OrderStatus = b'9' # 委托失败状态 if rtn_order["InstrumentID"] == self.parameter_field.AInstrumentID: self.on_leg1_insert_fail(rtn_order) elif rtn_order["InstrumentID"] == self.parameter_field.BInstrumentID: self.on_leg2_insert_fail(rtn_order) # 撤单失败 elif rtn_order["OrderSubmitStatus"] == b'5': local_order_info.OrderStatus = b'8' # 撤单失败状态 if rtn_order["InstrumentID"] == self.parameter_field.AInstrumentID: self.on_leg1_action_fail(rtn_order) elif rtn_order["InstrumentID"] == self.parameter_field.BInstrumentID: self.on_leg2_action_fail(rtn_order) self.last_rtn_order_id = last_rtn_order_id |
4、当腿一全部成交/部分成交时,计算腿一成交手数,录入相应手数腿二的报单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
def on_leg1_traded(self, rtn_order): """ 腿一(不活跃合约)成交时需要执行的交易逻辑。 :param rtn_order: AlgoPlus.CTP.ApiStruct中OrderFireq_order_inserteld的实例。 :return: """ local_order_info = self.local_order_dict[rtn_order["OrderRef"]] # 本地订单信息 volume_traded = local_order_info.VolumeTotal - rtn_order["VolumeTotal"] # 腿一成交数量 if volume_traded > 0: local_order_info.VolumeTotal = rtn_order["VolumeTotal"] # 腿一剩余数量 if rtn_order["CombOffsetFlag"] == b'0': self.position_a += volume_traded # 腿一总持仓 else: self.position_a -= volume_traded # 腿一总持仓 self.order_ref += 1 if rtn_order["Direction"] == b'0': order_price = self.get_order_price_l2(b'1') # 腿二报单价格 if rtn_order["CombOffsetFlag"] == b'0': self.sell_open(self.parameter_field.BExchangeID, self.parameter_field.BInstrumentID, order_price, volume_traded, self.order_ref) else: self.sell_close(self.parameter_field.BExchangeID, self.parameter_field.BInstrumentID, order_price, volume_traded, self.order_ref) else: order_price = self.get_order_price_l2(b'0') # 腿二报单价格 if rtn_order["CombOffsetFlag"] == b'0': self.buy_open(self.parameter_field.BExchangeID, self.parameter_field.BInstrumentID, order_price, volume_traded, self.order_ref) else: self.buy_close(self.parameter_field.BExchangeID, self.parameter_field.BInstrumentID, order_price, volume_traded, self.order_ref) |
5、当腿二全部成交,计算腿二成交手数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def on_leg2_traded(self, rtn_order): """ 腿二(活跃合约)成交时需要执行的交易逻辑。 :param rtn_order: AlgoPlus.CTP.ApiStruct中OrderField的实例。 :return: """ local_order_info = self.local_order_dict[rtn_order["OrderRef"]] # 本地订单信息 volume_traded = local_order_info.VolumeTotal - rtn_order["VolumeTotal"] # 腿二成交数量 if volume_traded > 0: local_order_info.VolumeTotal = rtn_order["VolumeTotal"] # 腿二成交数量 if rtn_order["CombOffsetFlag"] == b'0': self.position_b += volume_traded # 腿二总持仓 else: self.position_b -= volume_traded # 腿二总持仓 |
6、当腿二撤单时,以新价格重新报单:
1 2 3 4 5 6 7 8 9 |
def on_leg2_action(self, rtn_order_field): """ 腿二(活跃合约)撤单成功时需要执行的交易逻辑。 :param rtn_order_field: AlgoPlus.CTP.ApiStruct中OrderField的实例。 :return: """ self.order_ref += 1 order_price = self.get_order_price_l2(rtn_order_field.Direction) # 腿二报单价格 self.req_order_insert(rtn_order_field.ExchangeID, rtn_order_field.InstrumentID, order_price, rtn_order_field.VolumeTotal, self.order_ref, rtn_order_field.Direction, rtn_order_field.CombOffsetFlag) |
7、录入报单时,将关键信息保存在本地,收到订单状态通知时更新相关数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if self.ReqOrderInsert(input_order_field) == 0: # 本地订单信息字典 self.local_order_dict[input_order_field.OrderRef] = LocalOrderInfo( ExchangeID=input_order_field.ExchangeID, InstrumentID=input_order_field.InstrumentID, OrderRef=input_order_field.OrderRef, Direction=input_order_field.Direction, OffsetFlag=input_order_field.CombOffsetFlag, LimitPrice=input_order_field.LimitPrice, VolumeTotalOriginal=input_order_field.VolumeTotalOriginal, VolumeTotal=input_order_field.VolumeTotalOriginal, OrderStatus=b'', OrderSysID=b'', InputTime=timer(), ) |
8、实时监控所有本地订单信息,判断是否需要撤单,with_draw_leg1_order实现腿一撤单请求逻辑,with_draw_leg2_order方法实现腿二撤单请求逻辑,如果所有订单均已成交或者撤单,则重置状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
def check_local_orders(self): """ 检查所有挂单是否满足撤单条件。 :return: """ has_untraded_order = False try: for order_ref in list(self.local_order_dict): local_order_field = self.local_order_dict[order_ref] if local_order_field.OrderStatus == b'1' or local_order_field.OrderStatus == b'3': has_untraded_order = True if local_order_field.InstrumentID == self.parameter_field.AInstrumentID: self.with_draw_leg1_order(local_order_field) elif local_order_field.InstrumentID == self.parameter_field.BInstrumentID: self.with_draw_leg2_order(local_order_field) elif local_order_field.OrderStatus != b'0' and local_order_field.OrderStatus != b'5': has_untraded_order = True except Exception as err: pass finally: if not has_untraded_order and self.sig_stage != 0: if self.position_a == self.position_b: self.sig_stage = 0 self.local_order_dict.clear() if self.position_b == 0: self.position_status = 0 elif self.position_status > 0: self.position_status = 1 elif self.position_status < 0: self.position_status = -1 self._write_log(f"腿一与腿二配对!目前持仓情况,腿一:{self.position_a},腿二:{self.position_b},sig_stage:{self.sig_stage},position_status:{self.position_status}") |
9、触发开仓风控时停止开仓交易,触发平仓风控时停止平仓交易:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def update_open_status(self): """ 开仓限制条件,以撤单次数为例。 :return: 可开仓,返回True。否则返回False。 """ if self.with_draw_num < self.parameter_field.MaxActionNum and self.position_a < self.parameter_field.MaxPosition: return True return False def update_close_status(self): """ 平仓限制条件。 :return: 可平仓,返回True。否则返回False。 """ return True |
10、价差相关参数以结构体形式保存,并通过Queue完成初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
my_strategy_parameter_field = SpreadTradingFields( StrategyName=b'AlgoPlus Spread Trading Exemplification', StrategyID=1, AInstrumentID=b'ni2001', APriceTick=10, AExchangeID=b'SHFE', BInstrumentID=b'ni1912', # B合约代码 BPriceTick=10, # B最小变动价位 BExchangeID=b'SHFE', # B交易所代码 BuyOpenSpread=-2500, # 买开仓价差 SellCloseSpread=-2000, # 卖平仓价差 SellOpenSpread=-1500, # 卖开仓价差 BuyCloseSpread=-2000, # 买平仓价差 Lots=1, # 下单手数 MaxActionNum=100, # 最大撤单次数 MaxPosition=10, # 最大持仓手数 AWaitSeconds=1, # B合约撤单前等待秒 BWaitSeconds=1, # B合约撤单前等待秒 ) share_queue.put(my_strategy_parameter_field) |
完整代码下载地址:
码云:https://gitee.com/AlgoPlus/AlgoPlus
GitHub:https://github.com/CTPPlus/AlgoPlus
评论前必须登录!
注册