async def sync_bars(frame: str = None, codes: str = None): """立即同步行情数据 如果`frame`, `codes`没有提供,则从配置文件中读取相关信息 Args: frame: codes: Returns: """ await _init() if frame: frame_type = FrameType(frame) params = sync.read_sync_params(FrameType(frame)) if codes: params["secs"] = list(map(lambda x: x.strip(" "), codes.split(","))) await sync.trigger_bars_sync(frame_type, params, force=True) logger.info("request %s,%s send to workers.", params, codes) else: for frame_type in itertools.chain(tf.day_level_frames, tf.minute_level_frames): params = sync.read_sync_params(frame_type) if params: await sync.trigger_bars_sync(frame_type, params, force=True) logger.info("request %s,%s send to workers.", params, codes)
async def sync_bars(frame: str = None, codes: str = None): """立即同步行情数据 如果`frame`, `codes`没有提供,则从配置文件中读取相关信息 Args: frame: codes: Returns: """ await _init() if frame: frame_type = FrameType(frame) params = syncjobs.load_sync_params(frame_type) if codes: params["cat"] = None params["include"] = codes await syncjobs.trigger_bars_sync(params, force=True) logger.info("request %s,%s send to workers.", params, codes) else: for frame_type in itertools.chain(tf.day_level_frames, tf.minute_level_frames): params = syncjobs.load_sync_params(frame_type) if not params: continue if codes: params["cat"] = None params["include"] = codes await syncjobs.trigger_bars_sync(params, force=True) logger.info("request %s,%s send to workers.", params, codes)
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 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: str, frame_type: Union[str, FrameType] = '30m', win: int = 5, flag: str = 'both', slip: float = 0.015): """ 测试当前股价是否达到均线(frame_type和win指定)附近 Args: code: slip: params: frame_type, win, flag Returns: """ frame_type = FrameType(frame_type) bars = await self.get_bars(code, win, frame_type) ma = signal.moving_average(bars['close'], win) c0 = bars[-1]['close'] if abs(c0 / ma[-1] - 1) <= slip: await self.fire_trade_signal(flag, code, bars[-1]['frame'], frame_type, slip=slip, win=win)
async def _test_sync_bars(self): # fixme: recover this test later config_items = [ [ { "frame": "1d", "start": "2020-01-01", "delay": 3, "type": [], "include": "000001.XSHE,000004.XSHE", "exclude": "000001.XSHG", } ] ] sync_request = [] async def on_sync_bars(params: dict): sync_request.append(params) emit.register(Events.OMEGA_DO_SYNC, on_sync_bars) for config in config_items: cfg.omega.sync.bars = config for frame_config in config: frame_type = FrameType(frame_config.get("frame")) sync_params = frame_config await sync.trigger_bars_sync(frame_type, sync_params, force=True) await asyncio.sleep(0.2) self.assertDictEqual( {"start": "2020-01-01", "stop": None, "frame_type": FrameType.DAY}, sync_request[0], )
def __init__(self, frame_type: Union[str, FrameType], jitter: str = None): """构造函数 jitter的格式用正则式表达为`r"([-]?)(\\d+)([mshd])"`,其中第一组为符号,'-'表示提前; 第二组为数字,第三组为单位,可以为`m`(分钟), `s`(秒), `h`(小时),`d`(天)。 下面的示例构造了一个只在交易日,每30分钟触发一次,每次提前15秒触的trigger。即它的触发时 间是每个交易日的09:29:45, 09:59:45, ... Examples: >>> FrameTrigger(FrameType.MIN30, '-15s') ... # doctest: +ELLIPSIS <triggers.FrameTrigger object at 0x...> Args: frame_type: jitter: 单位秒。其中offset必须在一个FrameType的长度以内 """ self.frame_type = FrameType(frame_type) if jitter is None: _jitter = 0 else: matched = re.match(r"([-]?)(\d+)([mshd])", jitter) if matched is None: # pragma: no cover raise ValueError( "malformed. jitter should be [-](number)(unit), " "for example, -30m, or 30s") sign, num, unit = matched.groups() num = int(num) if unit.lower() == "m": _jitter = 60 * num elif unit.lower() == "s": _jitter = num elif unit.lower() == "h": _jitter = 3600 * num elif unit.lower() == "d": _jitter = 3600 * 24 * num else: # pragma: no cover raise ValueError("bad time unit. only s,h,m,d is acceptable") if sign == "-": _jitter = -_jitter self.jitter = datetime.timedelta(seconds=_jitter) if (frame_type == FrameType.MIN1 and abs(_jitter) >= 60 or frame_type == FrameType.MIN5 and abs(_jitter) >= 300 or frame_type == FrameType.MIN15 and abs(_jitter) >= 900 or frame_type == FrameType.MIN30 and abs(_jitter) >= 1800 or frame_type == FrameType.MIN60 and abs(_jitter) >= 3600 or frame_type == FrameType.DAY and abs(_jitter) >= 24 * 3600 # it's still not allowed if offset > week, month, etc. Would anybody # really specify an offset longer than that? ): raise ValueError("offset must be less than frame length")
async def evaluate(self, code: str, frame_type: Union[str, FrameType], flag: str, win: int, price: float, slip: float = 0.015): frame_type = FrameType(frame_type) bars = await self.get_bars(code, 1, frame_type) if abs(bars[-1]['close'] / price - 1) <= slip: await self.fire_trade_signal(flag, code, bars[-1]['frame'], frame_type)
async def evaluate(self, code: str, frame_type: Union[FrameType, str], flag: str, win: int, c1: float, d1: Frame, c2: float, d2: Frame, slip: float = 0.015): frame_type = FrameType(frame_type) n1 = tf.count_frames(d1, d2, frame_type) slp = (c2 - c1) / n1 n2 = tf.count_frames(d2, tf.floor(arrow.now(), frame_type), frame_type) c_ = c2 + slp * n2 bars = await self.get_bars(code, 1, frame_type) if abs(c_ / bars[-1]['close'] - 1) <= slip: await self.fire_trade_signal(flag, code, bars[-1]['frame'], frame_type)
async def get_bars_handler(self, request): try: sec = request.json.get("sec") frame_type = FrameType(request.json.get("frame_type")) end = arrow.get(request.json.get("end"), tzinfo=cfg.tz) end = end.date() if frame_type in tf.day_level_frames else end.datetime n_bars = request.json.get("n_bars") include_unclosed = request.json.get("include_unclosed", False) bars = await aq.get_bars(sec, end, n_bars, frame_type, include_unclosed) body = pickle.dumps(bars, protocol=cfg.pickle.ver) return response.raw(body) except Exception as e: logger.exception(e) return response.raw(pickle.dumps(None, protocol=cfg.pickle.ver))
async def build_train_data(self, save_to: str, frame_type: str = '1d', n=10) -> List: await self.init() frame_type = FrameType(frame_type) data = await self._build_train_data(frame_type, n) date = tf.date2int(arrow.now().date()) path = os.path.abspath(save_to) path = os.path.join(path, f"momemtum.{frame_type.value}.tsv") with open(path, "w") as f: cols = "code,date,a5,b5,err5,a10,b10,err10,a20,b20,err20,y".split(",") f.writelines("\t".join(cols)) f.writelines("\n") for item in data: f.writelines("\t".join(map(lambda x: str(x), item))) f.writelines("\n") return data
async def get_stock_pool(request): args = request.args frames = int(args.get('frames')[0]) frame_types = args.getlist("frame_types") if frame_types: frame_types = [FrameType(frame_type) for frame_type in frame_types] else: frame_types = tf.day_level_frames frame_types.extend(tf.minute_level_frames) plots = args.getlist('plots') or ['momentum'] results = [] for plot_name in plots: plot = create_plot(plot_name) results.append(await plot.list_stock_pool(frames, frame_types)) return response.json(body=results)
def test_comparison(self): day = FrameType("1d") week = FrameType("1w") month = FrameType("1M") quater = FrameType("1Q") year = FrameType("1Y") min_1 = FrameType("1m") min_5 = FrameType("5m") self.assertTrue(week > day) self.assertTrue(day < week) self.assertTrue(week < month) self.assertTrue(month < quater) self.assertTrue(quater < year) self.assertTrue(min_1, min_5) self.assertTrue(day >= day) self.assertTrue(day <= day)
def __init__(self, frame_type: Union[str, FrameType], jitter: str = None): """ Args: frame_type: jitter: in seconds unit, offset must within one frame """ self.frame_type = FrameType(frame_type) if jitter is None: _jitter = 0 else: matched = re.match(r"([-]?)(\d+)([mshd])", jitter) if matched is None: raise ValueError( "malformed. jitter should be [-](number)(unit), " "for example, -30m, or 30s") sign, num, unit = matched.groups() num = int(num) if unit.lower() == "m": _jitter = 60 * num elif unit.lower() == "s": _jitter = num elif unit.lower() == "h": _jitter = 3600 * num elif unit.lower() == "d": _jitter = 3600 * 24 * num else: raise ValueError("bad time unit. only s,h,m,d is acceptable") if sign == "-": _jitter = -_jitter self.jitter = datetime.timedelta(seconds=_jitter) if (frame_type == FrameType.MIN1 and abs(_jitter) >= 60 or frame_type == FrameType.MIN5 and abs(_jitter) >= 300 or frame_type == FrameType.MIN15 and abs(_jitter) >= 900 or frame_type == FrameType.MIN30 and abs(_jitter) >= 1800 or frame_type == FrameType.MIN60 and abs(_jitter) >= 3600 or frame_type == FrameType.DAY and abs(_jitter) >= 24 * 3600 # it's still not allowed if offset > week, month, etc. Would anybody # really specify an offset longer than that? ): raise ValueError("offset must be less than frame length")
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')
async def quick_scan(): # fixme secs = Securities() report = logging.getLogger("quickscan") counters = {} for sync_config in cfg.omega.sync.bars: frame = sync_config.get("frame") start = sync_config.get("start") if frame is None or start is None: logger.warning("skipped %s: required fields are [frame, start]", sync_config) continue frame_type = FrameType(frame) start = arrow.get(start).date() start = tf.floor(start, FrameType.DAY) stop = sync_config.get("stop") or arrow.now().date() if frame_type in tf.minute_level_frames: minutes = tf.ticks[frame_type][0] h, m = minutes // 60, minutes % 60 start = datetime.datetime(start.year, start.month, start.day, h, m, tzinfo=tz.gettz(cfg.tz)) stop = datetime.datetime(stop.year, stop.month, stop.day, 15, tzinfo=tz.gettz(cfg.tz)) counters[frame] = [0, 0] codes = secs.choose(sync_config.get("type")) include = filter(lambda x: x, sync_config.get("include", "").split(",")) include = map(lambda x: x.strip(" "), include) codes.extend(include) exclude = sync_config.get("exclude", "") exclude = map(lambda x: x.strip(" "), exclude) codes = set(codes) - set(exclude) counters[frame][1] = len(codes) for code in codes: head, tail = await cache.get_bars_range(code, frame_type) if head is None or tail is None: report.info("ENOSYNC,%s,%s", code, frame) counters[frame][0] = counters[frame][0] + 1 continue expected = tf.count_frames(head, tail, frame_type) # 'head', 'tail' should be excluded actual = (await cache.security.hlen(f"{code}:{frame_type.value}")) - 2 if actual != expected: report.info("ELEN,%s,%s,%s,%s,%s,%s", code, frame, expected, actual, head, tail) counters[frame][0] = counters[frame][0] + 1 continue sec = Security(code) if start != head: if (type(start) == datetime.date and start > sec.ipo_date or (type(start) == datetime.datetime and start.date() > sec.ipo_date)): report.info("ESTART,%s,%s,%s,%s,%s", code, frame, start, head, sec.ipo_date) counters[frame][0] = counters[frame][0] + 1 continue if tail != stop: report.info("EEND,%s,%s,%s,%s", code, frame, stop, tail) counters[frame][0] = counters[frame][0] + 1 return counters
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 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' }] }
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)
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)
def test_frame_type_convert(self): for frame_type in FrameType: self.assertEqual(frame_type.to_int(), FrameType.from_int(frame_type.to_int()).to_int())