def _gen_delisted_days(self, infos, limit_date): if not infos: return list() # 生成某只股票的汇总退市时间列表 # 将 infos 按照 change_date 进行排序 infos = sorted(infos, key=lambda item: item.get("change_date")) delisted_days = list() for j in range(len(infos) - 1): start = infos[j] end = infos[j + 1] # 分为4种情况 (1) True, True 数据错误 无暂停即恢复 # (2) True, False 上市时间 [ ) # (3) False, False 退市时间 [ ] # (4) False, True 退市时间 [ ) if start == end: logger.info("从上市至今未经历停牌.") return list() elif start.get("is_listed") and end.get("is_listed"): logger.info("数据错误,未经历退市即又上市") elif start.get( "is_listed") and not end.get("is_listed"): # True False # 将最后一天加入 退市时间 # delisted_days.append(end.get("change_date")) # 已有最后一天到截止时间的判断 pass elif not start.get("is_listed") and not end.get( "is_listed"): # False False delisted_days.extend( self.get_date_list(start.get("change_date"), end.get("change_date"))) elif not start.get("is_listed") and end.get( "is_listed"): # False True # 直到重新上市时间的前一天加入退市时间 delisted_days.extend( self.get_date_list( start.get("change_date"), end.get("change_date") - datetime.timedelta(days=1))) # 从最后一个时间到今天的判断 last_info = infos[-1] if last_info.get("is_listed"): pass else: # 将最后一个change_date 到今天的时间加到退市时间里面 delisted_days.extend( self.get_date_list(last_info.get("change_date"), limit_date)) # 去除重复值 # delisted_days = sorted(list(set(delisted_days))) return delisted_days
def gen_bulk(self, code, suspended, start, end): suspended = sorted(list(set(suspended))) bulk = list() dt = start if not suspended: logger.info("无 sus_bulk") for _date in self.get_date_list(dt, end): bulk.append({ "code": code, "date": _date, 'date_int': self.yyyymmdd_date(_date), "ok": True }) else: for d in suspended: # 对于其中的每一个停牌日 # 转换为 整点 模式, 为了 {datetime.datetime(1991, 4, 14, 1, 0)} 这样的数据的存在 d = datetime.datetime.combine(d.date(), datetime.time.min) while dt <= d: if dt < d: bulk.append({ "code": code, "date": dt, "date_int": self.yyyymmdd_date(dt), "ok": True }) # print(f"{yyyymmdd_date(dt)}: True") else: # 相等即为非交易日 bulk.append({ "code": code, "date": dt, "date_int": self.yyyymmdd_date(dt), "ok": False }) # print(f"{yyyymmdd_date(dt)}: False") dt += datetime.timedelta(days=1) # print(dt) # dt 此时已经是最后一个停牌日期 + 1 的状态了 # print(end) # dt > d: 已经跳出停牌日 在(停牌日+1) 到 截止时间 之间均为交易日 if dt <= end: for _date in self.get_date_list( suspended[-1] + datetime.timedelta(days=1), end): bulk.append({ "code": code, "date": _date, 'date_int': self.yyyymmdd_date(_date), "ok": True }) logger.info(f"{code} \n{bulk[0]} \n{bulk[-1]}") return bulk
def bulk_insert(self, mon, code, suspended, start, end): """ 按照顺序生成起止时间内的全部交易日历信息 :param mon: :param code: 前缀形式的股票代码 :param suspended: 非交易日 :param start: 开始时间 :param end: 结束时间 :return: """ # # 保险起见 将 suspend 去重排序 # suspended = sorted(list(set(suspended))) # bulk = list() # dt = start # # if not suspended: # logger.info("无 sus_bulk") # # for _date in self.get_date_list(dt, end): # bulk.append({"code": code, "date": _date, 'date_int': self.yyyymmdd_date(_date), "ok": True}) # # else: # for d in suspended: # 对于其中的每一个停牌日 # # 转换为 整点 模式, 为了 {datetime.datetime(1991, 4, 14, 1, 0)} 这样的数据的存在 # d = datetime.datetime.combine(d.date(), datetime.time.min) # # while dt <= d: # # if dt < d: # bulk.append({"code": code, "date": dt, "date_int": self.yyyymmdd_date(dt), "ok": True}) # # print(f"{yyyymmdd_date(dt)}: True") # # else: # 相等即为非交易日 # bulk.append({"code": code, "date": dt, "date_int": self.yyyymmdd_date(dt), "ok": False}) # # print(f"{yyyymmdd_date(dt)}: False") # # dt += datetime.timedelta(days=1) # # # print(dt) # dt 此时已经是最后一个停牌日期 + 1 的状态了 # # print(end) # # # dt > d: 已经跳出停牌日 在(停牌日+1) 到 截止时间 之间均为交易日 # if dt <= end: # for _date in self.get_date_list(suspended[-1] + datetime.timedelta(days=1), end): # bulk.append({"code": code, "date": _date, 'date_int': self.yyyymmdd_date(_date), "ok": True}) # logger.info(f"{code} \n{bulk[0]} \n{bulk[-1]}") bulk = self.gen_bulk(code, suspended, start, end) try: mon.insert_many(bulk) except Exception as e: logger.info(f"批量插入失败 {code}, 原因是 {e}")
def calendars_detection(self, ts1, ts2): """ 检查两个时间点之间的变动 :param ts1: :param ts2: :return: """ DATACENTER = self.DC2() DATACENTER.execute("use datacenter;") delisted_sql = f""" SELECT A.SecuCode from stk_liststatus B,const_secumainall A WHERE A.InnerCode=B.InnerCode AND A.SecuMarket IN(83,90) AND A.SecuCategory=1 AND B.ChangeType IN(1,2,3,4,5,6) and A.UPDATETIMEJZ > "{ts1}" and A.UPDATETIMEJZ <= "{ts2}" and B.UPDATETIMEJZ > "{ts1}" and B.UPDATETIMEJZ <= "{ts2}"; """ d_changed = list(DATACENTER.execute(delisted_sql)) if d_changed: d_changed = [j[0] for j in d_changed] logger.info(f"d_changed: {d_changed}") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - s_sql = f""" select SecuCode from stk_specialnotice_new where NoticeTypei = 18 and NoticeTypeii != 1703 and UPDATETIMEJZ > '{ts1}' and UPDATETIMEJZ <= '{ts2}' ;""" s_changed = list(DATACENTER.execute(s_sql)) if s_changed: s_changed = [j[0] for j in s_changed] logger.info(f"s_changed: {s_changed}") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - changed = set(d_changed + s_changed) logger.info(f"changed: {changed}") # changed 和 all_codes 相交的部分才有意义 changed = set(self.all_codes) & changed if changed: logger.info(f"有改动的数据是 {changed}") # check_and_update(changed, ts2) self.calendars_check(changed, ts2) else: logger.info(f"{ts1} 和 {ts2} 之间无更新")
def holiday_mark(self): """ mark 的主程序入口 :return: """ logger.info("开始检查更新节假日字段") s = datetime.datetime(2004, 1, 1) e = datetime.datetime(2020, 12, 30) self.add_holiday_field(s, e) self.check(s, e) # if __name__ == "__main__": # d = HolidaysMixin() # d.holiday_mark() # logger.info("holiday")
def check_market_calendar(self, mon, ts): """ 每日定时任务检查市场交易日历是否发生变化 :param ts: :return: """ exist = mon.find({"code": "SH000001"}).count() market_limit_date = MARKET_LIMIT_DATE start = self.market_first_day() sus, _ = self.gen_sh000001(start, market_limit_date, ts) sh0001_sus = sorted(list(set(sus))) if not exist: logger.info("market first sync.") self.bulk_insert(mon, "SH000001", sh0001_sus, start=start, end=market_limit_date) else: already_dates = mon.find({ "code": "SH000001", "ok": False }).distinct("date") int_already_dates = set( [self.yyyymmdd_date(date) for date in already_dates]) int_sh0001_sus = set([self.yyyymmdd_date(d) for d in sh0001_sus]) if int_sh0001_sus == int_already_dates: logger.info("无需更新 .") else: logger.info("市场交易日历需要重新插入.") # 记录了交易日历需要重新插入: 先删除原来的 再重新插入 mon.delete_many({"code": "SH000001"}) logger.info("原市场交易日历 SH000001 删除") self.bulk_insert(mon, "SH000001", sh0001_sus, start=start, end=market_limit_date) logger.info("新市场交易日历更新成功 ")
def calendars_check(self, codes, ts): logger.info(f"开始检查数据的一致性,本次检查的快照时间戳是 {ts}") # 检验的截止时间 limit_date = self.gen_next_date() # 拿到有记录的市场交易日的第一天 market_start = self.market_first_day() # 拿到所有的codes # codes = self.all_codes # codes_map = convert_front_map(codes) for code in codes: _, mysql_calendars_sus = self.gen_mysql_sus_info( code, market_start, limit_date, ts) f_code = self.convert_6code(code) calendars_mongo_sus = self.gen_calendars_mongo_sus(f_code) if calendars_mongo_sus == mysql_calendars_sus: logger.info("check right!") continue else: logger.warning("check wrong!") real_sus_dates = set(mysql_calendars_sus) - set( calendars_mongo_sus) logger.info(f"real_sus_dates: {real_sus_dates}") real_trading_dates = set(calendars_mongo_sus) - set( mysql_calendars_sus) logger.info(f"real_trading_dates: {real_trading_dates}") self.update_calendars(f_code, real_sus_dates, real_trading_dates) # mark 一遍 holiday 字段 # 只在 124 服务上添加该字段 if RUN_ENV == "124": self.holiday_mark()
def main(): task_5mins_run() task_day_run() sentry.captureMessage( f"现在是 {datetime.datetime.today()}, 开始增量 stock.calendars 交易日历 ") schedule.every().day.at("02:00").do(task_day_run) schedule.every(5).minutes.do(task_5mins_run) logger.info("开始检测更新了.. ") while True: logger.info(schedule.jobs) schedule.run_pending() time.sleep(300) logger.info("no work to do, waiting")
def gen_inc_code_sus(self, code, start, end, timestamp): """ start 和 end 之间的某只股票的全部停牌日 包含 strat 和 end :param code: :param start: :param end: :param timestamp: 进行本次查询的时间戳 :return: """ # 分为两种情况: # (1)在首次更新的时候,我们会在一段较长的 strat 和 end 之间寻找作为时间段存在的 notice_start 和 notice_end # (2) 在增量更新的时候我们往往会判断一个较短的时间段(start 到 end) 是否处于某个停牌期 # # 在第(1) 种情况下, 要想和一段较长的时间 start 和 end 有交集 # # 必须满足: # # notice_end> start and notice_start < end # # 其中 notice_end 可能为 NULL, 说明停牌到今天,此时就将停牌日设置为 end if start > end: return suspended = list() conn = self.DC() query_sql = f""" select id, NoticeStartDate, NoticeEndDate from stk_specialnotice_new where SecuCode = {code} and NoticeTypei = 18 and NoticeTypeii != 1703 and SuspendedType is not null and NoticeStartDate <= '{end}' and UPDATETIMEJZ <= '{timestamp}' -- and NoticeEndDate >= '{start}' ;""" # print(query_sql) try: with conn.cursor() as cursor: cursor.execute(query_sql) res = cursor.fetchall() if not res: return list() for column in res: notice_start = column[1] notice_end = column[2] # 对于数据存在以及合理性的判断 (1) 无 notice start 是错误数据 (2) 无 notice end 置为 end # (3) 开始不能大于 结束 if not notice_start: logger.info("no notice start date, wrong.") continue if not notice_end: notice_end = end if notice_end < notice_start: logger.info( f"notice_start > notice_end, something has been wrong. code is {code}" ) sys.exit(1) # check相交的情况: if notice_start == notice_end: # 四点为同一天 suspended.append(notice_start) elif notice_end <= end: # 停牌的起止完全包含于所给时间的情况 suspended.extend( self.get_date_list(notice_start, notice_end)) elif notice_start <= start: # 停牌日从 start 到 notice_end suspended.extend(self.get_date_list(start, notice_end)) elif notice_end >= end: # 停牌日从 notice_start 到 end suspended.extend(self.get_date_list(notice_start, end)) else: # 包含 NoticeEndDate >= '{start}' logger.info( f'未知情况, 未做处理. {code}, {notice_start}, {notice_end}' ) sys.exit(1) finally: conn.commit() suspended = sorted(list(set(suspended))) return suspended # if __name__ == "__main__": # d = SuspendDaysMixin() # ret = d.gen_inc_code_sus("000693", datetime.datetime(2005, 1, 1), datetime.datetime(2020, 1, 1), # datetime.datetime.now()) # print(ret) # logger.info("sus")
def gen_mysql_sus_info(self, code, start, end, ts): """ 在 ts 时间戳时效下 某只代码 从开始时间到结束时间的 从 mysql 数据库中计算的全部停牌日 :param code: 股票代码 原始数字格式 :param start: 开始时间【包括】 :param end: 结束时间 【包括】 :param ts: 时间戳 :return: """ logger.info("") logger.info("") logger.info(f"code: {code}") logger.info("市场停牌: ") market_sus, _ = self.gen_sh000001(start, end, ts) if market_sus: logger.info(f"market_sus_0: {market_sus[0]}") logger.info(f"market_sus_-1: {market_sus[-1]}") logger.info("个股停牌: ") code_sus = self.gen_inc_code_sus(code, start, end, ts) if code_sus: logger.info(f"code_sus_0: {code_sus[0]}") logger.info(f"code_sus_-1: {code_sus[-1]}") else: logger.info(f"{code} no suspended days") logger.info("个股退市: ") delisted = self.delisted_days(code, ts, end) if delisted: logger.info(f"delisted_0: {delisted[0]}") logger.info(f"delisted_-1: {delisted[-1]}") else: logger.info(f"{code} no delisted") _all_sus = set(market_sus + code_sus + delisted) mysql_all_sus = sorted(_all_sus) mysql_calendars_sus = sorted(_all_sus - set(market_sus)) return mysql_all_sus, mysql_calendars_sus