class TestQuote(object): """TestQuote""" def __init__(self): """""" self.q = CtpQuote() self.q.OnConnected = lambda x: self.q.ReqUserLogin( '008107', '1', '9999') self.q.OnUserLogin = lambda o, i: self.q.ReqSubscribeMarketData( 'rb1910') def run(self): self.q.ReqConnect('tcp://180.168.146.187:10010') def release(self): self.q.ReqUserLogout()
class TestQuote(object): """TestQuote""" def __init__(self, addr: str, broker: str, investor: str, pwd: str): """""" self.front = addr self.broker = broker self.investor = investor self.pwd = pwd self.q = CtpQuote() self.q.OnConnected = lambda x: self.q.ReqUserLogin(self.investor, self.pwd, self.broker) self.q.OnUserLogin = lambda o, i: self.q.ReqSubscribeMarketData('rb1910') def run(self): self.q.ReqConnect(self.front) def release(self): self.q.ReqUserLogout()
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)
class TickCtp(object): """""" def __init__(self): """初始化""" self.inst_mins = {} self.received_tick = [] self.trade_time = {} self.trading_days = [] self.TradingDay = '' self.Actionday = '' self.Actionday1 = '' # tick时间 self.tick_time = '' # 计算指数相关 self.product_time = {} self.product_rate = {} self.inst_queue = Queue(0) # 当前文件路径 self.cwd = os.path.dirname(os.path.realpath(__file__)) self.t = CtpTrade() self.q = CtpQuote() def OnFrontConnected(self, obj): cfg.log.war('t:connected') self.t.ReqUserLogin(cfg.investor, cfg.pwd, cfg.broker, '@haifeng', cfg.appid, cfg.authcode) def OnFrontDisConnected(self, obj: CtpTrade, reason: int): cfg.log.war('t:disconnectd:{}'.format(reason)) def OnRspUserLogin(self, obj: CtpTrade, info: InfoField): cfg.log.info('t:{}'.format(info)) if info.ErrorID == 0: self.TradingDay = obj.tradingday self.received_tick.clear() self.inst_mins.clear() threading.Thread(target=self.start_quote).start() def start_quote(self): """""" self.get_actionday() self.get_trading_time() # 品种交易时间==>合约交易时间 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'] # 隔夜时:行情重新登录后会重新获取夜盘的数据 # if len(self.inst_pre_vol) == 0: cfg.log.info(f'req connect quote front address: {cfg.front_quote}') self.q.OnConnected = self.q_OnFrontConnected self.q.OnDisconnected = self.q_OnDisConnected self.q.OnUserLogin = self.q_OnRspUserLogin self.q.OnTick = self.q_OnTick self.q.ReqConnect(cfg.front_quote) def q_OnFrontConnected(self, obj): cfg.log.war('q:connected') self.q.ReqUserLogin(cfg.investor, cfg.pwd, cfg.broker) def q_OnDisConnected(self, obj, nReason: int): cfg.log.war('q:disconnected') def q_OnRspUserLogin(self, obj, info): cfg.log.info('q:{}'.format(info)) for proc in [p.ProductID for p in self.t.instruments.values()]: self.inst_mins[proc + '_000'] = {'pre_vol': 0} for inst in [ i.InstrumentID for i in self.t.instruments.values() if i.ProductType != 'EFP' and i.ProductType != 'Combination' ]: self.inst_mins[inst] = {'pre_vol': 0} if cfg.rds: if cfg.rds.exists(inst): self.inst_mins[inst] = json.loads( cfg.rds.lindex(inst, -1).replace('\'', '"')) if 'pre_vol' not in self.inst_mins[inst]: self.inst_mins[inst]['pre_vol'] = 0 self.q.ReqSubscribeMarketData(inst) # self.q.ReqSubscribeMarketData('rb1901') cfg.log.info('sub count:{}'.format(len(self.inst_mins))) def q_OnTick(self, obj: CtpQuote, tick: Tick): # 某个合约报 most recent call last 错误: 尝试改为线程处理 if sys.float_info.max == tick.LastPrice: # or sys.float_info.max == tick.AskPrice or sys.float_info.max == tick.BidPrice or sys.float_info.max == tick.LowerLimitPrice: return threading.Thread(target=self.run_tick, args=(tick, )).start() # 非线程模式,导致数据延时 # self.run_tick(tick) def run_tick(self, 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']: ut = (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 ut = (datetime.strptime(ut, '%H:%M:%S') + timedelta(minutes=-1)).strftime('%H:%M:%S') else: return # 首tick不处理(新开盘时会收到之前的旧数据) if tick.Instrument not in self.received_tick: self.received_tick.append(tick.Instrument) return proc: InstrumentField = self.t.instruments.get(tick.Instrument) if not proc: return # 合约合成分钟 self.tick_min(tick, ut) # 指数合约合成分成 # pre_time = self.product_time.get(proc.ProductID) # if not pre_time: # self.product_time[proc.ProductID] = tick.UpdateTime # elif pre_time != tick.UpdateTime and tick.UpdateTime[-2:0] != '00': # 整分时等下一秒再处理,以处理小节收盘单tick的问题 # self.product_time[proc.ProductID] = tick.UpdateTime # # 计算合约权重 # ticks = [f for k, f in self.q.inst_tick.items() if self.t.instruments[k].ProductID == proc.ProductID] # sum_oi = sum([f.OpenInterest for f in ticks]) # if sum_oi == 0: # return # rate = json.loads('{{{}}}'.format(','.join(['"{}":{}'.format(f.Instrument, f.OpenInterest / sum_oi) for f in ticks]))) # # 计算000 # tick000: Tick = Tick() # tick000.Instrument = proc.ProductID + '_000' # tick000.UpdateTime = tick.UpdateTime # for inst, rate in rate.items(): # f: Tick = self.q.inst_tick[inst] # tick000.AskPrice += f.AskPrice * rate # tick000.BidPrice += f.BidPrice * rate # tick000.LastPrice += f.LastPrice * rate # tick000.AveragePrice += f.AveragePrice * rate # tick000.LowerLimitPrice += f.LowerLimitPrice * rate # tick000.UpperLimitPrice += f.UpperLimitPrice * rate # tick000.AskVolume += f.AskVolume # tick000.BidVolume += f.BidVolume # tick000.Volume += f.Volume # tick000.OpenInterest += f.OpenInterest # try: # # 防止因值为 sys.float_info.max 而报错: 只有lastprice参与分钟数据计算 # # tick000.AskPrice = round(tick000.AskPrice / proc.PriceTick) * proc.PriceTick # # tick000.BidPrice = round(tick000.BidPrice / proc.PriceTick) * proc.PriceTick # tick000.LastPrice = round(tick000.LastPrice / proc.PriceTick) * proc.PriceTick # # tick000.AveragePrice = round(tick000.AveragePrice / proc.PriceTick) * proc.PriceTick # # tick000.LowerLimitPrice = round(tick000.LowerLimitPrice / proc.PriceTick) * proc.PriceTick # # tick000.UpperLimitPrice = round(tick000.UpperLimitPrice / proc.PriceTick) * proc.PriceTick # except Exception as identifier: # cfg.log.error(str(identifier)) # # cfg.log.error('tick:{0}-{1},000:{2},rate:{3}'.format(tick.Instrument, tick.AskPrice, tick000.AskPrice, proc.PriceTick)) # self.tick_min(tick000, ut) def tick_min(self, tick: Tick, ut: str): actionday = self.TradingDay if ut[0:2] > '20': actionday = self.Actionday elif ut[0:2] < '04': actionday = self.Actionday1 # 分钟入库 cur_min = self.inst_mins[tick.Instrument] if '_id' not in cur_min or cur_min['_id'][11:] != ut: cur_min['_id'] = actionday[0:4] + '-' + actionday[ 4:6] + '-' + actionday[6:] + ' ' + ut cur_min['TradingDay'] = self.TradingDay cur_min['Low'] = cur_min['Close'] = cur_min['High'] = cur_min[ 'Open'] = tick.LastPrice cur_min['OpenInterest'] = tick.OpenInterest # cur_min['AveragePrice'] = tick.AveragePrice # 首个tick不计算成交量, 否则会导致隔夜的早盘第一个分钟的成交量非常大 cur_min['Volume'] = tick.Volume - cur_min['pre_vol'] cur_min['pre_vol'] = tick.Volume if cfg.rds: cfg.rds.rpush(tick.Instrument, json.dumps(cur_min)) else: cur_min['High'] = max(cur_min['High'], tick.LastPrice) cur_min['Low'] = min(cur_min['Low'], tick.LastPrice) cur_min['Close'] = tick.LastPrice cur_min['OpenInterest'] = tick.OpenInterest cur_min['Volume'] = tick.Volume - cur_min['pre_vol'] if cfg.rds: cfg.rds.lset(tick.Instrument, -1, json.dumps(cur_min)) self.tick_time = tick.UpdateTime def get_actionday(self): with open(os.path.join(self.cwd, 'calendar.csv')) as f: reader = csv.DictReader(f) for r in reader: if r['tra'] == 'false': continue self.trading_days.append(r['day']) # 接口未登录,不计算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 get_trading_time(self): self.trade_time.clear() tmp = {} # conn = cfg.pg.raw_connection() # cursor = conn.cursor() # cursor.execute('select "GroupId", "WorkingTimes" from (select "GroupId", "OpenDate", "WorkingTimes", row_number() over(partition by "GroupId" order by "OpenDate" desc) as row_no from future.tradingtime) a where row_no=1') # g = cursor.fetchall() with open(os.path.join(self.cwd, 'tradingtime.csv')) as f: reader = csv.DictReader(f) proc_day = {} for r in reader: # 按时间排序, 确保最后实施的时间段作为依据. if r['GroupId'] not in proc_day or r['OpenDate'] > proc_day[ r['GroupId']]: tmp[r['GroupId']] = r['WorkingTimes'] proc_day[r['GroupId']] = r['OpenDate'] # 根据时间段设置,生成 opens; ends; mins盘中时间 for g_id, section in tmp.items(): opens = [] ends = [] mins = [] for s in json.loads(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 } def run(self): """""" self.t.OnConnected = self.OnFrontConnected self.t.OnUserLogin = self.OnRspUserLogin self.t.OnDisConnected = self.OnFrontDisConnected self.t.OnInstrumentStatus = lambda x, y, z: str(z) cfg.log.info(f'req connect trade front address: {cfg.front_trade}') self.t.ReqConnect(cfg.front_trade) def run_seven(self): """7*24""" if cfg.investor != '': cfg.log.info('investor:' + cfg.investor) else: cfg.investor = input('investor:') if cfg.pwd == '': cfg.pwd = getpass.getpass() threading.Thread(target=self._run_seven).start() def _run_seven(self): print_time = '' 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.get_actionday() self.get_trading_time() 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: # 当前非交易日 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: sleep(1) else: cfg.log.info('{} is not tradingday.'.format(day)) cfg.log.info( 'continue after {}'.format(next_trading_day + ' 08:30:00')) sleep((datetime.strptime(next_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) elif now_time <= '083000': cfg.log.info('continue after {}'.format(day + ' 08:30:00')) sleep(( datetime.strptime(day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) elif now_time >= '153000': if has_hight: if datetime.now().strftime('%H%M%S') < '203000': cfg.log.info( 'continue after {}'.format(day + ' 20:30:00')) sleep((datetime.strptime(day + '20:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) else: cfg.log.info( 'continue after {}'.format(next_trading_day + ' 08:30:00')) sleep((datetime.strptime(next_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) self.run() 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: cfg.log.info('continue after {}'.format(day + ' 20:30:00')) sleep(( datetime.strptime(day + '20:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) else: cfg.log.info('continue after {}'.format(next_trading_day + ' 08:30:00')) sleep((datetime.strptime(next_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) # if cfg.mongo: # cfg.mongo.conn.drop_database('future_real_min') if cfg.rds: cfg.rds.flushdb() # 夜盘全部非交易 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] 周末时取值不对 cfg.log.info('continue after {}'.format(cur_trading_day + ' 08:30:00')) sleep((datetime.strptime(cur_trading_day + '08:31:00', '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds()) else: # 没有行情时不会显示 if print_time != self.tick_time: actionday = self.TradingDay if self.tick_time[0:2] > '20': actionday = self.Actionday elif self.tick_time[0:2] < '04': actionday = self.Actionday1 print_time = self.tick_time cfg.log.info('tick time:{} [diff]{}s'.format( print_time, (datetime.strptime(actionday + print_time, '%Y%m%d%H:%M:%S') - datetime.now()).total_seconds())) sleep(60)