class ATP(object): """""" def __init__(self): self.TradingDay = '' '''交易日''' self.Actionday = '' '''自然日''' self.Actionday1 = '' '''隔夜自然日''' self.tick_time = '' '''最后tick的交易时间:yyyy-MM-dd HH:mm:ss''' self.trading_days: list = [] '''交易日序列''' self.trade_time: dict = {} '''品种交易时间''' self.instrument_info = {} '''合约信息''' self.received_instrument: list = [] '''已接收tick的合约''' self.cfg = Config() self.stra_instances = [] self.q = CtpQuote() self.t = CtpTrade() def on_order(self, stra: Strategy, data: Data, order: OrderItem): """此处调用ctp接口即可实现实际下单""" # print('stra order') self.cfg.log.war('{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\n'.format( len(stra.Orders), stra.D[-1], order.Direction, order.Offset, order.Price, order.Volume, order.Remark)) if self.cfg.real_order_enable: # print(order) order_id = stra.ID * 1000 + len(stra.GetOrders()) + 1 # 价格修正 order.Price = order.Price // self.t.instruments[ order.Instrument].PriceTick * self.t.instruments[ order.Instrument].PriceTick if order.Offset != OffsetType.Open: key = '{0}_{1}'.format( order.Instrument, 'Sell' if order.Direction == DirectType.Buy else 'Buy') # 无效,没提示...pf = PositionField() pf = self.t.positions.get(key) if not pf or pf.Position <= 0: self.cfg.log.error('没有对应的持仓') else: volClose = min(pf.Position, order.Volume) # 可平量 instField = self.t.instruments[order.Instrument] if instField.ExchangeID == 'SHFE': tdClose = min(volClose, pf.TdPosition) if tdClose > 0: self.t.ReqOrderInsert(order.Instrument, order.Direction, OffsetType.CloseToday, order.Price, tdClose, OrderType.Limit, order_id) volClose -= tdClose if volClose > 0: self.t.ReqOrderInsert(order.Instrument, order.Direction, OffsetType.Close, order.Price, volClose, OrderType.Limit, order_id) else: self.t.ReqOrderInsert(order.Instrument, order.Direction, OffsetType.Open, order.Price, order.Volume, OrderType.Limit, order_id) def get_orders(self, stra): """获取策略相关的委托列表""" rtn = [] for (k, v) in self.t.orders.items(): if v.Custom // 1000 == stra.ID: rtn.append(v) return rtn def get_lastorder(self, stra): """获取最后一个委托""" rtn = self.get_orders(stra) if len(rtn) > 0: sorted(rtn, key=lambda o: o.Custom) return rtn[-1] return None def get_notfill_orders(self, stra): """获取未成交委托""" rtn = [] orders = self.get_orders(stra) if len(orders) > 0: for order in orders: if order.Status == OrderStatus.Normal or order.Status == OrderStatus.Partial: rtn.append(order) return rtn def req_order(self, instrument: str, dire: DirectType, offset: OffsetType, price: float, volume: int, type: OrderType = OrderType.Limit, stra: Strategy = ''): """发送委托""" order_id = stra.ID * 1000 + len(stra.GetOrders()) + 1 # 价格修正 price = price // self.t.instruments[ instrument].PriceTick * self.t.instruments[instrument].PriceTick self.t.ReqOrderInsert(instrument, dire, offset, price, volume, type, order_id) def cancel_all(self, stra): """撤销所有委托""" for order in self.get_notfill_orders(stra): self.t.ReqOrderAction(order.OrderID) def load_strategy(self): """加载../strategy目录下的策略""" """通过文件名取到对应的继承Data的类并实例""" if not self.cfg.stra_path: return for path in self.cfg.stra_path: for stra_name in self.cfg.stra_path[path]: f = os.path.join(path, '{}.py'.format(stra_name)) # 只处理对应的 py文件 if os.path.isdir(f) or os.path.splitext(f)[0] == '__init__': continue # 以目录结构作为 namespace module_name = "{0}.{1}".format( os.path.split(os.path.dirname(f))[1], stra_name) module = __import__(module_name) # import module c = getattr(getattr(module, stra_name), stra_name) # 双层调用才是class,单层是为module if not issubclass(c, Strategy): # 类c是Data的子类 continue # 与策略文件同名的 yaml 作为配置文件处理 cfg_name = os.path.join(path, '{0}.yml'.format(stra_name)) if os.path.exists(cfg_name): with open(cfg_name, encoding='utf-8') as stra_cfg_json_file: params = yaml.load(stra_cfg_json_file) for param in [p for p in params if p is not None]: # 去除None的配置 if param['ID'] not in self.cfg.stra_path[path][ stra_name]: continue stra: Strategy = c(param) stra.ID = param['ID'] self.cfg.log.info("# strategy:{0}".format(stra)) for data in stra.Datas: data.InstrumentInfo = self.instrument_info[ data.Instrument] data.SingleOrderOneBar = self.cfg.single_order_one_bar self.stra_instances.append(stra) else: self.cfg.log.error("缺少对应的 yaml 配置文件{0}".format(cfg_name)) def get_data_zmq(self, req: ReqPackage) -> dict: '''''' # pip install pyzmq即可安装 context = zmq.Context() socket = context.socket(zmq.REQ) # REQ模式,即REQ-RSP CS结构 # socket.connect('tcp://localhost:8888') # 连接本地测试 socket.connect(self.cfg.cfg_zmq) # 实际netMQ数据服务器地址 p = req.__dict__() req['Type'] = req.Type.value socket.send_json(p) # 直接发送__dict__转换的{}即可,不需要再转换成str # msg = socket.recv_unicode() # 服务器此处查询出现异常, 排除中->C# 正常 # 用recv接收,但是默认的socket中并没有此提示函数(可能是向下兼容的函数),不知能否转换为其他格式 bs = socket.recv() # 此处得到的是bytes # gzip解压:decompress(bytes)解压,会得到解压后的bytes,再用decode转成string gzipper = gzip.decompress(bs).decode() # decode转换为string # json解析:与dumps对应,将str转换为{} if len(gzipper) == 0: return {} bs = json.loads(gzipper) # json解析 return bs def read_bars(self, stra: Strategy) -> list: """netMQ""" bars = [] for data in stra.Datas: # 请求数据格式 req = ReqPackage() req.Type = BarType.Min req.Instrument = data.Instrument req.Begin = stra.BeginDate req.End = stra.EndDate # __dict__返回diction格式,即{key,value}格式 for bar in self.get_data_zmq(req): bar['Instrument'] = data.Instrument bar['DateTime'] = bar.pop('_id') bars.append(bar) if stra.EndDate == time.strftime("%Y%m%d", time.localtime()): # 实时K线数据 req.Type = BarType.Real for bar in self.get_data_zmq(req): bar['Instrument'] = data.Instrument bar['DateTime'] = bar.pop('_id') bars.append(bar) bars.sort(key=lambda bar: bar['DateTime']) # 按时间升序 return bars def read_data_test(self): """取历史和实时K线数据,并执行策略回测""" stra: Strategy = None # 只为后面的提示信息创建 for stra in self.stra_instances: self.cfg.log.info('策略 {0} 正在加载历史数据'.format(stra.ID)) if stra.TickTest: tradingday = stra.BeginDate tick: Tick = None while tradingday < time.strftime('%Y%m%d', time.localtime()): for tick in self.read_ticks(stra, tradingday): for data in stra.Datas: if data.Instrument == tick.Instrument: data.on_tick(tick, tradingday) tradingday = datetime.strftime( datetime.strptime(tradingday, '%Y%m%d') + timedelta(days=1), '%Y%m%d') else: listBar = [] bars = self.read_bars(stra) listBar = [ Bar(b['DateTime'], b['Instrument'], b['High'], b['Low'], b['Open'], b['Close'], b['Volume'], b['OpenInterest'], b['Tradingday']) for b in bars ] for bar in listBar: for data in stra.Datas: if data.Instrument == bar.Instrument: data.__new_min_bar__(bar) # 调Data的onbar # 生成策略的测试报告 # if len(stra.Orders) > 0: # Report(stra) self.cfg.log.war("test history is end.") def link_fun(self): '''策略函数与ATP关联''' for stra in self.stra_instances: stra._data_order = self.on_order stra._get_orders = self.get_orders stra._get_lastorder = self.get_lastorder stra._get_notfill_orders = self.get_notfill_orders stra._req_order = self.req_order stra.ReqCancel = self.t.ReqOrderAction stra._req_cancel_all = self.cancel_all for data in stra.Datas: if self.q is not None and self.q.logined: self.q.ReqSubscribeMarketData(data.Instrument) def OnFrontConnected(self, t: CtpTrade): """""" self.cfg.log.war("[trade] connected by client") if self.t.ReqUserLogin: self.t.ReqUserLogin(self.cfg.investor, self.cfg.password, self.cfg.broker, self.cfg.product_info, self.cfg.app_id, self.cfg.auth_code) def relogin(self): """""" self.t.ReqUserLogout() self.cfg.log.info( '[trade] sleep 60 seconds to wait try connect next time') time.sleep(60) self.t.ReqConnect(self.cfg.front_trade) def OnRspUserLogin(self, t: CtpTrade, info: InfoField): """""" if info.ErrorID == 0: self.TradingDay = self.t.tradingday self.get_actionday() # 取得交易日后才能取actionday self.received_instrument.clear() # 记录收到的tick的合约 self.get_trading_time() # 取品种交易时间信息 if not self.q.logined: self.q.OnConnected = self.q_OnFrontConnected self.q.OnUserLogin = self.q_OnRspUserLogin self.q.OnDisConnected = lambda o, x: self.cfg.log.war( f'[QUOTE]disconnected: {x}') # self.q.OnTick = self.q_Tick self.q.OnTick = lambda o, f: threading.Thread( target=self.q_Tick, args=(o, f)).start() self.q.ReqConnect(self.cfg.front_quote) if self.cfg.show_tick_time: threading.Thread(target=self.showmsg).start() self.cfg.log.info('[trade] {}'.format(info)) else: self.cfg.log.error('[trade] {}'.format(info)) if info.ErrorID == 7: threading.Thread(target=self.relogin).start() def showmsg(self): while self.t.logined: if self.tick_time != '': msg = '' stra: Strategy = None for stra in self.stra_instances: msg += '{}[L={}; S={}]{}||'.format( type(stra).__name__, stra.PositionLong, stra.PositionShort, stra.Params) print(self.tick_time + '||' + msg, end='\r') time.sleep(1) def get_stra(self, order: OrderField) -> Strategy: lst = [ stra for stra in self.stra_instances if stra.ID == order.Custom // 1000 ] if len(lst) > 0: return lst[0] return None def OnOrder(self, t: CtpTrade, order: OrderField): """""" if not order.IsLocal: return self.cfg.log.info(order) if order.VolumeLeft == 0: return stra = self.get_stra(order) if stra is None: return if self.cfg.chasing: threading.Thread(target=self.resend, args=(order, )).start() stra.OnOrder(order) def resend(self, order: OrderField): wait = self.cfg.chasing['wait_seconds'] if wait > 0: time.sleep(wait) if order.VolumeLeft > 0: self.t.ReqOrderAction(order.OrderID) def OnCancel(self, t: CtpTrade, order: OrderField): """""" # self.cfg.log.info(order) if not order.IsLocal: return if order.VolumeLeft == 0: return stra = self.get_stra(order) if stra is None: return custom = order.Custom custom = custom + 100 times = custom % 1000 // 100 if times <= self.cfg.chasing['resend_times']: if order.Direction == DirectType.Buy: price = self.q.inst_tick[ order.InstrumentID].AskPrice + self.cfg.chasing[ 'offset_ticks'] * self.t.instruments[ order.InstrumentID].PriceTick else: price = self.q.inst_tick[ order.InstrumentID].BidPrice - self.cfg.chasing[ 'offset_ticks'] * self.t.instruments[ order.InstrumentID].PriceTick else: if order.Direction == DirectType.Buy: price = self.q.inst_tick[order.InstrumentID].UpperLimitPrice else: price = self.q.inst_tick[order.InstrumentID].LowerLimitPrice self.t.ReqOrderInsert(order.InstrumentID, order.Direction, order.Offset, price, order.VolumeLeft, OrderType.Limit, pCustom=custom) stra.OnCancel(order) def OnTrade(self, t: CtpTrade, trade: TradeField): """""" order = self.t.orders[trade.OrderID] if not order.IsLocal: return self.cfg.log.info(trade) stra = self.get_stra(order) if stra is None: return stra.OnTrade(trade) def OnRtnErrOrder(self, t: CtpTrade, order: OrderField, info: InfoField): """""" if not order.IsLocal: return self.cfg.log.info(order) stra = self.get_stra(order) if stra is None: return stra.OnErrOrder(order, info) def OnInstrumentStatus(self, obj, inst: str, status: InstrumentStatus): """ 交易合约状态响应 :param obj: Trade 交易接口 :param inst: str 合约/品种 :param status: InstrumentStatus 状态 :return: """ # 夜盘结算or市场收盘自动退出 if (datetime.now().strftime('%H%M%S') < '030000' and sum([ 1 if x == InstrumentStatus.Continous else 0 for x in self.t.instrument_status.values() ]) == 0) or (sum([ 1 if x != InstrumentStatus.Closed else 0 for x in self.t.instrument_status.values() ]) == 0): threading.Thread(target=self.close_api).start() def close_api(self): time.sleep(1) if self.t and self.t.logined: self.t.ReqUserLogout() if self.q and self.q.logined: self.q.ReqUserLogout() def q_OnFrontConnected(self, q: CtpQuote): """""" self.cfg.log.info("[quote] connected by client") self.q.ReqUserLogin(self.cfg.investor, self.cfg.password, self.cfg.broker) def q_OnRspUserLogin(self, q: CtpQuote, info: InfoField): """""" self.cfg.log.info('[quote] {}'.format(info)) for stra in self.stra_instances: for data in stra.Datas: self.q.ReqSubscribeMarketData(data.Instrument) def q_Tick(self, q: CtpQuote, tick: Tick): """""" # 对tick时间进行修正处理 ut = tick.UpdateTime[0:6] + '00' mins_dict = self.trade_time[tick.Instrument] # 由下面的 updatetime[-2:0] != '00' 处理 if ut not in mins_dict['Mins']: # 开盘/收盘 if ut in mins_dict['Opens']: tick.UpdateTime = (datetime.strptime(ut, '%H:%M:%S') + timedelta(minutes=1)).strftime('%H:%M:%S') elif ut in mins_dict['Ends']: # 重新登录会收到上一节的最后tick tick_dt = datetime.strptime( '{} {}'.format(datetime.now().strftime('%Y%m%d'), tick.UpdateTime), '%Y%m%d %H:%M:%S') now_dt = datetime.now() diff_snd = 0 if tick_dt > now_dt: diff_snd = (tick_dt - now_dt).seconds else: diff_snd = (now_dt - tick_dt).seconds if diff_snd > 30: return tick.UpdateTime = (datetime.strptime(ut, '%H:%M:%S') + timedelta(seconds=-1)).strftime('%H:%M:%S') else: return # 首tick不处理(新开盘时会收到之前的旧数据) if tick.Instrument not in self.received_instrument: self.received_instrument.append(tick.Instrument) return actionday = self.TradingDay if tick.UpdateTime[0:2] > '20': actionday = self.Actionday elif tick.UpdateTime[0:2] < '04': actionday = self.Actionday1 ut = actionday[0:4] + '-' + actionday[4:6] + '-' + actionday[ 6:] + ' ' + tick.UpdateTime tick.UpdateTime = ut for stra in self.stra_instances: for data in stra.Datas: if data.Instrument == tick.Instrument: data.on_tick(tick, self.TradingDay) self.tick_time = ut def get_trading_time(self): self.trade_time.clear() req = ReqPackage() req.Type = BarType.Time times = self.get_data_zmq(req) # 按时间排序, 确保最后实施的时间段作为依据. # 根据时间段设置,生成 opens; ends; mins盘中时间 for group in times: g_id = group['GroupId'] # list(group.keys())[0] section = group['WorkingTimes'] # list(group.values())[0] opens = [] ends = [] mins = [] for s in section: opens.append((datetime.strptime(s['Begin'], '%H:%M:%S') + timedelta(minutes=-1)).strftime('%H:%M:00')) ends.append(s['End']) t_begin = datetime.strptime('20180101' + s['Begin'], '%Y%m%d%H:%M:%S') s_end = datetime.strptime('20180101' + s['End'], '%Y%m%d%H:%M:%S') if t_begin > s_end: # 夜盘 s_end += timedelta(days=1) while t_begin < s_end: mins.append(t_begin.strftime('%H:%M:00')) t_begin = t_begin + timedelta(minutes=1) self.trade_time[g_id] = { 'Opens': opens, 'Ends': ends, 'Mins': mins } # 品种交易时间==>合约交易时间 for inst, info in self.t.instruments.items(): proc = info.ProductID if proc in self.trade_time: self.trade_time[inst] = self.trade_time[proc] else: self.trade_time[inst] = self.trade_time['default'] def get_actionday(self): # 取交易日历 req = ReqPackage() req.Type = BarType.TradeDate self.trading_days = self.get_data_zmq(req) req.Type = BarType.Product products = self.get_data_zmq(req) proc_dict = {} for p in products: proc_dict[p['_id']] = p req.Type = BarType.InstrumentInfo insts = self.get_data_zmq(req) for inst_proc in insts: proc = proc_dict[inst_proc['ProductID']] f = InstrumentField() f.ExchangeID = proc['ExchangeID'] f.InstrumentID = inst_proc['_id'] f.PriceTick = proc['PriceTick'] f.ProductID = inst_proc['ProductID'] f.ProductType = proc['ProductType'] f.VolumeMultiple = proc['VolumeTuple'] if 'MAXLIMITORDERVOLUME' in proc: f.MaxOrderVolume = proc['MAXLIMITORDERVOLUME'] self.instrument_info[inst_proc['_id']] = f # 接口未登录,不计算Actionday if self.TradingDay == '': return self.Actionday = self.TradingDay if self.trading_days.index( self.TradingDay) == 0 else self.trading_days[ self.trading_days.index(self.TradingDay) - 1] self.Actionday1 = (datetime.strptime(self.Actionday, '%Y%m%d') + timedelta(days=1)).strftime('%Y%m%d') def Run(self): """""" if self.cfg.front_trade == '' or self.cfg.front_quote == '': self.cfg.log.war('**** 交易接口未配置 ****') self.get_actionday() else: if self.cfg.investor == '': self.cfg.investor = input('invesorid on {}:'.format( self.cfg.front_name)) else: self.cfg.log.war('{} loging by ctp'.format(self.cfg.investor)) if self.cfg.password == '': self.cfg.password = getpass.getpass() if self.cfg.running_as_server: self.cfg.log.war('7*24 as server ....') threading.Thread(target=self._run_seven, daemon=True).start() else: self.cfg.log.war('run once only') self.start_api() while not self.q.logined: time.sleep(1) self.cfg.log.info('加载策略...') self.load_strategy() self.cfg.log.info('历史数据回测...') self.read_data_test() self.link_fun() def start_api(self): '''启动接口''' self.t.OnConnected = self.OnFrontConnected self.t.OnDisConnected = lambda o, x: self.cfg.log.war( f'[TRADE]disconnected: {x}') self.t.OnUserLogin = self.OnRspUserLogin self.t.OnOrder = self.OnOrder self.t.OnTrade = self.OnTrade self.t.OnCancel = self.OnCancel self.t.OnErrOrder = self.OnRtnErrOrder self.t.OnInstrumentStatus = self.OnInstrumentStatus self.t.ReqConnect(self.cfg.front_trade) def _run_seven(self): '''7*24''' while True: day = datetime.now().strftime('%Y%m%d') left_days = list(filter(lambda x: x > day, self.trading_days)) if len(left_days) == 0: self.cfg.log.info('读取交易日历...') self.get_actionday() self.cfg.log.info('读取交易日历完成') left_days = list(filter(lambda x: x > day, self.trading_days)) next_trading_day = left_days[0] has_hight = (datetime.strptime(next_trading_day, '%Y%m%d') - datetime.strptime(day, '%Y%m%d')).days in [1, 3] now_time = datetime.now().strftime('%H%M%S') if not self.t.logined: # 当前非交易日 sleep_seconds = 0 if day not in self.trading_days: # 昨天有夜盘:今天凌晨有数据 if now_time <= '020000' and ( datetime.today() + timedelta.days(-1) ).strftime('%Y%m%d') in self.trading_days: time.sleep(1) else: self.cfg.log.info('{} is not tradingday.'.format(day)) self.cfg.log.info( 'continue after {}'.format(next_trading_day + ' 08:30:00')) sleep_seconds = (int)((datetime.strptime( next_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) elif now_time <= '083000': self.cfg.log.info('continue after {}'.format(day + ' 08:30:00')) sleep_seconds = (int)(( datetime.strptime(day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) elif now_time >= '150000': if has_hight: if datetime.now().strftime('%H%M%S') < '203000': self.cfg.log.info( 'continue after {}'.format(day + ' 20:30:00')) sleep_seconds = (int)( (datetime.strptime(day + '20:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) else: self.cfg.log.info( 'continue after {}'.format(next_trading_day + ' 08:30:00')) sleep_seconds = (int)((datetime.strptime( next_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) if sleep_seconds > 0: time.sleep(sleep_seconds) sleep_seconds = 0 # 启动接口 self.start_api() time.sleep(10) # 已收盘 # elif sum([1 if x != InstrumentStatus.Closed else 0 for x in self.t.instrument_status.values()]) == 0: # self.t.ReqUserLogout() # self.q.ReqUserLogout() # if has_hight: # self.cfg.log.info('continue after {}'.format(day + ' 20:30:00')) # time.sleep((datetime.strptime(day + '20:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) # else: # self.cfg.log.info('continue after {}'.format(next_trading_day + ' 08:30:00')) # time.sleep((datetime.strptime(next_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) # # 夜盘全部非交易 # elif now_time < '030000' and sum([1 if x == InstrumentStatus.Continous else 0 for x in self.t.instrument_status.values()]) == 0: # cur_trading_day = self.t.tradingday # self.t.ReqUserLogout() # self.q.ReqUserLogout() # # cur_trading_day = self.trading_days[self.trading_days.index(next_trading_day) - 1] 周末时取值不对 # self.cfg.log.info('continue after {}'.format(cur_trading_day + ' 08:30:00')) # time.sleep((datetime.strptime(cur_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) else: time.sleep(60 * 10)
class ATP(object): """""" def __init__(self): self.TradingDay = '' self.ActionDay = '' self.ActionDay1 = '' self.tick_time = '' self.cfg = Config() self.stra_instances = [] dllpath = os.path.join(os.getcwd(), self.cfg.ctp_dll_path) self.q = CtpQuote(dllpath) self.t = CtpTrade(dllpath) def on_order(self, stra: Strategy, data: Data, order: OrderItem): """此处调用ctp接口即可实现实际下单""" # print('stra order') self.cfg.log.war('{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\n'.format( len(stra.Orders), stra.D[-1], order.Direction, order.Offset, order.Price, order.Volume, order.Remark)) if self.cfg.real_order_enable: # print(order) order_id = stra.ID * 1000 + len(stra.GetOrders()) + 1 # 价格修正 order.Price = order.Price // self.t.instruments[ order.Instrument].PriceTick * self.t.instruments[ order.Instrument].PriceTick if order.Offset != OffsetType.Open: key = '{0}_{1}'.format( order.Instrument, 'Sell' if order.Direction == DirectType.Buy else 'Buy') # 无效,没提示...pf = PositionField() pf = self.t.positions.get(key) if not pf or pf.Position <= 0: self.cfg.log.error('没有对应的持仓') else: volClose = min(pf.Position, order.Volume) # 可平量 instField = self.t.instruments[order.Instrument] if instField.ExchangeID == 'SHFE': tdClose = min(volClose, pf.TdPosition) if tdClose > 0: self.t.ReqOrderInsert(order.Instrument, order.Direction, OffsetType.CloseToday, order.Price, tdClose, OrderType.Limit, order_id) volClose -= tdClose if volClose > 0: self.t.ReqOrderInsert(order.Instrument, order.Direction, OffsetType.Close, order.Price, volClose, OrderType.Limit, order_id) else: self.t.ReqOrderInsert(order.Instrument, order.Direction, OffsetType.Open, order.Price, order.Volume, OrderType.Limit, order_id) def get_orders(self, stra): """获取策略相关的委托列表""" rtn = [] for (k, v) in self.t.orders.items(): if v.Custom // 1000 == stra.ID: rtn.append(v) return rtn def get_lastorder(self, stra): """获取最后一个委托""" rtn = self.get_orders(stra) if len(rtn) > 0: sorted(rtn, key=lambda o: o.Custom) return rtn[-1] return None def get_notfill_orders(self, stra): """获取未成交委托""" rtn = [] orders = self.get_orders(stra) if len(orders) > 0: for order in orders: if order.Status == OrderStatus.Normal or order.Status == OrderStatus.Partial: rtn.append(order) return rtn def req_order(self, instrument: str, dire: DirectType, offset: OffsetType, price: float, volume: int, type: OrderType = OrderType.Limit, stra: Strategy = ''): """发送委托""" order_id = stra.ID * 1000 + len(stra.GetOrders()) + 1 # 价格修正 price = price // self.t.instruments[ instrument].PriceTick * self.t.instruments[instrument].PriceTick self.t.ReqOrderInsert(instrument, dire, offset, price, volume, type, order_id) def cancel_all(self, stra): """撤销所有委托""" for order in self.get_notfill_orders(stra): self.t.ReqOrderAction(order.OrderID) def load_strategy(self): """加载../strategy目录下的策略""" """通过文件名取到对应的继承Data的类并实例""" for path in self.cfg.stra_path: for filename in self.cfg.stra_path[path]: f = os.path.join(sys.path[0], '../{0}/{1}.py'.format(path, filename)) # 只处理对应的 py文件 if os.path.isdir(f) or os.path.splitext(f)[0] == '__init__': continue # 以目录结构作为 namespace module_name = "{0}.{1}".format(path, filename) class_name = filename module = __import__(module_name) # import module c = getattr(getattr(module, class_name), class_name) # 双层调用才是class,单层是为module if not issubclass(c, Strategy): # 类c是Data的子类 continue # 与策略文件同名的json作为配置文件处理 # file_name = os.path.join(os.getcwd(), path, '{0}.json'.format(class_name)) # if os.path.exists(file_name): # with open(file_name, encoding='utf-8') as stra_cfg_json_file: # cfg = json.load(stra_cfg_json_file) file_name = os.path.join(os.getcwd(), path, '{0}.yml'.format(class_name)) if os.path.exists(file_name): with open(file_name, encoding='utf-8') as stra_cfg_json_file: params = yaml.load(stra_cfg_json_file) for param in [p for p in params if p is not None]: # 去除None的配置 if param['ID'] not in self.cfg.stra_path[path][ filename]: continue stra: Strategy = c(param) stra.ID = param['ID'] self.cfg.log.info("# strategy:{0}".format(stra)) for data in stra.Datas: data.SingleOrderOneBar = self.cfg.single_order_one_bar self.stra_instances.append(stra) else: self.cfg.log.error("缺少对应的json文件{0}".format(file_name)) def get_data_zmq(self, req: ReqPackage) -> []: '''''' # pip install pyzmq即可安装 context = zmq.Context() socket = context.socket(zmq.REQ) # REQ模式,即REQ-RSP CS结构 # socket.connect('tcp://localhost:8888') # 连接本地测试 socket.connect(self.cfg.cfg_zmq) # 实际netMQ数据服务器地址 p = req.__dict__() req['Type'] = req.Type.value socket.send_json(p) # 直接发送__dict__转换的{}即可,不需要再转换成str # msg = socket.recv_unicode() # 服务器此处查询出现异常, 排除中->C# 正常 # 用recv接收,但是默认的socket中并没有此提示函数(可能是向下兼容的函数),不知能否转换为其他格式 bs = socket.recv() # 此处得到的是bytes # gzip解压:decompress(bytes)解压,会得到解压后的bytes,再用decode转成string gzipper = gzip.decompress(bs).decode() # decode转换为string # json解析:与dumps对应,将str转换为{} bs = json.loads(gzipper) # json解析 return bs def read_bars_pg(self, req: ReqPackage) -> []: """从postgres中读取数据""" if self.cfg.engine_postgres: conn = self.cfg.engine_postgres.raw_connection() cursor = conn.cursor() if req.Type == BarType.Min: sql = 'select "DateTime", \'{0}\' as "Instrument", "Tradingday", "High", "Low", "Open", "Close", "Volume", "OpenInterest" from future_min."{0}" where "Tradingday" between \'{1}\' and \'{2}\''.format( req.Instrument, req.Begin, req.End) if req.Type == BarType.Real: sql = 'select "DateTime", "Instrument", "Tradingday", "High", "Low", "Open", "Close", "Volume", "OpenInterest" from future_min.future_real where "Instrument" = \'{}\''.format( req.Instrument) cursor.execute(sql) data = cursor.fetchall() keys = [ "DateTime", "Instrument", "Tradingday", "High", "Low", "Open", "Close", "Volume", "OpenInterest" ] parsed_data = [] for row in data: parsed_data.append(dict(zip(keys, row))) return parsed_data def read_bars(self, stra: Strategy) -> []: """netMQ""" bars = [] for data in stra.Datas: # 请求数据格式 req = ReqPackage() req.Type = BarType.Min req.Instrument = stra.Instrument req.Begin = stra.BeginDate req.End = stra.EndDate # __dict__返回diction格式,即{key,value}格式 if self.cfg.engine_postgres: bars = bars + self.read_bars_pg(req) else: for bar in self.get_data_zmq(req): bar['Instrument'] = data.Instrument bar['DateTime'] = bar.pop('_id') bars.append(bar) if stra.EndDate == time.strftime("%Y%m%d", time.localtime()): # 实时K线数据 req.Type = BarType.Real if self.cfg.engine_postgres: bars = bars + self.read_bars_pg(req) else: for bar in self.get_data_zmq(req): bar['Instrument'] = data.Instrument bar['DateTime'] = bar.pop('_id') bars.append(bar) bars.sort(key=lambda bar: bar['DateTime']) # 按时间升序 return bars def read_ticks(self, stra: Strategy, tradingday: str) -> []: """读取tick数据 返回 list[Tick]""" ticks: list = [] if self.cfg.engine_postgres is not None: conn = self.cfg.engine_postgres.raw_connection() cursor = conn.cursor() sql = "select count(1) from pg_tables where schemaname='future_tick' and tablename='{}'".format( tradingday) try: cursor.execute(sql) if cursor.fetchone()[0] == 0: return [] for data in stra.Datas: sql = 'select "Actionday", "AskPrice", "AskVolume", "BidPrice", "BidVolume", "Instrument", "LastPrice", "OpenInterest", "UpdateMillisec", "UpdateTime", "Volume" from future_tick."{}" where "Instrument" = \'{}\''.format( tradingday, data.Instrument) cursor.execute(sql) rows = cursor.fetchall() for d in rows: tick = Tick() tick.Instrument = data.Instrument tick.AskPrice = d[1] tick.AskVolume = d[2] tick.BidPrice = d[3] tick.BidVolume = d[4] tick.LastPrice = d[6] tick.OpenInterest = d[7] tick.UpdateMillisec = d[8] tick.UpdateTime = d[0][0:4] + '-' + d[0][ 4:6] + '-' + d[0][6:] + ' ' + d[9] tick.Volume = d[10] ticks.append(tick) finally: conn.close() ticks.sort(key=lambda t: t.UpdateTime) return ticks def read_data_test(self): """取历史和实时K线数据,并执行策略回测""" stra: Strategy = None # 只为后面的提示信息创建 for stra in self.stra_instances: self.cfg.log.info('策略 {0} 正在加载历史数据'.format(stra.ID)) if stra.TickTest: tradingday = stra.BeginDate tick: Tick = None while tradingday < time.strftime('%Y%m%d', time.localtime()): for tick in self.read_ticks(stra, tradingday): for data in stra.Datas: if data.Instrument == tick.Instrument: data.on_tick(tick, tradingday) tradingday = datetime.strftime( datetime.strptime(tradingday, '%Y%m%d') + timedelta(days=1), '%Y%m%d') else: listBar = [] bars = self.read_bars(stra) listBar = [ Bar(b['DateTime'], b['Instrument'], b['High'], b['Low'], b['Open'], b['Close'], b['Volume'], b['OpenInterest'], b['Tradingday']) for b in bars ] for bar in listBar: for data in stra.Datas: if data.Instrument == bar.Instrument: data.__new_min_bar__(bar) # 调Data的onbar # 生成策略的测试报告 Report(stra) self.cfg.log.war("test history is end.") def link_fun(self): '''策略函数与ATP关联''' for stra in self.stra_instances: stra._data_order = self.on_order stra._get_orders = self.get_orders stra._get_lastorder = self.get_lastorder stra._get_notfill_orders = self.get_notfill_orders stra._req_order = self.req_order stra.ReqCancel = self.t.ReqOrderAction stra._req_cancel_all = self.cancel_all for data in stra.Datas: if self.q is not None and self.q.logined: self.q.ReqSubscribeMarketData(data.Instrument) def OnFrontConnected(self, t: CtpTrade): """""" self.cfg.log.war("t:connected by client") if self.t.ReqUserLogin: self.t.ReqUserLogin(self.cfg.investor, self.cfg.pwd, self.cfg.broker) def relogin(self): """""" self.t.ReqUserLogout() self.cfg.log.info('sleep 60 seconds to wait try connect next time') time.sleep(60) self.t.ReqConnect(self.cfg.front_trade) def OnRspUserLogin(self, t: CtpTrade, info: InfoField): """""" self.cfg.log.info('{0}:{1}'.format(info.ErrorID, info.ErrorMsg)) if info.ErrorID == 7: threading.Thread(target=self.relogin).start() if info.ErrorID == 0: self.TradingDay = self.t.tradingday self.get_actionday() # 取得交易日后才能取actionday if not self.q.logined: self.q.OnConnected = self.q_OnFrontConnected self.q.OnUserLogin = self.q_OnRspUserLogin # self.q.OnTick = self.q_Tick self.q.OnTick = lambda o, f: threading.Thread( target=self.q_Tick, args=(o, f)).start() self.q.ReqConnect(self.cfg.front_quote) threading.Thread(target=self.showmsg).start() def showmsg(self): while self.t.logined: if self.tick_time != '': msg = '' stra: Strategy = None for stra in self.stra_instances: msg += '{}[L={}; S={}]{}||'.format( type(stra).__name__, stra.PositionLong, stra.PositionShort, stra.Params) print(self.tick_time + '||' + msg, end='\r') time.sleep(1) def get_stra(self, order: OrderField) -> Strategy: lst = [ stra for stra in self.stra_instances if stra.ID == order.Custom // 1000 ] if len(lst) > 0: return lst[0] return None def OnOrder(self, t: CtpTrade, order: OrderField): """""" if not order.IsLocal: return self.cfg.log.info(order) if order.VolumeLeft == 0: return stra = self.get_stra(order) if stra is None: return if self.cfg.chasing: threading.Thread(target=self.resend, args=(order, )).start() stra.OnOrder(order) def resend(self, order: OrderField): time.sleep(self.cfg.chasing['wait_seconds']) if order.VolumeLeft > 0: self.t.ReqOrderAction(order.OrderID) def OnCancel(self, t: CtpTrade, order: OrderField): """""" # self.cfg.log.info(order) if not order.IsLocal: return if order.VolumeLeft == 0: return stra = self.get_stra(order) if stra is None: return custom = order.Custom custom = custom + 100 times = custom % 1000 // 100 if times <= self.cfg.chasing['resend_times']: if order.Direction == DirectType.Buy: price = self.q.inst_tick[ order.InstrumentID].AskPrice + self.cfg.chasing[ 'offset_ticks'] * self.t.instruments[ order.InstrumentID].PriceTick else: price = self.q.inst_tick[ order.InstrumentID].BidPrice - self.cfg.chasing[ 'offset_ticks'] * self.t.instruments[ order.InstrumentID].PriceTick else: if order.Direction == DirectType.Buy: price = self.q.inst_tick[order.InstrumentID].UpperLimitPrice else: price = self.q.inst_tick[order.InstrumentID].LowerLimitPrice self.t.ReqOrderInsert(order.InstrumentID, order.Direction, order.Offset, price, order.VolumeLeft, OrderType.Limit, pCustom=custom) stra.OnCancel(order) def OnTrade(self, t: CtpTrade, trade: TradeField): """""" order = self.t.orders[trade.OrderID] if not order.IsLocal: return self.cfg.log.info(trade) stra = self.get_stra(order) if stra is None: return stra.OnTrade(trade) def OnRtnErrOrder(self, t: CtpTrade, order: OrderField, info: InfoField): """""" if not order.IsLocal: return self.cfg.log.info(order) stra = self.get_stra(order) if stra is None: return stra.OnErrOrder(order, info) def q_OnFrontConnected(self, q: CtpQuote): """""" self.cfg.log.info("q:connected by client") self.q.ReqUserLogin(self.cfg.broker, self.cfg.investor, self.cfg.pwd) def q_OnRspUserLogin(self, q: CtpQuote, info: InfoField): """""" self.cfg.log.info(info) for stra in self.stra_instances: for data in stra.Datas: self.q.ReqSubscribeMarketData(data.Instrument) def q_Tick(self, q: CtpQuote, tick: Tick): """""" # print(tick) # self.fix_tick(tick) actionday = self.TradingDay if tick.UpdateTime[0:2] > '20': actionday = self.Actionday elif tick.UpdateTime[0:2] < '04': actionday = self.Actionday1 ut = actionday[0:4] + '-' + actionday[4:6] + '-' + actionday[ 6:] + ' ' + tick.UpdateTime tick.UpdateTime = ut for stra in self.stra_instances: for data in stra.Datas: if data.Instrument == tick.Instrument: data.on_tick(tick, self.TradingDay) self.tick_time = ut def get_actionday(self): # 接口未登录,不计算Actionday if self.TradingDay == '': return if self.cfg.engine_postgres: conn = self.cfg.engine_postgres.raw_connection() cursor = conn.cursor() cursor.execute( 'select _id from future_config.trade_date where trading = 1') self.trading_days = [c[0] for c in cursor.fetchall()] else: req = ReqPackage() req.Type = BarType.TradeDate self.trading_days = self.get_data_zmq(req) self.Actionday = self.TradingDay if self.trading_days.index( self.TradingDay) == 0 else self.trading_days[ self.trading_days.index(self.TradingDay) - 1] self.Actionday1 = (datetime.strptime(self.Actionday, '%Y%m%d') + timedelta(days=1)).strftime('%Y%m%d') def CTPRun(self): """""" if self.cfg.front_trade == '' or self.cfg.front_quote == '': self.cfg.log.war('交易接口未配置') return if self.cfg.investor == '': self.cfg.investor = input('invesorid on {}:'.format( self.cfg.front_name)) else: self.cfg.log.war('{} loging by ctp'.format(self.cfg.investor)) if self.cfg.pwd == '': self.cfg.pwd = getpass.getpass() self.t.OnConnected = self.OnFrontConnected self.t.OnDisConnected = lambda o, x: print('disconnected: {}'.format(x) ) self.t.OnUserLogin = self.OnRspUserLogin self.t.OnOrder = self.OnOrder self.t.OnTrade = self.OnTrade self.t.OnCancel = self.OnCancel self.t.OnErrOrder = self.OnRtnErrOrder self.t.OnInstrumentStatus = lambda x, y, z: str(z ) # print(z) 不再打印交易状态 self.t.ReqConnect(self.cfg.front_trade) while not self.q.logined: time.sleep(1)