Beispiel #1
0
    async def save_bars(self,
                        sec: str,
                        bars: np.ndarray,
                        frame_type: FrameType,
                        sync_mode: int = 1):
        """
        为每条k线记录生成一个ID,将时间:id存入该sec对应的ordered set
        code:frame_type -> {
                20191204: [date, o, l, h, c, v]::json
                20191205: [date, o, l, h, c, v]::json
                head: date or datetime
                tail: date or datetime
                }
        :param sec: the full qualified code of a security or index
        :param bars: the data to save
        :param frame_type: use this to decide which store to use
        :param sync_mode: 1 for update, 2 for overwrite
        :return:
        """
        if bars is None or len(bars) == 0:
            return

        head, tail = await self.get_bars_range(sec, frame_type)

        if not (head and tail) or sync_mode == 2:
            await self._save_bars(sec, bars, frame_type)
            return

        if (tf.shift(bars["frame"][-1], 1, frame_type) < head
                or tf.shift(bars["frame"][0], -1, frame_type) > tail):
            # don't save to database, otherwise the data is not continuous
            logger.warning(
                "discrete bars found, code: %s, db(%s, %s), bars(%s,%s)",
                sec,
                head,
                tail,
                bars["frame"][0],
                bars["frame"][-1],
            )
            return

        # both head and tail exist, only save bars out of database's range
        bars_to_save = bars[(bars["frame"] < head) | (bars["frame"] > tail)]
        if len(bars_to_save) == 0:
            return

        await self._save_bars(
            sec,
            bars_to_save,
            frame_type,
            min(head, bars["frame"][0]),
            max(tail, bars["frame"][-1]),
        )
Beispiel #2
0
    async def _build_train_data(self, frame_type: FrameType,
                                n: int, max_error: float = 0.01):
        """
        从最近的符合条件的日期开始,遍历股票,提取特征和标签,生成数据集。
        Args:
            n: 需要采样的样本数

        Returns:

        """
        watch_win = 5
        max_curve_len = 5
        max_ma_win = 20

        # y_stop = arrow.get('2020-7-24').date()
        y_stop = tf.floor(arrow.now(tz=cfg.tz), frame_type)
        y_start = tf.shift(y_stop, -watch_win + 1, frame_type)
        x_stop = tf.shift(y_start, -1, frame_type)
        x_start = tf.shift(x_stop, -(max_curve_len + max_ma_win - 1), frame_type)
        data = []
        while len(data) < n:
            for code in Securities().choose(['stock']):
            #for code in ['000601.XSHE']:
                try:
                    sec = Security(code)
                    x_bars = await sec.load_bars(x_start, x_stop, FrameType.DAY)
                    y_bars = await sec.load_bars(y_start, y_stop, FrameType.DAY)
                    # [a, b, axis] * 3
                    x = self.extract_features(x_bars, max_error)
                    if len(x) == 0: continue
                    y = np.max(y_bars['close']) / x_bars[-1]['close'] - 1
                    if np.isnan(y): continue

                    feature = [code, tf.date2int(x_stop)]
                    feature.extend(x)
                    data.append(feature)
                except Exception as e:
                    logger.warning("Failed to extract features for %s (%s)",
                                   code,
                                   x_stop)
                    logger.exception(e)
                if len(data) >= n:
                    break
                if len(data) % 500 == 0:
                    logger.info("got %s records.", len(data))
            y_stop = tf.day_shift(y_stop, -1)
            y_start = tf.day_shift(y_start, -1)
            x_stop = tf.day_shift(y_start, -1)
            x_start = tf.day_shift(x_start, -1)

        return data
Beispiel #3
0
    async def copy(self,
                   code: str,
                   frame_type: Union[str, FrameType],
                   frame: Union[str, Frame],
                   ma_wins=None):
        frame_type = FrameType(frame_type)
        frame = arrow.get(frame, tzinfo=cfg.tz)
        ma_wins = ma_wins or [5, 10, 20]
        fit_win = 7

        stop = frame
        start = tf.shift(stop, -fit_win - max(ma_wins), frame_type)

        sec = Security(code)
        bars = await sec.load_bars(start, stop, frame_type)
        features = []
        for win in ma_wins:
            ma = signal.moving_average(bars['close'], win)
            if len(ma) < fit_win:
                raise ValueError(
                    f"{sec.display_name} doesn't have enough bars for "
                    f"extracting features")

            err, (a, b, c), (vx,
                             _) = signal.polyfit(ma[-fit_win:] / ma[-fit_win])
            features.append((err, (a, b, c), vx))

        return features
Beispiel #4
0
    async def evaluate(self,
                       code: str,
                       frame_type: FrameType,
                       dt: Frame = None):
        logger.debug("测试%s, 参数:%s %s", code, frame_type, dt)

        win = 10
        sec = Security(code)
        end = dt or arrow.now(tz=cfg.tz).datetime
        start = tf.shift(dt, -29, frame_type)
        bars = await sec.load_bars(start, end, frame_type)

        feat = features.ma_lines_trend(bars, [5, 10, 20])
        ma5, ma10 = feat["ma5"][0], feat["ma10"][0]

        # 判断是否存在vcross
        vcrossed, (idx0, idx1) = signal.vcross(ma5[-win:], ma10[-win:])
        if not vcrossed:
            return
        else:
            dt1, dt2 = bars[-win:][idx0]['frame'], bars[-win:][idx1]['frame']
            logger.info("%s vcross(5,10): %s, %s", sec, dt1, dt2)

        # 月线要向上且形成支撑作用
        err, a, b, vx, fit_win, war = feat["ma20"][1]
        if err < self.err_20 and war > self.war_20:
            logger.info("%s月线向上,war:%s", code, war)

        await emit.emit(Events.sig_long, {
            "code": code,
            "plot": self.name,
            "desc": f"{sec.display_name}发出老鸭头信号"
        })
