Skip to content

Feed

Feed模块用以给backtest server提供撮合数据。

backtest server在进行撮合时,需要获取从下单时间起之后到当天结束时的撮合数据。backtest server本身并不提供这样的数据,它依赖data feed来提供。

backtest server本身提供了一个基于zillionare-omicron接口的data feed,该feeder基于分钟线数据提供撮合数据。

basefeed

BaseFeed

Source code in backtest/feed/basefeed.py
class BaseFeed(metaclass=ABCMeta):
    def __init__(self, *args, **kwargs):
        pass

    @abstractmethod
    async def init(self, *args, **kwargs):
        pass

    @classmethod
    async def create_instance(cls, interface="zillionare", **kwargs):
        """
        创建feed实例。当前仅支持zillionare接口。该接口要求使用[zillionare-omicron](https://zillionare.github.io/omicron/)来提供数据。
        """
        from backtest.feed.zillionarefeed import ZillionareFeed

        if interface == "zillionare":
            feed = ZillionareFeed(**kwargs)
            await feed.init()
            return feed
        else:
            raise TypeError(f"{interface} is not supported")

    @abstractmethod
    async def get_price_for_match(
        self, security: str, start: datetime.datetime
    ) -> np.ndarray:
        """获取从`start`之后起当天所有的行情数据,用以撮合

        这里没有要求指定行情数据的时间帧类型,理论上无论从tick级到日线级,backtest都能支持。返回的数据至少要包括`frame`、`price`、`volume`三列。

        Args:
            security : 证券代码
            start : 起始时间
        Returns:
            a numpy array which dtype is `match_data_dtype`
        """
        raise NotImplementedError

    @abstractmethod
    async def get_close_price(self, sec: str, date: datetime.date, fq=False) -> float:
        """
        获取证券品种在`date`日期的收盘价

        Args:
            sec: 证券代码
            date: 日期
            fq: 是否进行前复权

        Returns:
            `sec`在`date`日的收盘价
        """
        raise NotImplementedError

    @abstractmethod
    async def batch_get_close_price_in_range(
        self, secs: List[str], frames: List[datetime.date], fq=False
    ) -> Dict[str, np.array]:
        """获取多个证券在多个日期的收盘价

        Args:
            secs: 证券代码列表
            frames: 日期列表, 日期必须是有序且连续
            fq: 是否复权。

        Returns:
            a dict which key is `sec` and value is a numpy array which dtype is `[("frame", "O"), ("close", "f4")]`
        """
        raise NotImplementedError

    @abstractmethod
    async def get_trade_price_limits(self, sec: str, date: datetime.date) -> Tuple:
        """获取证券的交易价格限制

        获取证券`sec`在`date`日期的交易价格限制。

        Args:
            sec : 证券代码
            date : 日期

        Returns:
            交易价格限制,元组,(日期,涨停价,跌停价)
        """
        raise NotImplementedError

    @abstractmethod
    async def get_dr_factor(
        self, secs: Union[str, List[str]], frames: List[datetime.date]
    ) -> Dict[str, np.array]:
        """股票在[start,end]间的每天的复权因子,使用start日进行归一化处理

        注意实现者必须保证,复权因子的长度与日期的长度相同且完全对齐。如果遇到停牌的情况,应该进行相应的填充。

        Args:
            secs: 股票代码
            frames: 日期列表

        Returns:
            返回一个dict
        """
        raise NotImplementedError

batch_get_close_price_in_range(self, secs, frames, fq=False) async

获取多个证券在多个日期的收盘价

Parameters:

Name Type Description Default
secs List[str]

证券代码列表

required
frames List[datetime.date]

日期列表, 日期必须是有序且连续

required
fq

是否复权。

False

Returns:

Type Description
Dict[str, <built-in function array>]

a dict which key is sec and value is a numpy array which dtype is [("frame", "O"), ("close", "f4")]

Source code in backtest/feed/basefeed.py
@abstractmethod
async def batch_get_close_price_in_range(
    self, secs: List[str], frames: List[datetime.date], fq=False
) -> Dict[str, np.array]:
    """获取多个证券在多个日期的收盘价

    Args:
        secs: 证券代码列表
        frames: 日期列表, 日期必须是有序且连续
        fq: 是否复权。

    Returns:
        a dict which key is `sec` and value is a numpy array which dtype is `[("frame", "O"), ("close", "f4")]`
    """
    raise NotImplementedError

create_instance(interface='zillionare', **kwargs) async classmethod

创建feed实例。当前仅支持zillionare接口。该接口要求使用zillionare-omicron来提供数据。

