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], start: datetime.date, end: datetime.date, fq=False
    ) -> pd.DataFrame:
        """获取多个证券在[start, end]期间的收盘价。

        如果股票在[start,end]期间停牌,返回值将使用`ffill`填充。如果在`start`当天停牌,则将调用`get_close_price`取`start`前一期的收盘价。

        返回值示例:

        |          日期       | 000001.XSHE | 600000.XSHG |
        |--------------------|-------------|-------------|
        |         2022-03-01 |        9.51 |        4.56 |
        |         2022-03-02 |          10 |  5          |


        Args:
            secs: 证券代码列表
            start: 起始日期
            end: 截止日期
            fq: 是否复权。

        Returns:
            a dataframe which frames is index (sorted) and each sec as columns
        """
        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],
        normalized: bool = True,
    ) -> pd.DataFrame:
        """股票在[start,end]间的每天的复权因子,使用start日进行归一化处理

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

        Args:
            secs: 证券列表。如果传入str,则将会转换为列表
            frames: 待取日期。注意很多时候,可能需要传入起始日期之前的那个日期,以便对复权因子进行归一化,而不丢失信息。
            normalized: 传回的复权因子是否归一化到frames[0]
        Returns:
            返回复权因子DataFrame,其中secs为列,frames为index,每一个cell为该股该天的复权因子
        """
        raise NotImplementedError

batch_get_close_price_in_range(self, secs, start, end, fq=False) async

获取多个证券在[start, end]期间的收盘价。

如果股票在[start,end]期间停牌,返回值将使用ffill填充。如果在start当天停牌,则将调用get_close_pricestart前一期的收盘价。

返回值示例:

日期 000001.XSHE 600000.XSHG
2022-03-01 9.51 4.56
2022-03-02 10 5

Parameters:

Name Type Description Default
secs List[str]

证券代码列表

required
start date

起始日期

required
end date

截止日期

required
fq

是否复权。

False

Returns:

Type Description
DataFrame

a dataframe which frames is index (sorted) and each sec as columns

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

    如果股票在[start,end]期间停牌,返回值将使用`ffill`填充。如果在`start`当天停牌,则将调用`get_close_price`取`start`前一期的收盘价。

    返回值示例:

    |          日期       | 000001.XSHE | 600000.XSHG |
    |--------------------|-------------|-------------|
    |         2022-03-01 |        9.51 |        4.56 |
    |         2022-03-02 |          10 |  5          |


    Args:
        secs: 证券代码列表
        start: 起始日期
        end: 截止日期
        fq: 是否复权。

    Returns:
        a dataframe which frames is index (sorted) and each sec as columns
    """
    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, normalized=True) async

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

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

Parameters:

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

证券列表。如果传入str,则将会转换为列表

required
frames List[datetime.date]

待取日期。注意很多时候,可能需要传入起始日期之前的那个日期,以便对复权因子进行归一化,而不丢失信息。

required
normalized bool

传回的复权因子是否归一化到frames[0]

True

Returns:

Type Description
DataFrame

返回复权因子DataFrame,其中secs为列,frames为index,每一个cell为该股该天的复权因子

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

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

    Args:
        secs: 证券列表。如果传入str,则将会转换为列表
        frames: 待取日期。注意很多时候,可能需要传入起始日期之前的那个日期,以便对复权因子进行归一化,而不丢失信息。
        normalized: 传回的复权因子是否归一化到frames[0]
    Returns:
        返回复权因子DataFrame,其中secs为列,frames为index,每一个cell为该股该天的复权因子
    """
    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
    ) -> Optional[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], start: datetime.date, end: datetime.date, fq=False
    ) -> pd.DataFrame:
        if len(secs) == 0:
            raise ValueError("No securities provided")

        frames = [tf.int2date(f) for f in tf.get_frames(start, end, FrameType.DAY)]
        sec_dfs = [pd.DataFrame([], index=frames)]
        try:
            async for sec, values in Stock.batch_get_day_level_bars_in_range(
                secs, FrameType.DAY, start, end, fq=fq
            ):
                if len(values) > 0:  # 停牌的情况
                    close = array_math_round(values["close"], 2)  # type: ignore
                else:
                    close = []
                df = pd.DataFrame(
                    data=close,
                    columns=[sec],
                    index=[v.item().date() for v in values["frame"]],
                )  # type: ignore

                if len(df) == 0 or df.index[0] > start:  # type: ignore
                    close = await self.get_close_price(sec, start)
                    df.loc[start, sec] = close
                sec_dfs.append(df)

            df = pd.concat(sec_dfs, axis=1)
            df.sort_index(inplace=True)
            df.fillna(method="ffill", inplace=True)
            return df
        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 NoData(sec, date)

    async def get_dr_factor(
        self,
        secs: Union[str, List[str]],
        frames: List[datetime.date],
        normalized: bool = True,
    ) -> pd.DataFrame:
        if isinstance(secs, str):
            secs = [secs]
        try:
            dfs = [pd.DataFrame([], index=frames)]
            async for sec, bars in Stock.batch_get_day_level_bars_in_range(
                secs, FrameType.DAY, frames[0], frames[-1], fq=False
            ):
                df = pd.DataFrame(
                    data=bars["factor"],  # type: ignore
                    columns=[sec],
                    index=[v.item().date() for v in bars["frame"]],
                )  # type: ignore
                if normalized:  # fixme: 长时间停牌+复权会导致此处出错,因为iloc[0]可能停牌
                    df[sec] = df[sec] / df.iloc[0][sec]
                dfs.append(df)

            df = pd.concat([pd.DataFrame([], index=frames), *dfs], axis=1)
            df.sort_index(inplace=True)
            # issue 13: 停牌时factor假设为1
            df.iloc[0].fillna(1.0, inplace=True)
            df.ffill(inplace=True)

            return df
        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, start, end, fq=False) async

