您的当前位置:首页正文

zipline中扩展上交所交易日历SHStockCalendar

2024-11-27 来源:个人技术集锦

1 获取非固定假期

国内市场主要包含上海证券交易所、深圳证券交易所、香港证券交易所、全国中小企业股份转让系统有限公司、中国金融期货交易所、上海商品期货交易所、郑州商品期货交易所、大连商品期货交易所等,由于国内市场的节假日时间信息不像美国的有比较明确的规则,我们通过adhoc_holidays把所有的非固定假期进行枚举,简单有效。当然,这种枚举的方式要定期对数据进行更新以确保准确性。有兴趣的也可以试试自己整理国内市场节假日的规则。
下面我们就以国内股票交易市场(上交所)为例。

1.1 获取股票的历史非固定假期

我们先从万得WindPy中获取国内股票市场的交易日(我们要注意下结束日期到当前年份结束,超过的时间数据不准确,使用时请自己验证下),然后通过过周一至周五的时间序列减去交易日,就可以得到国内市场的历史非固定假期。

1.2 w.tdays接口

万得WindPy的w.tdays接口用于获取区间内日期序列。具体信息大家可以参考WindPy的接口手册。

1.3 参考代码

下面是通过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"				# 19901219日上海证券交易所开始正式营业
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')

2 自定义上交所交易日历

我们参考utils\calendars\us_futures_calendar.py来自定义继承 TradingCalendar类的上海股票市场的交易日历TradingCalendar类。

2.1 扩展上交所规则

首先,我们创建utils\calendars\exchange_calendar_sh.py文件,根据国内外市场的差异,结合上交所来定义一些规则。

扩展上交所的盘前集合竞价时间:

# 上交所盘前集合竞价时间
call_auction_start = time(9, 15)	# 915
call_auction_end = time(9, 25)		# 925

扩展上交所的午休时间:

# 上海证券交易所中午休息时间
lunch_break_start = time(11, 30)	# 1130
lunch_break_end = time(13, 1)		# 1300

扩展上交所科创板的盘后交易时间:

# 上交所科创板的盘后固定交易时间
after_close_time = time(15, 30)		# 1530

定义上交所交易日历的开始时间:

start_default = pd.Timestamp('1990-12-19', tz='UTC')	# 上海证券交易所开始正式营业时间

交易日历的结束时间与美国市场可以一致,我们使用trading_calendark中的end_default

from zipline.utils.calendars.trading_calendar import (
    end_default
)

2.2 SHStockCalendar类

我们继承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")

3 使用SHExchangeCalendar

打开\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")

4 通过crontab更新历史非固定假期

打开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',
        ],
    },
显示全文