Beispiel #5
0
    async def scan(self, stop: Frame = None):
        start = tf.shift(stop, -26, FrameType.WEEK)
        ERR = {5: 0.008, 10: 0.004, 20: 0.004}

        for code in Securities().choose(['stock']):
            #for code in ['002150.XSHE']:
            sec = Security(code)
            bars = await sec.load_bars(start, stop, FrameType.WEEK)
            if bars[-1]['frame'] != stop:
                raise ValueError("")

            t1, t2, t3 = False, False, False
            params = []
            for win in [5, 10, 20]:
                ma = signal.moving_average(bars['close'], win)
                err, (a, b, c), (vx, _) = signal.polyfit(ma[-7:] / ma[-7])
                if err > ERR[win]:
                    continue

                p = np.poly1d((a, b, c))
                slp3 = round(p(9) / p(6) - 1, 2)

                params.append(np.round([slp3, a, b], 4))
                if win == 5:
                    t1 = slp3 >= 0.03 and a > 0.005
                if win == 10:
                    t2 = slp3 >= 0.02 and (b > abs(10 * a) or a > 0.0005)
                if win == 20:
                    t3 = slp3 >= -1e-6 and a >= 0

            if all([t1, t2, t3]):
                print(sec.display_name, params)
Beispiel #6
0
    async def test_004_fq(self):
        """测试复权"""
        sec = Security("002320.XSHE")
        start = arrow.get("2020-05-06").date()
        stop = tf.shift(start, -249, FrameType.DAY)
        start, stop = stop, start
        # bars with no fq
        bars1 = await sec.load_bars(start, stop, FrameType.DAY, fq=False)
        bars2 = await sec.load_bars(start, stop, FrameType.DAY)

        self.assertEqual(250, len(bars1))
        expected1 = [
            [
                arrow.get("2019-04-24").date(),
                16.26,
                16.38,
                15.76,
                16.00,
                5981087.0,
                9.598480e07,
                3.846000,
            ],
            [
                arrow.get("2020-05-06").date(),
                10.94,
                11.22,
                10.90,
                11.15,
                22517883.0,
                2.488511e08,
                8.849346,
            ],
        ]
        expected2 = [
            [
                arrow.get("2019-04-24").date(),
                7.07,
                7.12,
                6.85,
                6.95,
                13762015.0,
                9.598480e07,
                3.846000,
            ],
            [
                arrow.get("2020-05-06").date(),
                10.94,
                11.22,
                10.90,
                11.15,
                22517883.0,
                2.488511e08,
                8.849346,
            ],
        ]
        self.assert_bars_equal(expected2, bars2)
        self.assert_bars_equal(expected1, bars1)
Beispiel #7
0
async def summary(code: str,
                  end: Frame,
                  frame: FrameType = FrameType.DAY,
                  win: int = 7):
    sec = Security(code)
    start = tf.shift(end, -(60 + 19), frame)
    bars = await sec.load_bars(start, end, frame)
    end_dt = bars['date'].iat[-1]

    ma5 = np.array(signal.moving_average(bars['close'], 5))
    ma10 = np.array(signal.moving_average(bars['close'], 10))
    ma20 = np.array(signal.moving_average(bars['close'], 20))
    ma60 = np.array(signal.moving_average(bars['close'], 60))

    _ma5, _ma10, _ma20 = ma5[-win:], ma10[-win:], ma20[-win:]

    xlen = 20
    ax_x = [i for i in range(xlen - win, xlen + 1)]  # 回归线的坐标
    # 5日均线及拟合点、预测点
    color = '#b08080'
    lw = 0.5
    err5, coef5, vertex5 = signal.polyfit(_ma5)
    vx, vy = vertex5
    vx = xlen - win + vx
    plt.plot(vx, vy * 0.995, "^", color=color)  # 5日低点

    plt.plot(ma5[-xlen:], color=color, linewidth=lw)  # 均线
    p5 = np.poly1d(coef5)
    y5 = [p5(i) for i in range(win + 1)]

    c0 = bars['close'].iat[-1]
    pred_c = y5[-1] * 5 - bars['close'][-4:].sum()

    plt.gcf().text(0.15, 0.85,
                   f"{code} {end_dt} ^{100 * (pred_c / c0 - 1):.02f}%")
    plt.plot(ax_x, y5, 'o', color=color, mew=0.25, ms=2.5)  # 回归均线

    # 10日均线及回归线
    color = '#00ff80'
    err10, coef10, vertex10 = signal.polyfit(_ma10)
    p10 = np.poly1d(coef10)
    y10 = [p10(i) for i in range(win + 1)]
    plt.plot(ma10[-xlen:], color=color, linewidth=lw)
    plt.plot(ax_x, y10, 'o', color=color, mew=0.25, ms=2.5)

    # 20日均线及回归线
    color = '#00ffff'
    err20, coef20, vertex20 = signal.polyfit(_ma20)
    p20 = np.poly1d(coef20)
    y20 = [p20(i) for i in range(win + 1)]
    plt.plot(ma20[-xlen:], color=color, linewidth=lw)
    plt.plot(ax_x, y20, 'o', color=color, mew=0.25, ms=2.5)

    # 60日均线
    color = "#2222ff"
    plt.plot(ma60[-xlen:], color=color, linewidth=lw)
Beispiel #8
0
    async def get_bars(self,
                       code: str,
                       n: int,
                       frame_type: FrameType,
                       end_dt: Frame = None):
        end_dt = end_dt or arrow.now(tz=cfg.tz)

        sec = Security(code)
        start = tf.shift(tf.floor(end_dt, frame_type), -n + 1, frame_type)
        return await sec.load_bars(start, end_dt, frame_type)
Beispiel #9
0
async def plot_ma(code: str,
                  groups=None,
                  end: Frame = None,
                  frame_type: FrameType = FrameType.DAY):
    groups = groups or [5, 10, 20, 60, 120]
    sec = Security(code)
    end = end or tf.floor(arrow.now(), frame_type)
    start = tf.shift(end, -(groups[-1] + 19), frame_type)
    bars = await sec.load_bars(start, end, frame_type)

    for win in groups:
        ma = signal.moving_average(bars['close'], win)
        plt.plot(ma[-20:])
Beispiel #10
0
 def test_shift_min1(self):
     X = [
         ("2020-03-26 09:31", 0, "2020-03-26 09:31"),
         ("2020-03-26 09:31", 1, "2020-03-26 09:32"),
         ("2020-03-26 11:30", 0, "2020-03-26 11:30"),
         ("2020-03-26 11:30", 1, "2020-03-26 13:01"),
         ("2020-03-26 11:30", 2, "2020-03-26 13:02"),
         ("2020-03-26 15:00", 0, "2020-03-26 15:00"),
         ("2020-03-26 15:00", 1, "2020-03-27 09:31"),
         ("2020-03-26 15:00", 241, "2020-03-30 09:31"),
     ]
     for i, (start, offset, expected) in enumerate(X):
         logger.debug("testing %s", X[i])
         actual = tf.shift(arrow.get(start, tzinfo=cfg.tz), offset, FrameType.MIN1)
         self.assertEqual(arrow.get(expected, tzinfo=cfg.tz).datetime, actual)