获取多个证券在[start, end]期间的收盘价。

如果股票在[start,end]期间停牌,返回值将使用ffill填充。如果在start当天停牌,则将调用get_close_pricestart前一期的收盘价。

返回值示例:

日期 000001.XSHE 600000.XSHG
2022-03-01 9.51 4.56
2022-03-02 10 5

Parameters:

Name Type Description Default
secs List[str]

证券代码列表

required
start date

起始日期

required
end date

截止日期

required
fq

是否复权。

False

Returns:

Type Description
DataFrame

a dataframe which frames is index (sorted) and each sec as columns

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

    frames = [tf.int2date(f) for f in tf.get_frames(start, end, FrameType.DAY)]
    sec_dfs = [pd.DataFrame([], index=frames)]
    try:
        async for sec, values in Stock.batch_get_day_level_bars_in_range(
            secs, FrameType.DAY, start, end, fq=fq
        ):
            if len(values) > 0:  # 停牌的情况
                close = array_math_round(values["close"], 2)  # type: ignore
            else:
                close = []
            df = pd.DataFrame(
                data=close,
                columns=[sec],
                index=[v.item().date() for v in values["frame"]],
            )  # type: ignore

            if len(df) == 0 or df.index[0] > start:  # type: ignore
                close = await self.get_close_price(sec, start)
                df.loc[start, sec] = close
            sec_dfs.append(df)

        df = pd.concat(sec_dfs, axis=1)
        df.sort_index(inplace=True)
        df.fillna(method="ffill", inplace=True)
        return df
    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
Optional[float]

secdate日的收盘价

Source code in backtest/feed/zillionarefeed.py
async def get_close_price(
    self, sec: str, date: datetime.date, fq=False
) -> Optional[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, normalized=True) async

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

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

Parameters:

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

证券列表。如果传入str,则将会转换为列表

required
frames List[datetime.date]

待取日期。注意很多时候,可能需要传入起始日期之前的那个日期,以便对复权因子进行归一化,而不丢失信息。

required
normalized bool

传回的复权因子是否归一化到frames[0]

True

Returns:

Type Description
DataFrame

返回复权因子DataFrame,其中secs为列,frames为index,每一个cell为该股该天的复权因子

Source code in backtest/feed/zillionarefeed.py
async def get_dr_factor(
    self,
    secs: Union[str, List[str]],
    frames: List[datetime.date],
    normalized: bool = True,
) -> pd.DataFrame:
    if isinstance(secs, str):
        secs = [secs]
    try:
        dfs = [pd.DataFrame([], index=frames)]
        async for sec, bars in Stock.batch_get_day_level_bars_in_range(
            secs, FrameType.DAY, frames[0], frames[-1], fq=False
        ):
            df = pd.DataFrame(
                data=bars["factor"],  # type: ignore
                columns=[sec],
                index=[v.item().date() for v in bars["frame"]],
            )  # type: ignore
            if normalized:  # fixme: 长时间停牌+复权会导致此处出错,因为iloc[0]可能停牌
                df[sec] = df[sec] / df.iloc[0][sec]
            dfs.append(df)

        df = pd.concat([pd.DataFrame([], index=frames), *dfs], axis=1)
        df.sort_index(inplace=True)
        # issue 13: 停牌时factor假设为1
        df.iloc[0].fillna(1.0, inplace=True)
        df.ffill(inplace=True)

        return df
    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 NoData(sec, date)