async def test_get_bars_with_turnover(self): code = "000001.XSHE" start = arrow.get("2020-01-03").date() stop = arrow.get("2020-1-16").date() frame_type = FrameType.DAY expected = [ 0.5752, 0.4442, 0.3755, 0.4369, 0.5316, 0.3017, 0.4494, 0.6722, 0.4429, 0.5298, ] sec = Security(code) bars = await sec.load_bars(start, stop, frame_type, turnover=True) for i, bar in enumerate(bars): self.assertAlmostEqual(expected[i], bar["turnover"], places=3) start = arrow.get("2020-11-02 15:00:00").datetime stop = arrow.get("2020-11-06 14:30:00").datetime frame_type = FrameType.MIN30 sec = Security(code) bars = await sec.load_bars(start, stop, frame_type, turnover=True) expected = [0.02299885, 0.02921041] self.assertAlmostEqual(expected[0], bars["turnover"][-2], places=3) self.assertAlmostEqual(expected[1], bars["turnover"][-1], places=3)
def test_000_properties(self): sec = Security("000001.XSHE") for key, value in zip( "display_name ipo_date end_date".split(" "), "平安银行 1991-04-03 2200-01-01".split(" "), ): self.assertEqual(str(getattr(sec, key)), value) sec = Security("399001.XSHE") print(sec)
async def test_buy_limit_events(self): end = arrow.get('2020-8-7').date() start = tf.day_shift(end, -9) sec = Security('603390.XSHG') bars = await sec.load_bars(start, end, FrameType.DAY) count, indices = count_buy_limit_event(sec, bars) self.assertEqual(count, 1) self.assertEqual( arrow.get('2020-7-28').date(), bars['frame'][indices[0]]) sec = Security('000070.XSHE') start = tf.day_shift(end, -29) bars = await sec.load_bars(start, end, FrameType.DAY) count_buy_limit_event(sec, bars)
async def add_monitor(request): """ supported cmd: add, remove,list Args: request: Returns: """ params = request.json code = params.get("code") plot = params.get('plot') if all([code is None, plot is None]): return response.text("必须指定要监控的股票代码", status=401) try: del params['plot'] await mm.add_monitor(plot, **params) display_name = Security(code).display_name return response.text(f"{display_name}已加入{plot}监控", status=200) except Exception as e: logger.exception(e) return response.text(e, status=500)
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: 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}发出老鸭头信号" })
async def list_momentum_pool(day_offset: int = 1, sort_by='y'): start = tf.day_shift(arrow.now().date(), -day_offset) key = f"plots.momentum.pool" recs = await cache.sys.hgetall(key) data = [] for k, v in recs.items(): frame, code = k.split(":") if arrow.get(frame) < arrow.get(start): continue sec = Security(code) v = json.loads(v) frame_type = FrameType(v.get("frame_type")) fired = tf.int2time(frame) if frame_type in tf.minute_level_frames else \ tf.int2date(frame) data.append({ "name": sec.display_name, "code": code, "fired": 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) }) if len(data) == 0: print("no data") else: df = DataFrame(data) df.set_index('fired', inplace=True) display(df.sort_values(sort_by))
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)
async def list_monitors(plot: str = None, code: str = None): monitors = {} for item in await request("monitor", "list", plot=plot, code=code): code = item[1].get("code") item[1]['name'] = Security(code).display_name item[1]['triggers'] = item[2] recs = monitors.get(item[0], []) recs.append(item[1]) monitors[item[0]] = recs pd.set_option('max_rows', 100) if plot is not None: plots = [plot] else: plots = monitors.keys() for plot in plots: recs = monitors.get(plot) df = DataFrame( recs, columns=["name", "code", "frame_type", "flag", "win", "triggers"]) df.columns = ["name", "code", "frame", "flag", "win", "trigger"] print(plot) display(df)
async def list_stock_pool(plot=None, time_offset: int = 3): now = arrow.now().date() start = tf.day_shift(now, -time_offset) if plot is None: keys = await cache.sys.keys("plots.*.pool") else: keys = [f"plots.{plot}.pool"] results = [] for key in keys: recs = await cache.sys.hgetall(key) data = [] for k, v in recs.items(): _frame, code = k.split(":") if len(_frame) == 8: frame = tf.int2date(int(_frame)) else: frame = tf.int2time(int(_frame)) if arrow.get(frame) < arrow.get(start): continue sec = Security(code) row = {"name": sec.display_name, "code": code, "frame": frame} row.update(json.loads(v)) data.append(row) print(f"----------{key.lower()}----------") df = DataFrame(data=data) df.set_index('frame', inplace=True) display(df) results.append(df) return results
def translate_monitor(self, job_name, params: dict, trigger: dict): _flag_map = {"both": "双向监控", "long": "做多信号", "short": "做空信号"} _frame_type_map = { "1d": "日线", "1w": "周线", "1M": "月线", "30m": "30分钟线", "60m": "60分钟线", "120m": "120分钟线" } items = {"key": job_name} for k, v in params.items(): if k == "flag": items['flag'] = _flag_map[v] elif k == "code": items['代码'] = v.split(".")[0] items['名称'] = Security(v).display_name elif k == "frame_type": items['周期'] = _frame_type_map[v] elif k == "win": items['均线'] = f"MA{v}" items['监控计划'] = mm.translate_trigger(trigger) return items
async def test_price_change(self): sec = Security("000001.XSHG") frame_type = FrameType.DAY start = arrow.get("2020-07-29").date() end = arrow.get("2020-8-7").date() pc = await sec.price_change(start, end, frame_type, False) self.assertAlmostEqual(pc, 3354.04 / 3294.55 - 1, places=3)
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)
async def predict(self, code, x_end_date: datetime.date, max_error: float = 0.01): sec = Security(code) start = tf.day_shift(x_end_date, -29) bars = await sec.load_bars(start, x_end_date, FrameType.DAY) features = self.extract_features(bars, max_error) if len(features) == 0: logger.warning("cannot extract features from %s(%s)", code, x_end_date) else: return self.model.predict([features])
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)
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)
async def test_cross(self): end = arrow.get('2020-7-24').date() start = tf.day_shift(end, -270) sec = Security('000035.XSHE') jlkg = await sec.load_bars(start, end, FrameType.DAY) ma5 = signal.moving_average(jlkg['close'], 5) ma250 = signal.moving_average(jlkg['close'], 250) flag, idx = signal.cross(ma5[-10:], ma250[-10:]) self.assertEqual(flag, -1) self.assertEqual(idx, 8)
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 _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
async def test_001_choose(self): s = Securities() result = s.choose(["stock", "index"]) self.assertEqual("000001.XSHE", result[0]) result = s.choose(["stock"], exclude_300=True) self.assertTrue(all([not x.startswith("300") for x in result])) result = s.choose(["stock"], exclude_st=True) for code in result: sec = Security(code) self.assertTrue(sec.display_name.upper().find("ST") == -1) result = s.choose(["stock"], exclude_688=True) self.assertTrue(all([not x.startswith("688") for x in result]))
async def read_fired_signal_msg(msg: dict): plot, flag, code = msg.get("plot"), msg.get("flag"), msg.get("code") plot_name = msg.get("name") sec = Security(code) if flag == "long": sig_name = "发出买入信号" elif flag == "short": sig_name = "发出卖出信号" else: sig_name = "触发" text = f"{plot_name}监控策略在{sec.display_name}上{sig_name}" msg.update({"股票名": sec.display_name}) await read_msg(text)
async def find_by_moving_average(self): result = [] for code in Securities().choose(['stock']): day = arrow.now().date() sec = Security(code) try: signal, fit = await self.test_signal(sec, day) if abs(signal) == 1: result.append([code, day, signal, fit]) except Exception as e: logger.info(e) continue # reporter.info("%s,%s,%s,%s,%s,%s,%s,%s,%s", # code, day, signal, *fit[0], *fit[1], *fit[2]) return result
async def test_load_bars_batch(self): codes = ["000001.XSHE", "000001.XSHG"] # end = arrow.now(tz=cfg.tz).datetime # async for code, bars in Security.load_bars_batch(codes, end, 10, # FrameType.MIN30): # print(bars[-2:]) # self.assertEqual(10, len(bars)) # # 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)
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 test_screen(self): import cfg4py import os from alpha.config import get_config_dir os.environ[cfg4py.envar] = 'PRODUCTION' cfg4py.init(get_config_dir()) await omicron.init() code = '300023.XSHE' sec = Security(code) stop = arrow.now().datetime start = tf.day_shift(stop, -3) bars = await sec.load_bars(start, stop, FrameType.DAY) print(bars) if np.all(bars['close'] > bars['open']): print(sec.display_name, "\t", 100 * (bars[-1]['close'] / bars[-2]['close'] - 1))
async def test_005_realtime_bars(self): """测试获取实时行情""" sec = Security("000001.XSHE") frame_type = FrameType.MIN15 logger.info("scenario: get realtime bars") start = arrow.get("2020-05-06 10:15:00", tzinfo=cfg.tz).datetime stop = arrow.get("2020-05-06 10:25:00", tzinfo=cfg.tz).datetime await cache.clear_bars_range(sec.code, frame_type) bars = await sec.load_bars(start, stop, frame_type) self.assertEqual(start, bars[0]["frame"]) self.assertEqual(stop, bars[-1]["frame"]) # now we've cached bars at 2020-05-06 10:15:00 bars = await sec.load_bars(start, stop, frame_type) self.assertEqual(start, bars[0]["frame"]) self.assertEqual(stop, bars[-1]["frame"])
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 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 test_003_slice(self): sec = Security("000001.XSHE") start = arrow.get("2020-01-03").date() stop = arrow.get("2020-01-16").date() await sec.load_bars(start, stop, FrameType.DAY) bars = sec[0:] expected = [ [ start, 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 ], ] self.assert_bars_equal(expected, bars) expected = [ [ arrow.get("2020-01-03").date(), 16.94, 17.31, 16.92, 17.18, 1.11619481e8, 1914495474.63, 118.73, ], [ arrow.get("2020-01-06").date(), 17.01, 17.34, 16.91, 17.07, 86208350.0, 1477930193.19, 118.73, ], ] self.assert_bars_equal(expected, sec[0:2])