欺骗开盘价
原文:
www.backtrader.com/blog/posts/2017-05-01-cheat-on-open/cheat-on-open/
发布1.9.44.116
版本增加了对Cheat-On-Open
的支持。这似乎是人们要求的功能,他们在某个条的结束后进行了计算,但期望与open
价格匹配
当开盘价格出现间隙(向上或向下,取决于buy
或sell
是否生效)且现金不足以进行全仓操作时,这种用例将失败。这迫使经纪人拒绝操作
尽管人们可以尝试通过积极的[1]
索引方法来预测未来,但这需要预加载数据,而这并不总是可用的
这种模式:
cerebro = bt.Cerebro(cheat_on_open=True)
这:
- 在系统中激活了一个额外的周期,该周期调用策略中的
next_open
、nextstart_open
和prenext_open
方法
决定增加另一组方法是为了明确区分基于检查的价格不再可用且未来未知的常规方法和作弊模式中的操作
这也避免了对常规next
方法的两次调用
在xxx_open
方法内部时,以下内容为真:
- 指标尚未重新计算,并保留了在等效的
xxx
常规方法中上一个周期中看到的值 - 经纪人尚未评估新周期的待处理订单,可以引入新订单,如果可能的话将进行评估
请注意:
Cerebro
还有一个broker_coo
(默认为True
)参数,告诉 cerebro,如果激活了cheat-on-open
,则尽可能在经纪人中也激活它
模拟经纪人有一个名为:coo
的参数和一个名为set_coo
的方法
尝试欺骗开盘价
下面的示例具有具有 2 种不同行为的策略:
- 如果cheat-on-open为True,则只会从
next_open
操作 - 如果cheat-on-open为False,则只会从
next
操作
在两种情况下,匹配价格必须是相同的
- 如果不作弊,则订单在前一天结束时发布,并将与下一个到来的价格匹配,即
open
价格 - 如果作弊,则订单在执行当天发布。因为订单是在经纪人评估订单之前发布的,所以它也将与下一个到来的价格匹配,即
open
价格
这种第二种情况,允许计算全仓策略的确切赌注,因为可以直接访问当前的open
价格
在两种情况下
- 当前的
open
和close
价格将从next
中打印出来
常规执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=False ... 2005-04-07 next, open 3073.4 close 3090.72 2005-04-08 next, open 3092.07 close 3088.92 Strat Len 68 2005-04-08 Send Buy, fromopen False, close 3088.92 2005-04-11 Buy Executed at price 3088.47 2005-04-11 next, open 3088.47 close 3080.6 2005-04-12 next, open 3080.42 close 3065.18 ...
订单:
- 在 2005-04-08 close之后发布
- 在 2005-04-11 以
3088.47
的open
价格执行
欺骗执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=True ... 2005-04-07 next, open 3073.4 close 3090.72 2005-04-08 next, open 3092.07 close 3088.92 2005-04-11 Send Buy, fromopen True, close 3080.6 2005-04-11 Buy Executed at price 3088.47 2005-04-11 next, open 3088.47 close 3080.6 2005-04-12 next, open 3080.42 close 3065.18 ...
订单:
- 在 2005 年 04 月 11 日在开盘之前发布
- 它在 2005 年 04 月 11 日以
3088.47
的open
价格执行。
而且在图表上看到的整体结果也是相同的。
结论
在开盘前作弊允许在开盘前发布订单,这样可以例如允许精确计算all-in情景的赌注。
样本用法
$ ./cheat-on-open.py --help usage: cheat-on-open.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] Cheat-On-Open Sample optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: ../../datas/2005-2006-day-001.txt) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: ) --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: )
样本来源
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt class St(bt.Strategy): params = dict( periods=[10, 30], matype=bt.ind.SMA, ) def __init__(self): self.cheating = self.cerebro.p.cheat_on_open mas = [self.p.matype(period=x) for x in self.p.periods] self.signal = bt.ind.CrossOver(*mas) self.order = None def notify_order(self, order): if order.status != order.Completed: return self.order = None print('{} {} Executed at price {}'.format( bt.num2date(order.executed.dt).date(), 'Buy' * order.isbuy() or 'Sell', order.executed.price) ) def operate(self, fromopen): if self.order is not None: return if self.position: if self.signal < 0: self.order = self.close() elif self.signal > 0: print('{} Send Buy, fromopen {}, close {}'.format( self.data.datetime.date(), fromopen, self.data.close[0]) ) self.order = self.buy() def next(self): print('{} next, open {} close {}'.format( self.data.datetime.date(), self.data.open[0], self.data.close[0]) ) if self.cheating: return self.operate(fromopen=False) def next_open(self): if not self.cheating: return self.operate(fromopen=True) 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) # Data feed data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs) cerebro.adddata(data0) # 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=( 'Cheat-On-Open Sample' ) ) parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt', required=False, help='Data to read in') # Defaults for dates parser.add_argument('--fromdate', required=False, default='', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--todate', required=False, default='', 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') return parser.parse_args(pargs) if __name__ == '__main__': runstrat()
交易日历
原文:
www.backtrader.com/blog/posts/2017-04-16-trading-calendar/tradingcalendar/
发行版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 '''
每个数据源
通过在addcalendar
中描述的相同约定指定calendar
参数。
例如:
... 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^(st)日期是策略进行的会计。第 2^(nd)日期是每日的。
周末如预期地在 2016-03-24(星期四)结束,但没有交易日历,重采样代码无法知道,因此传递了日期为 2016-03-18(前一周)的重采样条。当交易转移到 2016-03-28(星期一)时,重新采样器检测到周转变并在同一天传递了日期为 2016-03-24 的重采样条。
使用PandasMarketCalendar
运行相同的操作,但用于NYSE
(并添加绘图)
$ ./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 的柱子),每日柱子就被交付,因为交易日历告诉重新采样代码这一天已经结束。
让我们添加一个精心制作的定义。与以前相同,但通过一些 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 分钟柱子一起交付。
BackTrader 中文文档(十七)(2)https://developer.aliyun.com/article/1505385