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 |
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 |
|
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都能支持。返回的数据至少要包括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) -> 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 |
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 |
|
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都能支持。返回的数据至少要包括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 EntrustError(EntrustError.NODATA, security=sec, time=date)