Beispiel #11
0
 def test_shift_min5(self):
     X = [
         ("2020-03-26 09:35", 0, "2020-03-26 09:35"),
         ("2020-03-26 09:35", 1, "2020-03-26 09:40"),
         ("2020-03-26 09:35", 2, "2020-03-26 09:45"),
         ("2020-03-26 11:30", 0, "2020-03-26 11:30"),
         ("2020-03-26 11:30", 1, "2020-03-26 13:05"),
         ("2020-03-26 11:30", 2, "2020-03-26 13:10"),
         ("2020-03-26 15:00", 0, "2020-03-26 15:00"),
         ("2020-03-26 15:00", 1, "2020-03-27 09:35"),
         ("2020-03-26 15:00", 49, "2020-03-30 09:35"),
     ]
     for i, (start, offset, expected) in enumerate(X):
         logger.info("testing %s", X[i])
         actual = tf.shift(arrow.get(start, tzinfo=cfg.tz), offset,
                           FrameType.MIN5)
         self.assertEqual(arrow.get(expected, tzinfo=cfg.tz), actual)
Beispiel #12
0
    async def evaluate(self,
                       code: str,
                       frame_type: FrameType,
                       dt: Frame = None,
                       win=15):
        sec = Security(code)
        end = dt or arrow.now(tz=cfg.tz)
        start = tf.shift(tf.floor(end, frame_type), -win - 20, frame_type)
        bars = await sec.load_bars(start, end, frame_type)

        if len(bars) < win + 20:
            return

        # 使用股价重心而不是收盘价来判断走势
        o, c = bars[-1]['open'], bars[-1]['close']

        feat = features.ma_lines_trend(bars, [5, 10, 20])
        ma5, ma10, ma20 = feat["ma5"][0], feat["ma10"][0], feat["ma20"][0]

        if np.any(np.isnan(ma5)):
            return

        mas = np.array([ma5[-1], ma10[-1], ma20[-1]])
        # 起涨点:一阳穿三线来确认
        if not (np.all(o <= mas) and np.all(c >= mas)):
            return

        # 三线粘合:三线距离小于self.max_distance
        distance = self.distance(ma5[:-1], ma10[:-1], ma20[:-1])
        if distance > self.max_distance:
            return

        # 月线要拉直,走平或者向上
        err, (a, b) = signal.polyfit((ma20 / ma20[0]), deg=1)
        if err > self.fiterr_ma20 and a < self.fitslp_ma20:
            return

        logger.info("%s",
                    f"{sec.display_name}\t{distance:.3f}\t{a:.3f}\t{err:.3f}")
        await self.fire("long",
                        code,
                        dt,
                        frame_type=frame_type.value,
                        distance=distance)
Beispiel #13
0
    def get_next_fire_time(
        self,
        previous_fire_time: Union[datetime.date, datetime.datetime],
        now: Union[datetime.date, datetime.datetime],
    ):
        ft = self.frame_type

        next_tick = now
        next_frame = tf.ceiling(now, ft)
        while next_tick <= now:
            if ft in tf.day_level_frames:
                next_tick = tf.combine_time(next_frame, 15) + self.jitter
            else:
                next_tick = next_frame + self.jitter

            if next_tick > now:
                return next_tick
            else:
                next_frame = tf.shift(next_frame, 1, ft)
Beispiel #14
0
    def test_shift_min15(self):
        X = [
            ["2020-03-26 09:45", 0, "2020-03-26 09:45"],
            ["2020-03-26 09:45", 5, "2020-03-26 11:00"],
            ["2020-03-26 09:45", 8, "2020-03-26 13:15"],
            ["2020-03-27 10:45", 14, "2020-03-30 10:15"],
            ["2020-03-26 13:15", -9, "2020-03-25 15:00"],
            ["2020-03-26 13:15", -18, "2020-03-25 11:15"],
            ["2020-03-26 13:15", -34, "2020-03-24 11:15"],
        ]

        fmt = "YYYY-MM-DD HH:mm"

        for i, (start, offset, expected) in enumerate(X):
            logger.debug("testing %s", X[i])
            actual = tf.shift(
                arrow.get(start, fmt, tzinfo=cfg.tz), offset, FrameType.MIN15
            )
            self.assertEqual(arrow.get(expected, fmt, tzinfo=cfg.tz).datetime, actual)
Beispiel #15
0
async def predict_ma(code: str,
                     frame_type: FrameType = FrameType.DAY,
                     end: Frame = None):
    """
    预测ma5、ma10、ma20的下一个数据
    Args:
        code:
        frame_type:
        end:

    Returns:

    """
    sec = Security(code)
    start = tf.shift(end, -29, frame_type)
    bars = await sec.load_bars(start, end, frame_type)

    c0 = bars['close'][-1]

    target = [c0 * (1 + f / 100) for f in range(-3, 3)]

    for c in target:
        close = np.append(bars['close'], c)
        ma5 = signal.moving_average(close, 5)
        ma10 = signal.moving_average(close, 10)
        ma20 = signal.moving_average(close, 20)

        fig = plt.figure()
        axes = plt.subplot(111)
        axes.plot([i for i in range(27)],
                  close[-27:],
                  color='#000000',
                  linewidth=0.5)  # len(ma5) == 27
        axes.plot(ma5)
        axes.plot([i for i in range(5, len(ma10) + 5)], ma10)
        axes.text(26, ma10[-1], f"{ma10[-1]:.2f}")
        axes.plot([i for i in range(15, len(ma20) + 15)], ma20)
        axes.text(26, c, f"{c:.2f}")
        axes.text(
            0.5, 3450,
            f"{100 * (c / c0 - 1):.2f}% {c:.2f} {ma5[-1]:.2f} {ma10[-1]:.2f}")
