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)
def test_000_something(self): """Test something.""" bars = jq.get_bars('002082.XSHE', 30, df=False, fq_ref_date='2020-7-29', end_dt='2020-7-30') ma5 = moving_average(bars['close'], 5) ma10 = moving_average(bars['close'], 10) ma20 = moving_average(bars['close'], 20) print(ma5[-5:]) print(bars['date'][-1], len(ma5), len(ma10), len(ma20)) for i in range(16, 21): err, curve, coef = polyfit(ma5[i:i + 5]) a, b, c = coef axis_x = -b / (2 * a) axis_y = (4 * a * c - b * b) / (4 * a) print(f"{bars['date'][i+9]} err:{err:.4f},coef:{a:.3f},{b:.3f}" f",{c:.3f}, " f"(x," f"y):{axis_x:.1f}" f",{axis_y:.2f}") print("=" * 5 + "ma10") for i in range(11, 16): err, curve, coef = polyfit(ma10[i:i + 5]) a, b, c = coef axis_x = -b / (2 * a) axis_y = (4 * a * c - b * b) / (4 * a) print( f"{bars[i+14]['date']} err:{err:.4f}, coef:{a:.3f},{b:.3f},{c:.3f}, " f"(x,y):{axis_x:.1f}" f",{axis_y:.2f}") print("=" * 5 + "ma20") for i in range(1, 6): err, curve, coef = polyfit(ma20[i:i + 5]) a, b, c = coef axis_x = -b / (2 * a) axis_y = (4 * a * c - b * b) / (4 * a) print( f"{bars[i+24]['date']} err:{err:.4f}, coef:{a:.3f},{b:.3f},{c:.3f}, " f"(x,y):{axis_x:.1f}" f",{axis_y:.2f}")
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
async def evaluate(self, code, frame_type, flag, ma_win: int = 20, slp=1e-2): """ 当股价回归到指定的均线上时,发出信号 Args: code: frame_type: flag: ma:需要监控的均线 Returns: """ bars = await self.get_bars(code, ma_win + 10, frame_type) ma = signal.moving_average(bars['close'], ma_win) err, (a, b, c), (vx, _) = signal.polyfit(ma[-7:] / ma[-7]) p = np.poly1d((a, b, c)) if (p(11) / p(6) - 1) / 5 < slp: return sec = Security(code) alarm = f"{sec.display_name}触达{ma_win}均线。" await emit.emit(Events.sig_long, {"alarm": alarm})
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)
def extract_features(self, bars, max_error): """ Args: bars: Returns: """ ma5 = moving_average(bars['close'], 5)[-5:] ma10 = moving_average(bars['close'], 10)[-5:] ma20 = moving_average(bars['close'], 20)[-5:] feat = [] for ts in [ma5, ma10, ma20]: try: err, coef = polyfit(ts) # 取系数a和b。对于一次函数,a=0;对于二次函数和指数函数,a,b都存在。c为截距,忽略 a, b = coef[0], coef[1] if np.isnan(a) or np.isnan(b) or np.isnan(err) or err > max_error: raise ValueError feat.extend([a, b, -b / (2 * a)]) except Exception: return feat return feat
async def copy(self, code: str, frame_type: FrameType, start, stop): n = tf.count_frames(start, stop, frame_type) bars = await self.get_bars(code, n + 20, frame_type, stop) feat = features.ma_lines_trend(bars, [5, 10, 20]) ma5, ma10, ma20 = feat["ma5"][0], feat["ma10"][0], feat["ma20"][0] self.max_distance = self.distance(ma5, ma10, ma20) err, (a, b) = signal.polyfit(ma20, deg=1) self.fitslp_ma20 = a self.fiterr_ma20 = err
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)
async def scan_1(self, ma_win: int, frame_type: FrameType, a: float = None, b: float = None, err=1e-3, end: Frame = None): """ 在所有股票中,寻找指定均线强于拟合均线(a,b,1)的,如果当前收盘价在均线附近,且近期存在 大阳线,则发出信号 Returns: """ if a is None: err = self.ref_lines[f"ma{ma_win}"].get("err") a, b = self.ref_lines[f"ma{ma_win}"].get("coef") fit_win = 7 secs = Securities() p = np.poly1d((a, b, 1.0)) slp3 = p(fit_win - 1 + 3) / p(fit_win - 1) - 1 count = 0 for i, code in enumerate(secs.choose(['stock'])): if (i + 1) % 500 == 0: logger.debug("handled %s", i + 1) sec = Security(code) bars = await self.get_bars(code, fit_win + 19, frame_type, end) ma = signal.moving_average(bars['close'], ma_win) err_, (a_, b_, c_), (vx_, _) = signal.polyfit(ma[-fit_win:] / ma[-fit_win]) if err_ > err: continue #p_ = np.poly1d((a_,b_,1.0)) # 如果abs(b) < fit_win * a,曲线(在x不超过fit_win的地方)接近于直线,此时应该比较b t5, t10, t20 = False, None, None #slp3_5 = p_(fit_win+2)/p_(fit_win-1) - 1 t5 = a_ >= a * 0.99 and fit_win + 1 >= vx_ >= fit_win - 2 if t5: print(f"{sec.display_name},{vx_:.1f}")
def ma_lines_trend(bars: np.array, ma_wins: List[int]): """ 从bars数据中提取均线的走势特征 Args: bars: ma_wins: Returns: """ features = {} for win in ma_wins: ma = signal.moving_average(bars['close'], win) fit_win = 7 if win == 5 else 10 err, (a, b, c), (vx, _) = signal.polyfit(ma[-fit_win:] / ma[-fit_win]) p = np.poly1d((a, b, c)) # 预测一周后均线涨幅 war = p(fit_win + 5 - 1) / p(fit_win - 1) - 1 features[f"ma{win}"] = [ma, (err, a, b, vx, fit_win, war)] return features
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 }
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:] })
def screen(self, frame, end_dt=None, adv_lim=25, win=7, a5=0.02, a10=0.001): all = [] fired = [] if end_dt is None: end_dt = arrow.now().datetime for i, code in enumerate(stocks.all_stocks()): try: name = stocks.name_of(code) if name.endswith("退"): continue if name.find("ST") != -1: continue bars = stocks.get_bars(code, 30, frame, end_dt=end_dt) if len(bars) == 0: print("get 0 bars", code) continue if arrow.get(bars['date'].iat[-1]).date() != arrow.get( end_dt).date(): continue # 30日涨幅必须小于adv_lim if bars['close'].iat[-1] / bars['close'].min( ) >= 1 + adv_lim / 100: print(f"{code}涨幅大于", adv_lim) continue ma5 = np.array(moving_average(bars['close'], 5)) ma10 = np.array(moving_average(bars['close'], 10)) err5, coef5, vertex5 = polyfit(ma5[-win:]) err10, coef10, vertex10 = polyfit(ma10[-win:]) vx5, _ = vertex5 vx10, _ = vertex10 _a5 = coef5[0] _a10 = coef10[0] all.append([code, _a5, _a10, vx5, vx10, err5, err10]) # print(code, round_list([err5, vx, pred_up, y5, ma5[-1], y10, ma10[-1]],3)) # 如果曲线拟合较好,次日能上涨up%以上,10日线也向上,最低点在win/2以内 t1 = err5 <= 0.003 and err10 <= 0.003 t2 = _a5 > a5 and _a10 > a10 t3 = (win - 1 > vx5 >= win / 2 - 1) and (vx10 < win / 2 - 1) if t1 and t2 and t3: c1, c0 = bars['close'].iat[-2], bars['close'].iat[-1] if stocks.check_buy_limit(c1, c0, name): # 跳过涨停的 continue print(f"{stocks.name_of(code)} {code}", [_a5, _a10, vx5, vx10, err5, err10]) fired.append([code, _a5, _a10, vx5, vx10, err5, err10]) except Exception as e: print(i, e) continue return DataFrame( data=all, columns=['code', 'a5', 'a10', 'vx5', 'vx10', 'err_5', 'err_10'])
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)
async def evaluate(self, code: str, frame_type: str = '30m', dt: str = None, win=5, flag='long'): """ 如果股价从高点下来,或者从低点上来,则发出信号。高点和低点的确定,由于曲线拟合的原因, 可能产生上一周期未发出信号,这一周期发出信号,但高点或者低点已在几个周期之前。这里的 策略是,在新的趋势未形成之前,只报一次 Args: code: frame_type: frame_type dt: win: flag: Returns: """ stop = arrow.get(dt, tzinfo=cfg.tz) if dt else arrow.now(tz=cfg.tz) frame_type = FrameType(frame_type) ft = frame_type.value bars = await self.get_bars(code, win + self.fit_win, frame_type, stop) ma = signal.moving_average(bars['close'], win) _ma = ma[-self.fit_win:] err, (a, b, c), (vx, _) = signal.polyfit(_ma / _ma[0]) logger.debug("%s, %s, %s, %s, %s", code, err, a, b, vx) if err > self.baseline(f"ma{win}:{ft}:err"): self.remember(code, frame_type, "trend", "dunno") return p = np.poly1d((a, b, c)) y = p(self.fit_win + 2) / p(self.fit_win - 1) - 1 previous_status = self.recall(code, frame_type, "trend") # 如果b > 10 * a * x,则走势主要由b决定。这里x即fit_win序列,我们向后看3周期 if abs(b) > 10 * (self.fit_win + 3) * abs(a): if b > 0 and previous_status != "long" and flag in [ 'both', "long" ]: await self.fire_trade_signal('long', code, stop, frame_type, a=a, b=b, err=err, vx=vx, y=y) if b < 0 and previous_status != "short" and flag in [ 'both', "short" ]: await self.fire_trade_signal('short', code, stop, frame_type, err=err, a=a, b=b, y=y) return t1 = int(vx) < self.fit_win - 1 # 判断是否为看多信号 t2 = a > self.baseline(f"ma{win}:{ft}:a") if t1 and t2 and previous_status != "long" and flag in [ "long", "both" ]: await self.fire_trade_signal('long', code, stop, frame_type, err=err, a=a, b=b, y=y) # 判断是否为看空信号 t2 = a < -self.baseline(f"ma{win}:{ft}:a") if t1 and t2 and previous_status != "short" and flag in [ "short", "both" ]: await self.fire_trade_signal('short', code, stop, frame_type, err=err, a=a, b=b, y=y)
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')