Source code in backtest/feed/basefeed.py
@classmethod
async def create_instance(cls, interface="zillionare", **kwargs):
    """
    创建feed实例。当前仅支持zillionare接口。该接口要求使用[zillionare-omicron](https://zillionare.github.io/omicron/)来提供数据。
    """
    from backtest.feed.zillionarefeed import ZillionareFeed

    if interface == "zillionare":
        feed = ZillionareFeed(**kwargs)
        await feed.init()
        return feed
    else:
        raise TypeError(f"{interface} is not supported")

get_close_price(self, sec, date, fq=False) async

获取证券品种在date日期的收盘价

Parameters:

Name Type Description Default
sec str

证券代码

required
date date

日期

required
fq

是否进行前复权

False

Returns:

Type Description
float

secdate日的收盘价

Source code in backtest/feed/basefeed.py
@abstractmethod
async def get_close_price(self, sec: str, date: datetime.date, fq=False) -> float:
    """
    获取证券品种在`date`日期的收盘价

    Args:
        sec: 证券代码
        date: 日期
        fq: 是否进行前复权

    Returns:
        `sec`在`date`日的收盘价
    """
    raise NotImplementedError

get_dr_factor(self, secs, frames) async

股票在[start,end]间的每天的复权因子,使用start日进行归一化处理

注意实现者必须保证,复权因子的长度与日期的长度相同且完全对齐。如果遇到停牌的情况,应该进行相应的填充。

Parameters:

Name Type Description Default
secs Union[str, List[str]]

股票代码

required
frames List[datetime.date]

日期列表

required

Returns:

Type Description
Dict[str, <built-in function array>]

返回一个dict

Source code in backtest/feed/basefeed.py
@abstractmethod
async def get_dr_factor(
    self, secs: Union[str, List[str]], frames: List[datetime.date]
) -> Dict[str, np.array]:
    """股票在[start,end]间的每天的复权因子,使用start日进行归一化处理

    注意实现者必须保证,复权因子的长度与日期的长度相同且完全对齐。如果遇到停牌的情况,应该进行相应的填充。

    Args:
        secs: 股票代码
        frames: 日期列表

    Returns:
        返回一个dict
    """
    raise NotImplementedError

get_price_for_match(self, security, start) async

获取从start之后起当天所有的行情数据,用以撮合

这里没有要求指定行情数据的时间帧类型,理论上无论从tick级到日线级,backtest都能支持。返回的数据至少要包括framepricevolume三列。

Parameters:

Name Type Description Default
security

证券代码

required
start

起始时间

required

Returns:

Type Description
ndarray

a numpy array which dtype is match_data_dtype

Source code in backtest/feed/basefeed.py
@abstractmethod
async def get_price_for_match(
    self, security: str, start: datetime.datetime
) -> np.ndarray:
    """获取从`start`之后起当天所有的行情数据,用以撮合

    这里没有要求指定行情数据的时间帧类型,理论上无论从tick级到日线级,backtest都能支持。返回的数据至少要包括`frame`、`price`、`volume`三列。

    Args:
        security : 证券代码
        start : 起始时间
    Returns:
        a numpy array which dtype is `match_data_dtype`
    """
    raise NotImplementedError

get_trade_price_limits(self, sec, date) async

获取证券的交易价格限制

获取证券secdate日期的交易价格限制。

Parameters:

Name Type Description Default
sec

证券代码

required
date

日期

required

Returns:

Type Description
Tuple

交易价格限制,元组,(日期,涨停价,跌停价)

Source code in backtest/feed/basefeed.py
@abstractmethod
async def get_trade_price_limits(self, sec: str, date: datetime.date) -> Tuple:
    """获取证券的交易价格限制

    获取证券`sec`在`date`日期的交易价格限制。

    Args:
        sec : 证券代码
        date : 日期

    Returns:
        交易价格限制,元组,(日期,涨停价,跌停价)
    """
    raise NotImplementedError

zillionarefeed

ZillionareFeed (BaseFeed)