Beispiel #16
0
    async def fire_long(self,
                        end: Frame,
                        frame_type: FrameType.DAY,
                        win=60,
                        adv=0.03):
        secs = Securities()
        results = []
        for code in secs.choose(['stock']):
            #for code in ['601238.XSHG']:
            sec = Security(code)
            if sec.name.find("ST") != -1 or sec.code.startswith("688"):
                continue

            start = tf.shift(end, -win + 1, frame_type)
            bars = await sec.load_bars(start, end, frame_type)
            ilow = np.argmin(bars['low'])
            if ilow > win // 2:  #创新低及后面的反弹太近,信号不可靠
                continue

            low = bars['low'][ilow]
            last = bars['low'][-5:]
            if np.count_nonzero((last > low) & (last < low * 1.02)) < 3:
                # 对新低的测试不够
                continue

            c1, c0 = bars['close'][-2:]
            # 今天上涨幅度是否大于adv?
            if c0 / c1 - 1 < adv:
                continue
            # 是否站上5日线10日线?
            ma5 = signal.moving_average(bars['close'], 5)
            ma10 = signal.moving_average(bars['close'], 10)

            if c0 < ma5[-1] or c0 < ma10[-1]: continue

            price_change = await sec.price_change(end, tf.day_shift(end, 5),
                                                  frame_type)
            print(f"FIRED:{end}\t{code}\t{price_change:.2f}")
            results.append([end, code, price_change])

        return results
Beispiel #17
0
    async def copy(self,
                   code: str,
                   frame_type: FrameType,
                   end: Frame,
                   ma_win=5):
        fit_win = 7

        sec = Security(code)
        start = tf.shift(end, -(ma_win + fit_win), frame_type)
        bars = await sec.load_bars(start, end, frame_type)
        ma = signal.moving_average(bars['close'], ma_win)
        err, (a, b, c), (vx, _) = signal.polyfit(ma[-fit_win:] / ma[-fit_win])
        p = np.poly1d((a, b, c))
        slp3 = p(fit_win + 2) / p(fit_win - 1) - 1
        print(
            f"{sec.display_name}({code})\t{err:.4f}\t{a:.4f}\t{b:.4f}\t{vx:.1f}\
        \t{slp3:.2f}")
        self.ref_lines[f"ma{ma_win}"] = {
            "err": err,
            "coef": (a, b),
            "vx": vx,
            "slp3": slp3
        }
Beispiel #18
0
    async def evaluate(self, code: str, end: Frame):
        """
        最近穿越年线的股票,回归到[5,10,20]日均线时,如果均线形态良好,则提示买入
        Args:
            code:
            end:

        Returns:

        """
        start = tf.shift(tf.floor(end, FrameType.DAY), -26, FrameType.DAY)

        sec = Security(code)

        bars = await sec.load_bars(start, end, FrameType.DAY)
        close = bars['close']
        c0 = close[-1]

        # 检查接近5日均线,要求5日内强于均线,均线不能向下(或者拐头趋势)
        ma = signal.moving_average(close, 5)
        err, (a, b, c), (vx, _) = signal.polyfit(ma[-7:] / ma[-7])

        t1 = np.all(close[-5:] > ma[-5:])
        t2 = err < 3e-3
        t3 = a > 5e-4 or (abs(a) < 1e-5 and b > 1e-3)
        t4 = (c0 - ma[-1] / c0 < 5e-3)
        t5 = vx < 6

        logger.debug("%s 5日:%s, (a,b):%s,%s", sec, [t1, t2, t3, t4, t5], a, b)
        if all([t1, t2, t3, t4, t5]):
            logger.info("fired 5日买入:%s", sec)

            await emit.emit("/alpha/signals/long", {
                "plot":  "crossyear",
                "code":  code,
                "frame": str(end),
                "desc":  "回探5日线",
                "coef":  np.round([a, b], 4),
                "vx":    vx,
                "c":     c0,
                "ma":    ma[-5:]
            })
            return

        # 检查接近20日线买点
        ma = signal.moving_average(close, 20)
        err, (a, b, c), (vx, _) = signal.polyfit(ma[-10:] / ma[-10])

        t1 = err < 3e-3
        t2 = a > 5e-4 or (abs(a) < 1e-5 and b > 5e-3)
        t3 = (c0 - ma[-1]) < 5e-3
        t4 = vx < 9

        logger.debug("%s 20日:%s, (a,b):%s,%s", sec, [t1, t2, t3, t4], a, b)
        if all([t1, t2, t3, t4]):
            logger.info("fired 20日买入:%s", sec)
            await emit.emit("/alpha/signals/long", {
                "plot":  "crossyear",
                "code":  code,
                "frame": str(end),
                "desc":  "回探20日线",
                "coef":  np.round([a, b], 4),
                "vx":    vx,
                "c":     c0,
                "ma":    ma[-5:]
            })
            return

        # 检查是否存在30分钟买点
        start = tf.shift(tf.floor(end, FrameType.MIN30), -30, FrameType.MIN30)
        bars = await sec.load_bars(start, end, FrameType.MIN30)

        close = bars['close']
        ma = signal.moving_average(close, 5)
        err, (a, b, c), (vx, _) = signal.polyfit(ma[-7:] / ma[-7])
        t1 = err < 3e-3
        t2 = a > 5e-4 or (abs(a) < 1e-5 and b > 1e-2)
        t3 = vx < 6

        logger.debug("%s 30分钟:%s, (a,b)", sec, [t1, t2, t3, t4, t5], a, b)
        if all([t1, t2, t3]):
            logger.info("fired 30分钟买入:%s", sec)
            await emit.emit("/alpha/signals/long", {
                "plot":  "crossyear",
                "code":  code,
                "frame": str(end),
                "desc":  "30分钟买点",
                "vx":    vx,
                "c":     c0,
                "ma":    ma[-5:]
            })
Beispiel #19
0
    async def save_bars(self,
                        sec: str,
                        bars: np.ndarray,
                        frame_type: FrameType,
                        sync_mode: int = 1):
        """将行情数据存入缓存

        在redis cache中的数据以如下方式存储

        ```text
        "000001.XSHE:30m" -> {
            # frame     open    low  high  close volume      amount        factor
            "20200805": "13.82 13.85 13.62 13.76 144020313.0 1980352978.34 120.77"
            "20200806": "13.82 13.96 13.65 13.90 135251068.0 1868047342.49 120.77"
            "head": "20200805"
            "tail": "20200806"
        }
        ```

        这里的amount即对应frame的成交额;factor为复权因子

        args:
            sec: the full qualified code of a security or index
            bars: the data to save
            frame_type: use this to decide which store to use
            sync_mode: 1 for update, 2 for overwrite
        """
        if bars is None or len(bars) == 0:
            return

        head, tail = await self.get_bars_range(sec, frame_type)

        if not (head and tail) or sync_mode == 2:
            await self._save_bars(sec, bars, frame_type)
            return

        if (tf.shift(bars["frame"][-1], 1, frame_type) < head
                or tf.shift(bars["frame"][0], -1, frame_type) > tail):
            # don't save to database, otherwise the data is not continuous
            logger.warning(
                "discrete bars found, code: %s, db(%s, %s), bars(%s,%s)",
                sec,
                head,
                tail,
                bars["frame"][0],
                bars["frame"][-1],
            )
            return

        # both head and tail exist, only save bars out of database's range
        bars_to_save = bars[(bars["frame"] < head) | (bars["frame"] > tail)]
        if len(bars_to_save) == 0:
            return

        await self._save_bars(
            sec,
            bars_to_save,
            frame_type,
            min(head, bars["frame"][0]),
            max(tail, bars["frame"][-1]),
        )
