Skip to content

Latest commit

 

History

History
212 lines (141 loc) · 8.72 KB

File metadata and controls

212 lines (141 loc) · 8.72 KB

54、backtrader 的一些基本概念---如何进行时间管理?

原文:https://yunjinqi.blog.csdn.net/article/details/115284989

时间对比

在回测中,单个数据的时间对比一般不涉及到时区的问题,如果加载的多个数据并不是一个时区,在做时间对比的时候,需要做额外的时区转换;在实盘交易的过程中,如果交易所在的时区与交易的证券所在时区不一致,一般也是需要进行时区转换;

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 的作用

    • 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 的功能,可以考虑不用使用了,不论是在回测或者实盘中,自己额外多加载一个其他周期的数据,不是更方便,而且速度更快吗?

到这里,基本上除了实盘交易的内容,其他都已经分享完了。实盘交易的内容,会在策略、源码分析之后具体分享,如果大家等不及了,也可以参考官网的教程:

IB 实盘交易

自动化运行

好了,基本教程的分享到这里就告一段落了,接下来该进入不同资产的策略以及源码分析中了。