Source code in backtest/feed/zillionarefeed.py
class ZillionareFeed(BaseFeed):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    async def init(self, *args, **kwargs):
        pass

    async def get_price_for_match(
        self, security: str, start: datetime.datetime
    ) -> np.ndarray:
        end = datetime.datetime.combine(start.date(), datetime.time(15))
        bars = await Stock.get_bars(security, 240, FrameType.MIN1, end)
        if start.hour * 60 + start.minute <= 571:  # 09:31
            bars[0]["close"] = bars[0]["open"]

        return bars[bars["frame"] >= start][["frame", "close", "volume"]].astype(
            match_data_dtype
        )

    async def get_close_price(self, sec: str, date: datetime.date, fq=False) -> float:
        try:
            bars = await Stock.get_bars(sec, 1, FrameType.DAY, date, fq=fq)
            if len(bars):
                return math_round(bars[-1]["close"].item(), 2)
            else:
                bars = await Stock.get_bars(sec, 500, FrameType.DAY, date, fq=fq)
                return math_round(bars[-1]["close"].item(), 2)
        except Exception as e:
            logger.exception(e)
            logger.warning("get_close_price failed for %s:%s", sec, date)

        return None

    async def batch_get_close_price_in_range(
        self, secs: List[str], frames: List[datetime.date], fq=False
    ) -> Dict[str, np.array]:
        if len(secs) == 0:
            raise ValueError("No securities provided")

        start = frames[0]
        end = frames[-1]

        close_dtype = [("frame", "O"), ("close", "<f4")]
        result = {}

        try:
            async for sec, values in Stock.batch_get_day_level_bars_in_range(
                secs, FrameType.DAY, start, end, fq=fq
            ):
                closes = values[["frame", "close"]].astype(close_dtype)
                if len(closes) == 0:
                    # 遇到停牌的情况
                    price = await self.get_close_price(sec, frames[-1], fq=fq)
                    if price is None:
                        result[sec] = None
                    else:
                        result[sec] = np.array(
                            [(f, price) for f in frames], dtype=close_dtype
                        )
                    continue

                closes["close"] = array_math_round(closes["close"], 2)
                closes["frame"] = [item.date() for item in closes["frame"]]

                # find missed frames, using left fill
                missed = np.setdiff1d(frames, closes["frame"])
                if len(missed):
                    missed = np.array(
                        [(f, np.nan) for f in missed],
                        dtype=close_dtype,
                    )
                    closes = np.concatenate([closes, missed])
                    closes = np.sort(closes, order="frame")
                    closes["close"] = fill_nan(closes["close"])

                result[sec] = closes

            return result
        except Exception:
            logger.warning("get_close_price failed for %s:%s - %s", secs, start, end)
            raise

    async def get_trade_price_limits(self, sec: str, date: datetime.date) -> np.ndarray:
        prices = await Stock.get_trade_price_limits(sec, date, date)

        if len(prices):
            return prices[0]
        else:
            logger.warning("get_trade_price_limits failed for %s:%s", sec, date)
            raise EntrustError(EntrustError.NODATA, security=sec, time=date)

    async def get_dr_factor(
        self, secs: Union[str, List[str]], frames: List[datetime.date]
    ) -> Dict[str, np.ndarray]:
        try:
            result = {}
            async for sec, bars in Stock.batch_get_day_level_bars_in_range(
                secs, FrameType.DAY, frames[0], frames[-1], fq=False
            ):
                factors = bars[["frame", "factor"]].astype(
                    [("frame", "O"), ("factor", "<f4")]
                )

                # find missed frames, using left fill
                missed = np.setdiff1d(
                    frames, [item.item().date() for item in bars["frame"]]
                )
                if len(missed):
                    missed = np.array(
                        [(f, np.nan) for f in missed],
                        dtype=[("frame", "datetime64[s]"), ("factor", "<f4")],
                    )
                    factors = np.concatenate([factors, missed])
                    factors = np.sort(factors, order="frame")

                if all(np.isnan(factors["factor"])):
                    factors["factor"] = [1.0] * len(factors)
                else:
                    factors["factor"] = fill_nan(factors["factor"])

                result[sec] = factors["factor"] / factors["factor"][0]
            return result
        except Exception as e:
            logger.exception(e)
            logger.warning(
                "get_dr_factor failed for %s:%s ~ %s", secs, frames[0], frames[-1]
            )
            raise

batch_get_close_price_in_range(self, secs, frames, fq=False) async

获取多个证券在多个日期的收盘价

Parameters:

Name Type Description Default
secs List[str]

证券代码列表

required
frames List[datetime.date]

日期列表, 日期必须是有序且连续

required
fq

是否复权。

False

Returns:

Type Description
Dict[str, <built-in function array>]

a dict which key is sec and value is a numpy array which dtype is [("frame", "O"), ("close", "f4")]

Source code in backtest/feed/zillionarefeed.py
async def batch_get_close_price_in_range(
    self, secs: List[str], frames: List[datetime.date], fq=False
) -> Dict[str, np.array]:
    if len(secs) == 0:
        raise ValueError("No securities provided")

    start = frames[0]
    end = frames[-1]

    close_dtype = [("frame", "O"), ("close", "<f4")]
    result = {}

    try:
        async for sec, values in Stock.batch_get_day_level_bars_in_range(
            secs, FrameType.DAY, start, end, fq=fq
        ):
            closes = values[["frame", "close"]].astype(close_dtype)
            if len(closes) == 0:
                # 遇到停牌的情况
                price = await self.get_close_price(sec, frames[-1], fq=fq)
                if price is None:
                    result[sec] = None
                else:
                    result[sec] = np.array(
                        [(f, price) for f in frames], dtype=close_dtype
                    )
                continue

            closes["close"] = array_math_round(closes["close"], 2)
            closes["frame"] = [item.date() for item in closes["frame"]]

            # find missed frames, using left fill
            missed = np.setdiff1d(frames, closes["frame"])
            if len(missed):
                missed = np.array(
                    [(f, np.nan) for f in missed],
                    dtype=close_dtype,
                )
                closes = np.concatenate([closes, missed])
                closes = np.sort(closes, order="frame")
                closes["close"] = fill_nan(closes["close"])

            result[sec] = closes

        return result
    except Exception:
        logger.warning("get_close_price failed for %s:%s - %s", secs, start, end)
        raise