Beispiel #20
0
async def sync_bars_for_security(
    code: str,
    frame_type: FrameType,
    start: Union[datetime.date, datetime.datetime],
    stop: Union[None, datetime.date, datetime.datetime],
):
    counters = 0

    # 取数据库中该frame_type下该code的k线起始点
    head, tail = await cache.get_bars_range(code, frame_type)
    if not all([head, tail]):
        await cache.clear_bars_range(code, frame_type)
        n_bars = tf.count_frames(start, stop, frame_type)

        bars = await aq.get_bars(code, stop, n_bars, frame_type)
        if bars is not None and len(bars):
            logger.debug(
                "sync %s(%s), from %s to %s: actual got %s ~ %s (%s)",
                code,
                frame_type,
                start,
                head,
                bars[0]["frame"],
                bars[-1]["frame"],
                len(bars),
            )
            counters = len(bars)
        return

    if start < head:
        n = tf.count_frames(start, head, frame_type) - 1
        if n > 0:
            _end_at = tf.shift(head, -1, frame_type)
            bars = await aq.get_bars(code, _end_at, n, frame_type)
            if bars is not None and len(bars):
                counters += len(bars)
                logger.debug(
                    "sync %s(%s), from %s to %s: actual got %s ~ %s (%s)",
                    code,
                    frame_type,
                    start,
                    head,
                    bars[0]["frame"],
                    bars[-1]["frame"],
                    len(bars),
                )
                if bars["frame"][-1] != _end_at:
                    logger.warning(
                        "discrete frames found:%s, bars[-1](%s), "
                        "head(%s)",
                        code,
                        bars["frame"][-1],
                        head,
                    )

    if stop > tail:
        n = tf.count_frames(tail, stop, frame_type) - 1
        if n > 0:
            bars = await aq.get_bars(code, stop, n, frame_type)
            if bars is not None and len(bars):
                logger.debug(
                    "sync %s(%s), from %s to %s: actual got %s ~ %s (%s)",
                    code,
                    frame_type,
                    tail,
                    stop,
                    bars[0]["frame"],
                    bars[-1]["frame"],
                    len(bars),
                )
                counters += len(bars)
                if bars["frame"][0] != tf.shift(tail, 1, frame_type):
                    logger.warning(
                        "discrete frames found: %s, tail(%s), bars[0]("
                        "%s)",
                        code,
                        tail,
                        bars["frame"][0],
                    )
Beispiel #21
0
    def test_shift(self):
        mom = arrow.get("2020-1-20")

        self.assertEqual(tf.shift(mom, 1, FrameType.DAY), tf.day_shift(mom, 1))
        self.assertEqual(tf.shift(mom, 1, FrameType.WEEK), tf.week_shift(mom, 1))
        self.assertEqual(tf.shift(mom, 1, FrameType.MONTH), tf.month_shift(mom, 1))
