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 test_polyfit_inflextion(self): bars = stocks.get_bars('000001.XSHG', 100, '30m') ma20 = signal.moving_average(bars['close'], 20) peaks, valleys = signal.polyfit_inflextion(ma20, 10) print(peaks, valleys)
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
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}")
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:])
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:] })
async def scan(self, end: Frame = None, frame_type: FrameType = FrameType.DAY, codes=None, adv_limit=0.3): """ Args: end: adv_limit: 不包括在win周期内涨幅超过adv_limit的个股 Returns: """ win = 20 secs = Securities() end = end or tf.floor(arrow.now(), FrameType.DAY) results = [] holdings = await cache.sys.smembers("holdings") for i, code in enumerate(secs.choose(['stock'])): try: if code in holdings: # 如果已经持仓,则不跟踪评估 continue sec = Security(code) if sec.code.startswith('688') or sec.display_name.find('ST') != -1: continue start = tf.day_shift(end, -270) bars = await sec.load_bars(start, end, FrameType.DAY) close = bars['close'] ma5 = signal.moving_average(close, 5) ma250 = signal.moving_average(close, 250) cross, idx = signal.cross(ma5[-win:], ma250[-win:]) cross_day = bars[-win + idx]['frame'] if cross != 1: continue ma20 = signal.moving_average(close, 20) ma120 = signal.moving_average(close, 120) # 如果上方还有月线和ma120线,则不发出信号,比如广州浪奇 2020-7-23,泛海控股2020-8-3 if close[-1] < ma120[-1] or close[-1] < ma20[-1]: continue # 计算20日以来大阳次数。如果不存在大阳线,认为还未到上涨时机,跳过 grl, ggl = features.count_long_body(bars[-20:]) if grl == 0: continue # # # 计算突破以来净余买量(用阳线量减去阴线量来模拟,十字星不计入) # bsc = bars[-10 + idx:] # bars_since_open: included both side # ups = bsc[bsc['close'] > (bsc['open'] * 1.01)] # downs = bsc[bsc['open'] > (bsc['close'] * 0.99)] # balance = np.sum(ups['volume']) - np.sum(downs['volume']) # pc = await sec.price_change(cross_day, tf.day_shift(cross_day, 5), # FrameType.DAY, return_max=True) # faf = int(win - idx) # frames after fired adv = await sec.price_change(tf.day_shift(end, -win), end, FrameType.DAY, False) if adv > adv_limit: continue logger.info(f"{sec}上穿年线\t{cross_day}\t{faf}") await cache.sys.hmset_dict("plots.crossyear", {code: json.dumps({ "fired_at": tf.date2int(end), "cross_day": tf.date2int(cross_day), "faf": faf, "grl": grl, "ggl": ggl, "status": 0 # 0 - generated by plots 1 - disabled manually })}) results.append( [sec.display_name, tf.date2int(end), tf.date2int(cross_day), faf, grl, ggl]) except Exception as e: logger.exception(e) logger.info("done crossyear scan.") return results
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 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
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')