交易日历
发布1.9.42.116
版本添加了对交易日历的支持。在例如以下情况下重采样时,这很有用:
- 现在,从每日到每周的重采样可以将每周柱与周的最后一根柱一起交付。
这是因为交易日历可以确定下一个交易日和上周最后一个交易日可以提前识别 - 当会话结束时间不是常规时间时(这已经可以指定给数据源)时,从子日到每日的重采样
交易日历接口
有一个基类TradingCalendarBase
,用作任何交易日历的基础。它定义了必须被覆盖的两个(2)方法:
class TradingCalendarBase(with_metaclass(MetaParams, object)): def _nextday(self, day): ''' Returns the next trading day (datetime/date instance) after ``day`` (datetime/date instance) and the isocalendar components The return value is a tuple with 2 components: (nextday, (y, w, d)) where (y, w, d) ''' raise NotImplementedError def schedule(self, day): ''' Returns a tuple with the opening and closing times (``datetime.time``) for the given ``date`` (``datetime/date`` instance) ''' raise NotImplementedError
实施
PandasMarketCalendar
此实现基于一个很好的包,该包是 Quantopian 初始功能的分支。该软件包位于:pandas_market_calendars 并且可以轻松安装:
pip install pandas_market_calendars
该实现具有以下接口:
class PandasMarketCalendar(TradingCalendarBase): ''' Wrapper of ``pandas_market_calendars`` for a trading calendar. The package ``pandas_market_calendar`` must be installed Params: - ``calendar`` (default ``None``) The param ``calendar`` accepts the following: - string: the name of one of the calendars supported, for example `NYSE`. The wrapper will attempt to get a calendar instance - calendar instance: as returned by ``get_calendar('NYSE')`` - ``cachesize`` (default ``365``) Number of days to cache in advance for lookup See also: - https://github.com/rsheftel/pandas_market_calendars - http://pandas-market-calendars.readthedocs.io/ ''' params = ( ('calendar', None), # A pandas_market_calendars instance or exch name ('cachesize', 365), # Number of days to cache in advance )
交易日历
此实现允许通过指定节假日、提前天、非交易工作日和开放和关闭会话时间来构建日历:
class TradingCalendar(TradingCalendarBase): ''' Wrapper of ``pandas_market_calendars`` for a trading calendar. The package ``pandas_market_calendar`` must be installed Params: - ``open`` (default ``time.min``) Regular start of the session - ``close`` (default ``time.max``) Regular end of the session - ``holidays`` (default ``[]``) List of non-trading days (``datetime.datetime`` instances) - ``earlydays`` (default ``[]``) List of tuples determining the date and opening/closing times of days which do not conform to the regular trading hours where each tuple has (``datetime.datetime``, ``datetime.time``, ``datetime.time`` ) - ``offdays`` (default ``ISOWEEKEND``) A list of weekdays in ISO format (Monday: 1 -> Sunday: 7) in which the market doesn't trade. This is usually Saturday and Sunday and hence the default ''' params = ( ('open', time.min), ('close', _time_max), ('holidays', []), # list of non trading days (date) ('earlydays', []), # list of tuples (date, opentime, closetime) ('offdays', ISOWEEKEND), # list of non trading (isoweekdays) )
使用模式
全球交易日历
通过Cerebro
可以添加一个全局日历,该日历是所有数据源的默认日历,除非为数据源指定了一个:
def addcalendar(self, cal): '''Adds a global trading calendar to the system. Individual data feeds may have separate calendars which override the global one ``cal`` can be an instance of ``TradingCalendar`` a string or an instance of ``pandas_market_calendars``. A string will be will be instantiated as a ``PandasMarketCalendar`` (which needs the module ``pandas_market_calendar`` installed in the system. If a subclass of `TradingCalendarBase` is passed (not an instance) it will be instantiated '''
每个数据源
通过指定一个calendar
参数,按照上面addcalendar
中描述的相同约定。
例如:
... data = bt.feeds.YahooFinanceData(dataname='YHOO', calendar='NYSE', ...) cerebro.adddata(data) ...
示例
从每日到每周
让我们看看下面可以找到的代码示例的样本运行。2016 年,复活节星期五(2016-03-25)也是NYSE
的节假日。如果在没有交易日历的情况下运行示例,请看看该日期附近发生了什么。
在这种情况下,从每日重采样到每周(使用YHOO
和 2016 年的每日数据):
$ ./tcal.py ... Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11 datetime 2016-03-18 Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 11 datetime 2016-03-18 Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12 datetime 2016-03-24 ...
在这个输出中,第 1 个日期是策略的结算日期。第 2 个日期是每日的日期
一周结束时,如预期的那样,于 2016-03-24(星期四),但是如果没有交易日历,重采样代码就无法知道,因此将于 2016-03-18(前一周)交付重采样的柱形。当交易移至 2016-03-28(星期一)时,重采样器检测到周变化,并于 2016-03-24 交付重采样的柱。
同样的但是使用NYSE
的PandasMarketCalendar
运行(并添加绘图)
$ ./tcal.py --plot --pandascal NYSE ... Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11 datetime 2016-03-18 Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 12 datetime 2016-03-24 Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12 datetime 2016-03-24 ...
有一个变化!由于日历,重采样器知道周在 2016-03-24 结束,并于同一天交付了相应的每周重采样柱 2016-03-24。
以及绘图。
由于并非每个市场都可能提供相同的信息,因此人们也可以自己制定日历。对于NYSE
和2016
年,情况如下:
class NYSE_2016(bt.TradingCalendar): params = dict( holidays=[ datetime.date(2016, 1, 1), datetime.date(2016, 1, 18), datetime.date(2016, 2, 15), datetime.date(2016, 3, 25), datetime.date(2016, 5, 30), datetime.date(2016, 7, 4), datetime.date(2016, 9, 5), datetime.date(2016, 11, 24), datetime.date(2016, 12, 26), ] )
复活节星期五(2016-03-25)被列为假日之一。现在运行示例:
$ ./tcal.py --plot --owncal ... Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11 datetime 2016-03-18 Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 12 datetime 2016-03-24 Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12 datetime 2016-03-24 ...
并且使用精心制作的日历定义得到了相同的结果。
分钟转换为每日
使用一些私有的日内数据,并且知道 2016-11-25(感恩节后的第二天,市场在US/Eastern
时区于 13:00 关闭),另一个测试运行,这次是用第二个样本。
注意
源数据直接来自显示的数据,处于CET
时区,即使涉及的资产YHOO
在美国交易。代码中使用了tzinput='CET'
和tz='US/Eastern'
的数据提要,以便让平台适当地转换输入和显示输出
首先没有交易日历
$ ./tcal-intra.py ... Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 21 datetime 2016-11-23 16:00:00 Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 21 datetime 20 16-11-23 16:00:00 Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 22 datetime 2016-11-25 16:00:00 Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 22 datetime 2016-11-25 16:00:00 ...
如预期的那样,交易日在13:00
提前关闭,但重新采样器不知道这一点(官方交易结束时间为16:00
),并继续提供上一交易日(2016-11-23)的重新采样日线柱形图,新的重新采样日线柱形图首次在下一个交易日(2016-11-28)中交付,日期为 2016-11-25。
注意
数据在13:01
有一个额外的分钟柱形图,这可能是由于市场闭市后的拍卖过程提供了最后一个价格。
我们可以向流添加一个过滤器,以过滤掉会话时间之外的柱形图(该过滤器将从交易日历中找到)
但这不是这个示例的重点。
使用PandasMarketCalendar
实例进行相同运行:
$ ./tcal-intra.py --pandascal NYSE ... Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 15 datetime 2016-11-25 13:00:00 Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 15 datetime 2016-11-25 13:00:00 Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 15 datetime 2016-11-25 13:00:00 Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 15 datetime 2016-11-25 13:00:00 ...
现在,当每日 1 分钟的数据源命中 2016-11-25 的 13:00 时(我们忽略 13:01 柱形图),2016-11-25 的每日柱形图就被交付了,因为交易日历告诉重新采样代码该天已结束。
让我们添加一个精心制作的定义。与之前相同,但扩展了一些earlydays
class NYSE_2016(bt.TradingCalendar): params = dict( holidays=[ datetime.date(2016, 1, 1), datetime.date(2016, 1, 18), datetime.date(2016, 2, 15), datetime.date(2016, 3, 25), datetime.date(2016, 5, 30), datetime.date(2016, 7, 4), datetime.date(2016, 9, 5), datetime.date(2016, 11, 24), datetime.date(2016, 12, 26), ], earlydays=[ (datetime.date(2016, 11, 25), datetime.time(9, 30), datetime.time(13, 1)) ], open=datetime.time(9, 30), close=datetime.time(16, 0), )
运行:
$ ./tcal-intra.py --owncal ... Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 15 datetime 2016-11-23 16:00:00 Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 16 datetime 2016-11-25 13:01:00 Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 16 datetime 2016-11-25 13:01:00 Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 16 datetime 2016-11-25 13:01:00 ...
热心的读者会注意到,精心制作的定义包含将13:01
(使用datetime.time(13, 1)
)定义为我们 2016-11-25 的短日的会话结束。这只是为了展示精心制作的TradingCalendar
如何帮助调整事物。
现在 2016-11-25 的每日重新采样柱形图与 13:01 的 1 分钟柱形图一起交付。
策略的额外奖金
第一个datetime
,属于策略的时间,总是在一个不同的时区,实际上是UTC
。同样在这个版本1.9.42.116
中可以同步。以下参数已添加到Cerebro
(在实例化期间或使用cerebro.run
期间使用)
- ``tz`` (default: ``None``) Adds a global timezone for strategies. The argument ``tz`` can be - ``None``: in this case the datetime displayed by strategies will be in UTC, which has been always the standard behavior - ``pytz`` instance. It will be used as such to convert UTC times to the chosen timezone - ``string``. Instantiating a ``pytz`` instance will be attempted. - ``integer``. Use, for the strategy, the same timezone as the corresponding ``data`` in the ``self.datas`` iterable (``0`` would use the timezone from ``data0``)
还支持cerebro.addtz
方法:
def addtz(self, tz): ''' This can also be done with the parameter ``tz`` Adds a global timezone for strategies. The argument ``tz`` can be - ``None``: in this case the datetime displayed by strategies will be in UTC, which has been always the standard behavior - ``pytz`` instance. It will be used as such to convert UTC times to the chosen timezone - ``string``. Instantiating a ``pytz`` instance will be attempted. - ``integer``. Use, for the strategy, the same timezone as the corresponding ``data`` in the ``self.datas`` iterable (``0`` would use the timezone from ``data0``) '''
重复上次日内示例的运行,并在tz
中使用0
(与data0
的时区同步),以下是关注相同日期和时间的输出:
$ ./tcal-intra.py --owncal --cerebro tz=0 ... Strategy len 6838 datetime 2016-11-25 13:00:00 Data0 len 6838 datetime 2016-11-25 13:00:00 Data1 len 15 datetime 2016-11-23 16:00:00 Strategy len 6839 datetime 2016-11-25 13:01:00 Data0 len 6839 datetime 2016-11-25 13:01:00 Data1 len 16 datetime 2016-11-25 13:01:00 Strategy len 6840 datetime 2016-11-28 09:31:00 Data0 len 6840 datetime 2016-11-28 09:31:00 Data1 len 16 datetime 2016-11-25 13:01:00 Strategy len 6841 datetime 2016-11-28 09:32:00 Data0 len 6841 datetime 2016-11-28 09:32:00 Data1 len 16 datetime 2016-11-25 13:01:00 ...
时间戳现在是时区对齐的。
示例用法(tcal.py)
$ ./tcal.py --help usage: tcal.py [-h] [--data0 DATA0] [--offline] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] [--pandascal PANDASCAL | --owncal] [--timeframe {Weeks,Months,Years}] Trading Calendar Sample optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: YHOO) --offline Read from disk with same name as ticker (default: False) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: 2016-01-01) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: 2016-12-31) --cerebro kwargs kwargs in key=value format (default: ) --broker kwargs kwargs in key=value format (default: ) --sizer kwargs kwargs in key=value format (default: ) --strat kwargs kwargs in key=value format (default: ) --plot [kwargs] kwargs in key=value format (default: ) --pandascal PANDASCAL Name of trading calendar to use (default: ) --owncal Apply custom NYSE 2016 calendar (default: False) --timeframe {Weeks,Months,Years} Timeframe to resample to (default: Weeks)
示例用法(tcal-intra.py)
$ ./tcal-intra.py --help usage: tcal-intra.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] [--pandascal PANDASCAL | --owncal] [--timeframe {Days}] Trading Calendar Sample optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: yhoo-2016-11.csv) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: 2016-01-01) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: 2016-12-31) --cerebro kwargs kwargs in key=value format (default: ) --broker kwargs kwargs in key=value format (default: ) --sizer kwargs kwargs in key=value format (default: ) --strat kwargs kwargs in key=value format (default: ) --plot [kwargs] kwargs in key=value format (default: ) --pandascal PANDASCAL Name of trading calendar to use (default: ) --owncal Apply custom NYSE 2016 calendar (default: False) --timeframe {Days} Timeframe to resample to (default: Days)
示例代码(tcal.py)
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt class NYSE_2016(bt.TradingCalendar): params = dict( holidays=[ datetime.date(2016, 1, 1), datetime.date(2016, 1, 18), datetime.date(2016, 2, 15), datetime.date(2016, 3, 25), datetime.date(2016, 5, 30), datetime.date(2016, 7, 4), datetime.date(2016, 9, 5), datetime.date(2016, 11, 24), datetime.date(2016, 12, 26), ] ) class St(bt.Strategy): params = dict( ) def __init__(self): pass def start(self): self.t0 = datetime.datetime.utcnow() def stop(self): t1 = datetime.datetime.utcnow() print('Duration:', t1 - self.t0) def prenext(self): self.next() def next(self): print('Strategy len {} datetime {}'.format( len(self), self.datetime.date()), end=' ') print('Data0 len {} datetime {}'.format( len(self.data0), self.data0.datetime.date()), end=' ') if len(self.data1): print('Data1 len {} datetime {}'.format( len(self.data1), self.data1.datetime.date())) else: print() def runstrat(args=None): args = parse_args(args) cerebro = bt.Cerebro() # Data feed kwargs kwargs = dict() # Parse from/to-date dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S' for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']): if a: strpfmt = dtfmt + tmfmt * ('T' in a) kwargs[d] = datetime.datetime.strptime(a, strpfmt) YahooData = bt.feeds.YahooFinanceData if args.offline: YahooData = bt.feeds.YahooFinanceCSVData # change to read file # Data feed data0 = YahooData(dataname=args.data0, **kwargs) cerebro.adddata(data0) d1 = cerebro.resampledata(data0, timeframe=getattr(bt.TimeFrame, args.timeframe)) d1.plotinfo.plotmaster = data0 d1.plotinfo.sameaxis = True if args.pandascal: cerebro.addcalendar(args.pandascal) elif args.owncal: cerebro.addcalendar(NYSE_2016) # Broker cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')')) # Sizer cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')')) # Strategy cerebro.addstrategy(St, **eval('dict(' + args.strat + ')')) # Execute cerebro.run(**eval('dict(' + args.cerebro + ')')) if args.plot: # Plot if requested to cerebro.plot(**eval('dict(' + args.plot + ')')) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=( 'Trading Calendar Sample' ) ) parser.add_argument('--data0', default='YHOO', required=False, help='Data to read in') parser.add_argument('--offline', required=False, action='store_true', help='Read from disk with same name as ticker') # Defaults for dates parser.add_argument('--fromdate', required=False, default='2016-01-01', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--todate', required=False, default='2016-12-31', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--cerebro', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--broker', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--sizer', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--strat', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--plot', required=False, default='', nargs='?', const='{}', metavar='kwargs', help='kwargs in key=value format') pgroup = parser.add_mutually_exclusive_group(required=False) pgroup.add_argument('--pandascal', required=False, action='store', default='', help='Name of trading calendar to use') pgroup.add_argument('--owncal', required=False, action='store_true', help='Apply custom NYSE 2016 calendar') parser.add_argument('--timeframe', required=False, action='store', default='Weeks', choices=['Weeks', 'Months', 'Years'], help='Timeframe to resample to') return parser.parse_args(pargs) if __name__ == '__main__': runstrat()
BackTrader 中文文档(十三)(2)https://developer.aliyun.com/article/1505344