国内市场主要包含上海证券交易所、深圳证券交易所、香港证券交易所、全国中小企业股份转让系统有限公司、中国金融期货交易所、上海商品期货交易所、郑州商品期货交易所、大连商品期货交易所等,由于国内市场的节假日时间信息不像美国的有比较明确的规则,我们通过adhoc_holidays把所有的非固定假期进行枚举,简单有效。当然,这种枚举的方式要定期对数据进行更新以确保准确性。有兴趣的也可以试试自己整理国内市场节假日的规则。
下面我们就以国内股票交易市场(上交所)为例。
我们先从万得WindPy中获取国内股票市场的交易日(我们要注意下结束日期到当前年份结束,超过的时间数据不准确,使用时请自己验证下),然后通过过周一至周五的时间序列减去交易日,就可以得到国内市场的历史非固定假期。
万得WindPy的w.tdays接口用于获取区间内日期序列。具体信息大家可以参考WindPy的接口手册。
下面是通过WindPy的w.tdays接口获取历史非固定假期的参考代码,使用时请对数据进行更新以确保cn_adhoc_holidays.txt内容的准确性,代码文件命名为get_cn_adhoc_holidays.py。
# coding=utf-8
from WindPy import *
from pandas.tseries.offsets import CustomBusinessDay
import pandas as pd
# 结束日期可以超过当前年份,但是从wind得到的数据不准确,准确数据只到当年结束
start_date = "1990-12-19" # 1990年12月19日上海证券交易所开始正式营业
end_base = pd.Timestamp('today', tz='UTC')
end_date = end_base + pd.Timedelta(days=365) # 结束日期:下一年的当天
w.start()
data = w.tdays(start_date, end_date, "Trading") # 获取区间内交易日的日期序列
df = pd.DataFrame(data.Data[0])
start_date, end_date = df[0].iloc[[0, -1]] # 设置开始日期和结束日期
weekmask = 'Mon Tue Wed Thu Fri'
cbday = CustomBusinessDay(weekmask=weekmask) # 自定义工作日:周一至周五
dts = pd.date_range(start_date, end_date, freq=cbday) # 自定义工作日在这段交易日期内的时间范围
# 特定时段的非固定假期 = 自定义工作日在这段交易日期内的时间范围-这段交易日期内的交易日
df_adhoc_holidays = pd.DataFrame({"date": list(set(dts.to_series()) - set(df[0]))}).sort_values('date')
# 将国内市场的非固定假期写入cn_adhoc_holidays.txt
df_adhoc_holidays ['date'].to_csv('cn_adhoc_holidays.txt', index=False, date_format='%Y%m%d')
我们参考utils\calendars\us_futures_calendar.py来自定义继承 TradingCalendar类的上海股票市场的交易日历TradingCalendar类。
首先,我们创建utils\calendars\exchange_calendar_sh.py文件,根据国内外市场的差异,结合上交所来定义一些规则。
扩展上交所的盘前集合竞价时间:
# 上交所盘前集合竞价时间
call_auction_start = time(9, 15) # 9:15
call_auction_end = time(9, 25) # 9:25
扩展上交所的午休时间:
# 上海证券交易所中午休息时间
lunch_break_start = time(11, 30) # 11:30
lunch_break_end = time(13, 1) # 13:00
扩展上交所科创板的盘后交易时间:
# 上交所科创板的盘后固定交易时间
after_close_time = time(15, 30) # 15:30
定义上交所交易日历的开始时间:
start_default = pd.Timestamp('1990-12-19', tz='UTC') # 上海证券交易所开始正式营业时间
交易日历的结束时间与美国市场可以一致,我们使用trading_calendark中的end_default
from zipline.utils.calendars.trading_calendar import (
end_default
)
我们继承TradingCalendar类自定义上交所股票市场的交易日历SHStockCalendar
class SHStockCalendar(TradingCalendar):
现在,我们增加初始化函数来进行基本规则的设置
def __init__(self, start=start_default, end=end_default):
super(SHStockCalendar, self).__init__(start=start, end=end)
# 增加午休时间
self._lunch_break_starts = days_at_time(_all_days, lunch_break_start, self.tz, 0)
self._lunch_break_ends = days_at_time(_all_days, lunch_break_end, self.tz, 0)
# 在sessions中扩展午休时间,每一个sessions中包含开盘/收盘/午休开始/午休结束
self.schedule = pd.DataFrame(
index=_all_days,
columns=['market_open', 'market_close', 'lunch_break_start', 'lunch_break_end'],
data={
'market_open': self._opens,
'market_close': self._closes,
'call_auction_start':self._call_auction_start,
'call_auction_end':self._call_auction_end,
'lunch_break_start': self._lunch_break_starts,
'lunch_break_end': self._lunch_break_ends,
'after_close_time': self._after_close_time
},
dtype='datetime64[ns]',
)
通过名称属性定义上海证券交易所交易日历的名称:
@property
def name(self):
return "cn_sh_stock"
通过时区属性定义使用上海时区
@property
def tz(self):
return pytz.timezone("Asia/Shanghai")
定义上交所的开盘时间9:31
@property
def open_time(self):
return time(9, 31)
定义上交所的收盘时间15:00
@property
def close_time(self):
return time(15, 0)
def _get_from_file(filename, use_list=False):
with open(filename, 'r') as f:
data = f.readlines()
if use_list:
return [int_to_date(str_to_int(i.rstrip('\n'))) for i in data]
else:
return set([int_to_date(str_to_int(i.rstrip('\n'))) for i in data])
if use_list:
return []
else:
return set([])
def _get_adhoc_holidays(use_list=False):
data_file_path = os.path.join(os.path.dirname(__file__), 'cn_adhoc_holidays.txt')
return _get_from_file(data_file_path, use_list)
@property
def adhoc_holidays(self):
return [Timestamp(t, tz=self.tz) for t in _get_adhoc_holidays(use_list=True)]
获取每一个session的分钟数
@lazyval
def _minutes_per_session(self):
# 上午的分钟数=午休开始时间-开盘时间
diff_am = self.schedule.lunch_break_start- self.schedule.market_open
diff_am = diff.astype('timedelta64[m]')
# diff_am + 1
# 下午的分钟数=收盘时间-午休结束时间
diff_pm = self.schedule.market_close - self.schedule.lunch_break_end
diff_pm = diff.astype('timedelta64[m]')
# diff_pm + 1
return diff_am + diff_pm + 2
all_minutes 获取日历中所有分钟的时间索引
@property
@remember_last
def all_minutes(self):
"""
Returns a DatetimeIndex representing all the minutes in this calendar.
"""
opens_in_ns = \
self._opens.values.astype('datetime64[ns]')
closes_in_ns = \
self._closes.values.astype('datetime64[ns]')
# 扩展午休时间
lunch_break_start_in_ns = \
self._lunch_break_starts.values.astype('datetime64[ns]')
lunch_break_ends_in_ns = \
self._lunch_break_ends.values.astype('datetime64[ns]')
deltas_before_lunch = lunch_break_start_in_ns - opens_in_ns # 上午
deltas_after_lunch = closes_in_ns - lunch_break_ends_in_ns # 下午
# 扩展上午分钟线根数
daily_before_lunch_sizes = (deltas_before_lunch / NANOS_IN_MINUTE) + 1
# 扩展下午分钟线根数
daily_after_lunch_sizes = (deltas_after_lunch / NANOS_IN_MINUTE) + 1、
# 全天分钟线根数
daily_sizes = daily_before_lunch_sizes + daily_after_lunch_sizes
num_minutes = np.sum(daily_sizes).astype(np.int64)
all_minutes = np.empty(num_minutes, dtype='datetime64[ns]')
idx = 0
for day_idx, size in enumerate(daily_sizes):
# lots of small allocations, but it's fast enough for now.
# size is a np.timedelta64, so we need to int it
size_int = int(size)
# 扩展上午和下午的时间索引
before_lunch_size_int = int(daily_before_lunch_sizes[day_idx])
after_lunch_size_int = int(daily_after_lunch_sizes[day_idx])
all_minutes[idx:(idx + before_lunch_size_int)] = \
np.arange(
opens_in_ns[day_idx],
lunch_break_start_in_ns[day_idx] + NANOS_IN_MINUTE,
NANOS_IN_MINUTE
)
all_minutes[(idx + before_lunch_size_int):(idx + size_int)] = \
np.arange(
lunch_break_ends_in_ns[day_idx],
closes_in_ns[day_idx] + NANOS_IN_MINUTE,
NANOS_IN_MINUTE
)
idx += size_int
return DatetimeIndex(all_minutes).tz_localize("UTC")
打开\utils\calendars\calendar_utils.py文件,我们在_default_calendar_factories中加入上交所交易日历
_default_calendar_factories = {
'NYSE': NYSEExchangeCalendar,
'CME': CMEExchangeCalendar,
'ICE': ICEExchangeCalendar,
'CFE': CFEExchangeCalendar,
'BMF': BMFExchangeCalendar,
'LSE': LSEExchangeCalendar,
'TSX': TSXExchangeCalendar,
'us_futures': QuantopianUSFuturesCalendar,
'cn_sh_stock': SHExchangeCalendar,
}
也可以通过register_calendar来注册
register_calendar("cn_sh_stock", SHExchangeCalendar(), force=True)
后面我们可以通过get_calendar来使用
cn_calendar_=get_calendar("cn_sh_stock")
打开setup.py,我们在setup的entry_points中加入cn-adhoc-holidays-sync=cn_stock.get_cn_adhoc_holidays
这样我们可以在crontab中增加cn-adhoc-holidays-sync来更新cn-adhoc-holidays.txt文件
setup(
name='zipline',
url="http://zipline.io",
version=versioneer.get_version(),
cmdclass=LazyBuildExtCommandClass(versioneer.get_cmdclass()),
description='A backtester for financial algorithms.',
entry_points={
'console_scripts': [
'zipline = zipline.__main__:main',
'cn-adhoc-holidays-sync=cn_stock.get_cn_adhoc_holidays',
],
},