在回测中,单个数据的时间对比一般不涉及到时区的问题,如果加载的多个数据并不是一个时区,在做时间对比的时候,需要做额外的时区转换;在实盘交易的过程中,如果交易所在的时区与交易的证券所在时区不一致,一般也是需要进行时区转换;
import pytz
import bt
# 可以在加载数据的时候,增加响应的时区,这样数据的时间就可以和本地时间进行跨时区对比了
data = bt.feeds.MyFeed('ES-Mini', tz=pytz.timezone('US/Eastern'))
class Strategy(bt.Strategy):
def next(self):
# This will work all year round.
# The data source will return in the frame of the 'US/Eastern' time
# zone and the user is quoting '10:00' as reference time
# Because in the 'US/Eastern' timezone the SPX index always starts
# trading at 09:30, this will always work
if self.data.datetime.time() < datetime.time(10, 0):
# don't operate until the market has been running 30 minutes
return # -
Timer 的作用
-
Timer 基于绝对的时间输入,或者使用 session 开始(start)/结束(end)的时间
-
根据时间的规格,设定时区,可以直接设定,或者通过 pytz 或者通过 data feed
-
根据特定的时间开始 offset
-
重复间隔
-
周日期过滤(并且可以递推到下个交易日)
-
月日期过滤(并且可以递推到下个交易日)
-
常规的回调(callback)过滤
-
-
Timer 的调用方法
可以在策略的 notify_timer 中进行调用
def notify_timer(self, timer, when, *args, **kwargs): '''Receives a timer notification where ``timer`` is the timer which was returned by ``add_timer``, and ``when`` is the calling time. ``args`` and ``kwargs`` are any additional arguments passed to ``add_timer`` The actual ``when`` time can be later, but the system may have not be able to call the timer before. This value is the timer value and not the system time. ''' pass
-
怎么加载 Timer
加载 Timer 可以在 strategy 或者 cerebro 中,一般可以考虑在 strategy 的 init 中使用 add_timer 进行加载
def add_timer(self, when, offset=datetime.timedelta(), repeat=datetime.timedelta(), weekdays=[], weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args, **kwargs): pass
-
Timer 什么时候被调用
Timer 什么时候调用和是否使用 cheat 参数有关,默认情况下,cheat = False,这个时候 Timer 的调用顺序为:
- 在 data feed 已经为当前 bar 加载新的数据之后
- 在 broker 执行订单以及计算账户组合价值之后
- 在指标被重新计算之前
- 在策略(next 方法)被调用之前
如果 cheat 被设置成 True 的话,这个时候 Timer 的调用顺序为:
- 在 data feed 已经为当前 bar 加载新的数据之后
- 在 broker 执行订单以及计算账户组合价值之前
- 在指标被重新计算之前、在策略(next 方法)被调用之前
Timer 的加载顺序,尤其是 cheat = True 的时候,有不少的便利性,这个和 tbquant 的交易模式比较接近。
-
在日线中运行
想要理解在日线中的运行机制,其实只要了解这些参数的意义就可以了
when : 时间,什么时候开始;可以是具体的时间,如 datetime.time(15,30),也可以是 bt.timer.SESSION_START 这些
offset: 向后移的时间
repeat:多久重复调用一次,由于日线只有一个数据,repeat 的时间间隔小于 1 日的时候,将不会起作用
cheat :参数会影响 Timer 运行在什么之前
-
在分钟线中运行
分钟线中运行,参数意义和日线中一样,如果希望能够在 notify_timer 中下单,不仅需要把 cheat 设置成 True,还要把 coo 设置成 True.
具体在日线中和分钟线中运行的细节,可以参考原文或者自己尝试这些参数。
周日期过滤和月日期过滤
在 add_timer 中使用 weekdays=[5]代表只在周五运行 notify_timer,如果周五正好不是交易日,weekcarry=True,那么,notify_timer 的调用会顺延到下个交易日。weekcarry=False,就不会顺延。
如果月日期的过滤也是一样,在 add_timer 中使用 monthdays=[15]代表只在每个月的 15 号运行 notify_timer,如果这个月 15 号正好不是交易日,monthcarry=True,那么,notify_timer 的调用会顺延到下个交易日。monthcarry=False,就不顺延。
其他复杂日期的过滤,可以自定义,比如,每个季度末的第三个周五,运行一次,就需要额外写一个类,赋给 allow,即在策略中使用 self.add_timer(allow =FutOpExp() )
class FutOpExp(object): def __init__(self): self.fridays = 0 self.curmonth = -1 def __call__(self, d): _, _, isowkday = d.isocalendar() if d.month != self.curmonth: self.curmonth = d.month self.fridays = 0 # Mon=1 ... Sun=7 if isowkday == 5 and self.curmonth in [3, 6, 9, 12]: self.fridays += 1 if self.friday == 3: # 3rd Friday return True # timer allowed return False # timer disallowed
交易日历在把数据从高频率转化成低频率(resample)的时候特别有用,比如,用日线合成周线,用分钟线合成日线,当我们知道一天的交易是在什么时候结束,一周的交易日是在那一天结束,我们才能够更加方便的进行 resample.
backtrader 提供了 TradingCalendarBase,这个类提供了两种方法,_nextday 和 schedule,可以让我们进行重写,满足我们的特殊需求
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 -
使用方法
可以在 cerebro 中,使用 addcalendar 增加日历
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 '''
也可以在数据中添加
data = bt.feeds.YahooFinanceData(dataname='YHOO', calendar='NYSE', ...) cerebro.adddata(data)
详细细节参考:如下文章
这一讲主要分享了如何进行时间管理,即跨时区的时间对比,Timer 和 trading calendar。 Timer 在某种意义上提供了很大的方便性,但是在实际中,基本上可以在 next 中自己编程实现。resample 的功能,可以考虑不用使用了,不论是在回测或者实盘中,自己额外多加载一个其他周期的数据,不是更方便,而且速度更快吗?
到这里,基本上除了实盘交易的内容,其他都已经分享完了。实盘交易的内容,会在策略、源码分析之后具体分享,如果大家等不及了,也可以参考官网的教程:
好了,基本教程的分享到这里就告一段落了,接下来该进入不同资产的策略以及源码分析中了。