get_close_price(self, sec, date, fq=False) async

获取证券品种在date日期的收盘价

Parameters:

Name Type Description Default
sec str

证券代码

required
date date

日期

required
fq

是否进行前复权

False

Returns:

Type Description
float

secdate日的收盘价

Source code in backtest/feed/zillionarefeed.py
async def get_close_price(self, sec: str, date: datetime.date, fq=False) -> float:
    try:
        bars = await Stock.get_bars(sec, 1, FrameType.DAY, date, fq=fq)
        if len(bars):
            return math_round(bars[-1]["close"].item(), 2)
        else:
            bars = await Stock.get_bars(sec, 500, FrameType.DAY, date, fq=fq)
            return math_round(bars[-1]["close"].item(), 2)
    except Exception as e:
        logger.exception(e)
        logger.warning("get_close_price failed for %s:%s", sec, date)

    return None

get_dr_factor(self, secs, frames) async

股票在[start,end]间的每天的复权因子,使用start日进行归一化处理

注意实现者必须保证,复权因子的长度与日期的长度相同且完全对齐。如果遇到停牌的情况,应该进行相应的填充。

Parameters:

Name Type Description Default
secs Union[str, List[str]]

股票代码

required
frames List[datetime.date]

日期列表

required

Returns:

Type Description
Dict[str, numpy.ndarray]

返回一个dict

Source code in backtest/feed/zillionarefeed.py
async def get_dr_factor(
    self, secs: Union[str, List[str]], frames: List[datetime.date]
) -> Dict[str, np.ndarray]:
    try:
        result = {}
        async for sec, bars in Stock.batch_get_day_level_bars_in_range(
            secs, FrameType.DAY, frames[0], frames[-1], fq=False
        ):
            factors = bars[["frame", "factor"]].astype(
                [("frame", "O"), ("factor", "<f4")]
            )

            # find missed frames, using left fill
            missed = np.setdiff1d(
                frames, [item.item().date() for item in bars["frame"]]
            )
            if len(missed):
                missed = np.array(
                    [(f, np.nan) for f in missed],
                    dtype=[("frame", "datetime64[s]"), ("factor", "<f4")],
                )
                factors = np.concatenate([factors, missed])
                factors = np.sort(factors, order="frame")

            if all(np.isnan(factors["factor"])):
                factors["factor"] = [1.0] * len(factors)
            else:
                factors["factor"] = fill_nan(factors["factor"])

            result[sec] = factors["factor"] / factors["factor"][0]
        return result
    except Exception as e:
        logger.exception(e)
        logger.warning(
            "get_dr_factor failed for %s:%s ~ %s", secs, frames[0], frames[-1]
        )
        raise

get_price_for_match(self, security, start) async

获取从start之后起当天所有的行情数据,用以撮合

这里没有要求指定行情数据的时间帧类型,理论上无论从tick级到日线级,backtest都能支持。返回的数据至少要包括framepricevolume三列。

Parameters:

Name Type Description Default
security

证券代码

required
start

起始时间

required

Returns:

Type Description
ndarray

a numpy array which dtype is match_data_dtype

Source code in backtest/feed/zillionarefeed.py
async def get_price_for_match(
    self, security: str, start: datetime.datetime
) -> np.ndarray:
    end = datetime.datetime.combine(start.date(), datetime.time(15))
    bars = await Stock.get_bars(security, 240, FrameType.MIN1, end)
    if start.hour * 60 + start.minute <= 571:  # 09:31
        bars[0]["close"] = bars[0]["open"]

    return bars[bars["frame"] >= start][["frame", "close", "volume"]].astype(
        match_data_dtype
    )

get_trade_price_limits(self, sec, date) async

获取证券的交易价格限制

获取证券secdate日期的交易价格限制。

Parameters:

Name Type Description Default
sec

证券代码

required
date

日期

required

Returns:

Type Description
ndarray

交易价格限制,元组,(日期,涨停价,跌停价)

Source code in backtest/feed/zillionarefeed.py
async def get_trade_price_limits(self, sec: str, date: datetime.date) -> np.ndarray:
    prices = await Stock.get_trade_price_limits(sec, date, date)

    if len(prices):
        return prices[0]
    else:
        logger.warning("get_trade_price_limits failed for %s:%s", sec, date)
        raise EntrustError(EntrustError.NODATA, security=sec, time=date)