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_price
取start
前一期的收盘价。
返回值示例:
日期 | 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 |
|
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都能支持。返回的数据至少要包括frame
、price
、volume
三列。
Parameters:
Name | Type | Description | Default |
---|---|---|---|
security |
证券代码 |
required | |
start |
起始时间 |
required |
Returns:
Type | Description |
---|---|
ndarray |
a numpy array which dtype is |
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
¶
获取证券的交易价格限制
获取证券sec
在date
日期的交易价格限制。
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_price
取start
前一期的收盘价。
返回值示例:
日期 | 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] |
|
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都能支持。返回的数据至少要包括frame
、price
、volume
三列。
Parameters:
Name | Type | Description | Default |
---|---|---|---|
security |
证券代码 |
required | |
start |
起始时间 |
required |
Returns:
Type | Description |
---|---|
ndarray |
a numpy array which dtype is |
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
¶
获取证券的交易价格限制
获取证券sec
在date
日期的交易价格限制。
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)