Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
    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()
Beispiel #8
0
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")
Beispiel #9
0
    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