Beispiel #22
0
    async def visualize(self, code: Union[str, List[str]], frame: Union[str,
                                                                        Frame],
                        frame_type: Union[str, FrameType]):
        """
        将code列表中的股票的动量特征图象化
        Args:
            code:
            frame:
            frame_type:

        Returns:

        """
        import matplotlib.pyplot as plt

        if isinstance(code, str):
            code = [code]

        col = 4
        row = len(code) // col + 1
        plt.figure(figsize=(5 * row * col, 7))
        plt.subplots_adjust(wspace=0.2, hspace=0.2)

        fit_win = 7
        colors = {"5": '#808080', "10": '#00cc80', "20": '#00ccff'}

        frame = arrow.get(frame)
        frame_type = FrameType(frame_type)
        for i, code in enumerate(code):
            _code = code.split(".")[0]
            start = tf.shift(frame, -25, frame_type)
            bars = await Security(code).load_bars(start, frame, frame_type)

            plt.subplot(len(code) // col + 1, col, i + 1)
            y_lim = 0
            text = ""
            for win in [5, 10, 20]:
                ma = signal.moving_average(bars['close'], win)
                _ma = ma[-fit_win:]

                plt.plot(_ma, color=colors[f"{win}"])

                err, (a, b, c), (vx, _) = signal.polyfit(_ma / _ma[0])
                p = np.poly1d((a * _ma[0], b * _ma[0], c * _ma[0]))
                y = p(fit_win + 2) / p(fit_win - 1) - 1

                y_lim = max(y_lim, np.max(_ma))
                if win == 5:
                    text = f"{_code} a:{a:.4f} b:{b:.4f} vx:{vx:.1f} y:{y:.2f}"

                if err < self.baseline(f"ma{win}:{frame_type.value}:err"):
                    # 如果拟合在误差范围内,则画出拟合线
                    plt.plot([p(i) for i in range(len(_ma))],
                             "--",
                             color=colors[f"{win}"])
                    plt.plot([p(i) for i in range(len(_ma))],
                             "o",
                             color=colors[f"{win}"])

                    if 0 < vx < fit_win:
                        plt.plot([vx], p(vx), 'x')

            plt.plot(0, y_lim * 1.035)
            plt.text(0.1, y_lim * 1.02, text, color='r')
Beispiel #23
0
async def sync_bars_for_security(
    code: str,
    frame_type: FrameType,
    start: Union[datetime.date, datetime.datetime],
    stop: Union[None, datetime.date, datetime.datetime],
):
    counters = 0
    logger.info("syncing quotes for %s", code)

    # 取数据库中该frame_type下该code的k线起始点
    head, tail = await cache.get_bars_range(code, frame_type)
    if not all([head, tail]):
        await cache.clear_bars_range(code, frame_type)
        n_bars = tf.count_frames(start, stop, frame_type)
        bars = await aq.get_bars(code, stop, n_bars, frame_type)
        counters = len(bars)
        logger.info("finished sync %s(%s), %s bars synced", code, frame_type,
                    counters)
        return

    if start < head:
        n = tf.count_frames(start, head, frame_type) - 1
        if n > 0:
            _end_at = tf.shift(head, -1, frame_type)
            bars = await aq.get_bars(code, _end_at, n, frame_type)
            counters += len(bars)
            logger.debug(
                "sync %s level bars of %s to %s: expected: %s, actual %s",
                frame_type,
                code,
                _end_at,
                n,
                len(bars),
            )
            if len(bars) and bars["frame"][-1] != _end_at:
                logger.warning(
                    "discrete frames found:%s, bars[-1](%s), "
                    "head(%s)",
                    code,
                    bars["frame"][-1],
                    head,
                )

    if stop > tail:
        n = tf.count_frames(tail, stop, frame_type) - 1
        if n > 0:
            bars = await aq.get_bars(code, stop, n, frame_type)
            logger.debug(
                "sync %s level bars of %s to %s: expected: %s, actual %s",
                frame_type,
                code,
                stop,
                n,
                len(bars),
            )
            counters += len(bars)
            if bars["frame"][0] != tf.shift(tail, 1, frame_type):
                logger.warning(
                    "discrete frames found: %s, tail(%s), bars[0]("
                    "%s)",
                    code,
                    tail,
                    bars["frame"][0],
                )

    logger.info("finished sync %s(%s), %s bars synced", code, frame_type,
                counters)
Beispiel #24
0
    async def list_stock_pool(self,
                              frames: int,
                              frame_types: List[FrameType] = None):
        key = "plots.momentum.pool"

        recs = await cache.sys.hgetall(key)
        items = []
        now = arrow.now()
        for k, v in recs.items():
            frame, code = k.split(":")

            sec = Security(code)
            v = json.loads(v)

            frame_type = FrameType(v.get("frame_type"))

            if frame_type not in frame_types:
                continue

            latest_frame = tf.floor(now, frame_type)
            start = tf.shift(latest_frame, -frames, frame_type)

            fired = tf.int2time(frame) if frame_type in tf.minute_level_frames else \
                tf.int2date(frame)

            if fired < start:
                continue

            items.append({
                "name": sec.display_name,
                "code": code,
                "fired": str(fired),
                "frame": frame_type.value,
                "y": round(v.get("y"), 2),
                "vx": round(v.get("vx"), 1),
                "a": round(v.get("a"), 4),
                "b": round(v.get("b"), 4),
                "err": round(v.get("err"), 4)
            })

        return {
            "name":
            self.display_name,
            "plot":
            self.name,
            "items":
            items,
            "headers": [{
                "text": '名称',
                "value": 'name'
            }, {
                "text": '代码',
                "value": 'code'
            }, {
                "text": '信号时间',
                "value": 'fired'
            }, {
                "text": '预测涨幅',
                "value": 'y'
            }, {
                "text": '动能',
                "value": 'a'
            }, {
                "text": '势能',
                "value": 'b'
            }, {
                "text": '周期',
                "value": 'frame'
            }, {
                "text": '底部距离(周期)',
                "value": 'vx'
            }, {
                "text": '拟合误差',
                "value": 'err'
            }]
        }
Beispiel #25
0
    async def load_bars_batch(cls, codes: List[str], end: Frame, n: int,
                              frame_type: FrameType) -> AsyncIterator:
        """为一批证券品种加载行情数据

        examples:
        ```
        codes = ["000001.XSHE", "000001.XSHG"]

        end = arrow.get("2020-08-27").datetime
        async for code, bars in Security.load_bars_batch(codes, end, 5, FrameType.DAY):
            print(code, bars[-2:])
            self.assertEqual(5, len(bars))
            self.assertEqual(bars[-1]["frame"], end.date())
            if code == "000001.XSHG":
                self.assertAlmostEqual(3350.11, bars[-1]["close"], places=2)
        ```

        Args:
            codes : 证券列表
            end : 结束帧
            n : 周期数
            frame_type : 帧类型

        Returns:
            [description]

        Yields:
            [description]
        """
        assert type(end) in (datetime.date, datetime.datetime)
        closed_frame = tf.floor(end, frame_type)

        if end == closed_frame:
            start = tf.shift(closed_frame, -n + 1, frame_type)

            cached = [
                asyncio.create_task(
                    cls._get_bars(code, start, closed_frame, frame_type))
                for code in codes
            ]
            for fut in asyncio.as_completed(cached):
                rec = await fut
                yield rec
        else:
            start = tf.shift(closed_frame, -n + 2, frame_type)

            cached = [
                asyncio.create_task(
                    cls._get_bars(code, start, closed_frame, frame_type))
                for code in codes
            ]
            recs1 = await asyncio.gather(*cached)
            recs2 = await cls._load_bars_batch(codes, end, 1, frame_type)

            for code, bars in recs1:
                _bars = recs2.get(code)
                if _bars is None or len(_bars) != 1:
                    logger.warning("wrong/emtpy records for %s", code)
                    continue

                yield code, np.append(bars, _bars)
Beispiel #26
0
    async def load_bars(
        self,
        start: Frame,
        stop: datetime.datetime,
        frame_type: FrameType,
        fq=True,
        turnover=False,
    ) -> np.ndarray:
        """
        加载[`start`, `stop`]间的行情数据到`Security`对象中,并返回行情数据。

        这里`start`可以等于`stop`。

        为加快速度,对分钟级别的turnover数据,均使用当前周期的成交量除以最新报告期的流通股本数,
        注意这样得到的是一个近似值。如果近期有解禁股,则同样的成交量,解禁后的换手率应该小于解
        禁前。
        Args:
            start:
            stop:
            frame_type:
            fq: 是否进行复权处理
            turnover: 是否包含turnover数据。

        Returns:

        """
        self._bars = None
        start = tf.floor(start, frame_type)
        _stop = tf.floor(stop, frame_type)

        assert start <= _stop
        head, tail = await cache.get_bars_range(self.code, frame_type)

        if not all([head, tail]):
            # not cached at all, ensure cache pointers are clear
            await cache.clear_bars_range(self.code, frame_type)

            n = tf.count_frames(start, _stop, frame_type)
            if stop > _stop:
                self._bars = await get_bars(self.code, stop, n + 1, frame_type)
            else:
                self._bars = await get_bars(self.code, _stop, n, frame_type)

            if fq:
                self.qfq()

            if turnover:
                await self._add_turnover(frame_type)

            return self._bars

        if start < head:
            n = tf.count_frames(start, head, frame_type)
            if n > 0:
                _end = tf.shift(head, -1, frame_type)
                self._bars = await get_bars(self.code, _end, n, frame_type)

        if _stop > tail:
            n = tf.count_frames(tail, _stop, frame_type)
            if n > 0:
                await get_bars(self.code, _stop, n, frame_type)

        # now all closed bars in [start, _stop] should exist in cache
        n = tf.count_frames(start, _stop, frame_type)
        self._bars = await cache.get_bars(self.code, _stop, n, frame_type)

        if arrow.get(stop) > arrow.get(_stop):
            bars = await get_bars(self.code, stop, 2, frame_type)
            if len(bars) == 2 and bars[0]["frame"] == self._bars[-1]["frame"]:
                self._bars = np.append(self._bars, bars[1])

        if fq:
            self.qfq()

        if turnover:
            await self._add_turnover(frame_type)

        return self._bars
Beispiel #27
0
    async def scan(self,
                   frame_type: Union[str, FrameType] = FrameType.DAY,
                   end: Frame = None,
                   codes: List[str] = None):
        logger.info("running momentum scan at %s level", frame_type)
        if end is None:
            end = arrow.now(cfg.tz).datetime

        assert type(end) in (datetime.date, datetime.datetime)

        frame_type = FrameType(frame_type)
        ft = frame_type.value
        codes = codes or Securities().choose(['stock'])
        day_bars = {}
        async for code, bars in Security.load_bars_batch(
                codes, end, 2, FrameType.DAY):
            day_bars[code] = bars

        if len(day_bars) == 0:
            return

        async for code, bars in Security.load_bars_batch(
                codes, end, 11, frame_type):
            if len(bars) < 11:
                continue

            fired = bars[-1]['frame']
            day_bar = day_bars.get(code)
            if day_bar is None:
                continue

            c1, c0 = day_bars.get(code)[-2:]['close']
            cmin = min(bars['close'])

            # 还处在下跌状态、或者涨太多
            if c0 == cmin or (c0 / c1 - 1) > self.baseline(f"up_limit"):
                continue

            ma5 = signal.moving_average(bars['close'], 5)

            err, (a, b, c), (vx, _) = signal.polyfit(ma5[-7:] / ma5[-7])
            # 无法拟合,或者动能不足
            if err > self.baseline(f"ma5:{ft}:err") or a < self.baseline(
                    f"ma5:{ft}:a"):
                continue

            # 时间周期上应该是信号刚出现,还在窗口期内
            vx_range = self.baseline(f"ma5:{ft}:vx")
            if not vx_range[0] < vx < vx_range[1]:
                continue

            p = np.poly1d((a, b, c))
            y = p(9) / p(6) - 1
            # 如果预测未来三周期ma5上涨幅度不够
            if y < self.baseline(f"ma5:{ft}:y"):
                continue

            sec = Security(code)

            if frame_type == FrameType.DAY:
                start = tf.shift(tf.floor(end, frame_type), -249, frame_type)
                bars250 = await sec.load_bars(start, end, frame_type)
                ma60 = signal.moving_average(bars250['close'], 60)
                ma120 = signal.moving_average(bars250['close'], 120)
                ma250 = signal.moving_average(bars250['close'], 250)

                # 上方无均线压制
                if (c0 > ma60[-1]) and (c0 > ma120[-1]) and (c0 > ma250[-1]):
                    logger.info("%s, %s, %s, %s, %s, %s", sec, round(a, 4),
                                round(b, 4), round(vx, 1),
                                round(c0 / c1 - 1, 3), round(y, 3))
                    await self.enter_stock_pool(code,
                                                fired,
                                                frame_type,
                                                a=a,
                                                b=b,
                                                err=err,
                                                y=y,
                                                vx=self.fit_win - vx)
            elif frame_type == FrameType.WEEK:
                await self.enter_stock_pool(code,
                                            fired,
                                            frame_type,
                                            a=a,
                                            b=b,
                                            err=err,
                                            y=y,
                                            vx=self.fit_win - vx)
            elif frame_type == FrameType.MIN30:
                await self.fire_trade_signal('long',
                                             code,
                                             fired,
                                             frame_type,
                                             a=a,
                                             b=b,
                                             err=err,
                                             y=y,
                                             vx=self.fit_win - vx)
Beispiel #28
0
def parse_sync_params(
    frame: Union[str, Frame],
    cat: List[str] = None,
    start: Union[str, datetime.date] = None,
    stop: Union[str, Frame] = None,
    delay: int = 0,
    include: str = "",
    exclude: str = "",
) -> Tuple:
    """按照[使用手册](usage.md#22-如何同步K线数据)中的规则,解析和补全同步参数。

    如果`frame_type`为分钟级,则当`start`指定为`date`类型时,自动更正为对应交易日的起始帧;
    当`stop`为`date`类型时,自动更正为对应交易日的最后一帧。
    Args:
        frame (Union[str, Frame]): frame type to be sync.  The word ``frame`` is used
            here for easy understand by end user. It actually implies "FrameType".
        cat (List[str]): which catetories is about to be synced. Should be one of
            ['stock', 'index']. Defaults to None.
        start (Union[str, datetime.date], optional): [description]. Defaults to None.
        stop (Union[str, Frame], optional): [description]. Defaults to None.
        delay (int, optional): [description]. Defaults to 5.
        include (str, optional): which securities should be included, seperated by
            space, for example, "000001.XSHE 000004.XSHE". Defaults to empty string.
        exclude (str, optional):  which securities should be excluded, seperated by
            a space. Defaults to empty string.

    Returns:
        - codes (List[str]): 待同步证券列表
        - frame_type (FrameType):
        - start (Frame):
        - stop (Frame):
        - delay (int):
    """
    frame_type = FrameType(frame)

    if frame_type in tf.minute_level_frames:
        if stop:
            stop = arrow.get(stop, tzinfo=cfg.tz)
            if stop.hour == 0:  # 未指定有效的时间帧,使用当日结束帧
                stop = tf.last_min_frame(tf.day_shift(stop.date(), 0),
                                         frame_type)
            else:
                stop = tf.floor(stop, frame_type)
        else:
            stop = tf.floor(arrow.now(tz=cfg.tz).datetime, frame_type)

        if stop > arrow.now(tz=cfg.tz):
            raise ValueError(f"请勿将同步截止时间设置在未来: {stop}")

        if start:
            start = arrow.get(start, tzinfo=cfg.tz)
            if start.hour == 0:  # 未指定有效的交易帧,使用当日的起始帧
                start = tf.first_min_frame(tf.day_shift(start.date(), 0),
                                           frame_type)
            else:
                start = tf.floor(start, frame_type)
        else:
            start = tf.shift(stop, -999, frame_type)
    else:
        stop = (stop and arrow.get(stop).date()) or arrow.now().date()
        if stop == arrow.now().date():
            stop = arrow.now(tz=cfg.tz)

        stop = tf.floor(stop, frame_type)
        start = tf.floor(
            (start and arrow.get(start).date()), frame_type) or tf.shift(
                stop, -1000, frame_type)

    secs = Securities()
    codes = secs.choose(cat or [])

    exclude = map(lambda x: x, exclude.split(" "))
    codes = list(set(codes) - set(exclude))

    include = list(filter(lambda x: x, include.split(" ")))
    codes.extend(include)

    return codes, frame_type, start, stop, int(delay)
Beispiel #29
0
    async def test_002_load_bars(self):
        sec = Security("000001.XSHE")
        start = arrow.get("2020-01-03").date()
        stop = arrow.get("2020-1-16").date()
        frame_type = FrameType.DAY

        expected = [
            [
                arrow.get("2020-01-03").date(),
                16.94,
                17.31,
                16.92,
                17.18,
                1.11619481e8,
                1914495474.63,
                118.73,
            ],
            [
                stop, 16.52, 16.57, 16.2, 16.33, 1.02810467e8, 1678888507.83,
                118.73
            ],
        ]

        logger.info("scenario: no cache")
        await cache.clear_bars_range(sec.code, frame_type)
        bars = await sec.load_bars(start, start, frame_type)
        self.assert_bars_equal([expected[0]], bars)

        bars = await sec.load_bars(start, stop, frame_type)
        self.assert_bars_equal(expected, bars)

        logger.info("scenario: load from cache")
        bars = await sec.load_bars(start, stop, frame_type)

        self.assert_bars_equal(expected, bars)

        logger.info("scenario: partial data fetch: head")
        await cache.set_bars_range(sec.code,
                                   frame_type,
                                   start=arrow.get("2020-01-07").date())
        bars = await sec.load_bars(start, stop, frame_type)

        self.assert_bars_equal(expected, bars)

        logger.info("scenario: partial data fetch: tail")
        await cache.set_bars_range(sec.code,
                                   frame_type,
                                   end=arrow.get("2020-01-14").date())
        bars = await sec.load_bars(start, stop, frame_type)

        self.assert_bars_equal(expected, bars)

        logger.info("scenario: 1min level backward")
        frame_type = FrameType.MIN1
        start = arrow.get("2020-05-06 15:00:00", tzinfo=cfg.tz).datetime
        await cache.clear_bars_range(sec.code, frame_type)
        stop = tf.shift(start, -249, frame_type)
        start, stop = stop, start
        bars = await sec.load_bars(start, stop, frame_type)
        # fmt:off
        expected = [[
            arrow.get('2020-04-30 14:51:00', tzinfo=cfg.tz).datetime, 13.99,
            14., 13.98, 13.99, 281000., 3931001., 118.725646
        ],
                    [
                        arrow.get('2020-05-06 15:00:00',
                                  tzinfo=cfg.tz).datetime, 13.77, 13.77, 13.77,
                        13.77, 1383400.0, 19049211.45000005, 118.725646
                    ]]
        # fmt:on
        self.assert_bars_equal(expected, bars)

        logger.info("scenario: 30 min level")
        frame_type = FrameType.MIN15
        start = arrow.get("2020-05-06 10:15:00", tzinfo=cfg.tz).datetime
        await cache.clear_bars_range(sec.code, frame_type)
        stop = arrow.get("2020-05-06 15:00:00", tzinfo=cfg.tz).datetime
        bars = await sec.load_bars(start, stop, frame_type)
        # fmt: off
        expected = [[
            arrow.get('2020-05-06 10:15:00', tzinfo=cfg.tz).datetime, 13.67,
            13.74, 13.66, 13.72, 8341905., 1.14258451e+08, 118.725646
        ],
                    [
                        arrow.get('2020-05-06 15:00:00',
                                  tzinfo=cfg.tz).datetime, 13.72, 13.77, 13.72,
                        13.77, 7053085., 97026350.76999998, 118.725646
                    ]]
        # fmt: on
        self.assert_bars_equal(expected, bars)
Beispiel #30
0
    async def fire_long(self,
                        end: Frame = None,
                        overlap_win=10,
                        frame_type: FrameType = FrameType.MIN30):
        """
        寻找开多仓信号
        Args:

        Returns:

        """
        result = []
        end = end or arrow.now().datetime
        secs = Securities()
        for code in secs.choose(['stock']):
            #for code in ['600139.XSHG']:
            try:
                sec = Security(code)
                start = tf.shift(end, -(60 + overlap_win - 1), frame_type)
                bars = await sec.load_bars(start, end, frame_type)

                mas = {}
                for win in [5, 10, 20, 60]:
                    ma = signal.moving_average(bars['close'], win)
                    mas[f"{win}"] = ma

                # 收盘价高于各均线值
                c1, c0 = bars['close'][-2:]
                t1 = c0 > mas["5"][-1] and c0 > mas["10"][-1] and c0 > mas["20"][-1] \
                     and c0 > mas["60"][-1]

                # 60均线斜率向上
                slope_60, err = signal.slope(mas["60"][-10:])
                if err is None or err > 5e-4:
                    continue

                t2 = slope_60 >= 5e-4

                # 均线粘合
                diff = np.abs(mas["5"][-6:-1] -
                              mas["10"][-6:-1]) / mas["10"][-6:-1]
                overlap_5_10 = np.count_nonzero(diff < 5e-3)
                t3 = overlap_5_10 > 3

                diff = np.abs(mas["10"][-10:] -
                              mas["60"][-10:]) / mas["60"][-10:]
                overlap_10_60 = np.count_nonzero(diff < 5e-3)
                t4 = overlap_10_60 > 5

                price_change = await sec.price_change(
                    end, tf.shift(end, 8, frame_type), frame_type)
                result.append(
                    [end, code, t1, t2, t3, t4, slope_60, price_change, True])

                if t1 and t2 and t3 and t4:
                    print("FIRED:", [
                        end, code, t1, t2, t3, t4, slope_60, price_change, True
                    ])

            except Exception as e:
                pass

        return result