BackTrader 中文文档(二十四)(4)

简介: BackTrader 中文文档(二十四)

BackTrader 中文文档(二十四)(3)https://developer.aliyun.com/article/1505449

脚本代码和用法

backtrader源码中提供为样例:

$ ./ppsample.py --help
usage: ppsample.py [-h] [--data DATA] [--usepp1] [--plot] [--plot-on-daily]
Sample for pivot point and cross plotting
optional arguments:
  -h, --help       show this help message and exit
  --data DATA      Data to be read in (default:
                   ../../datas/2005-2006-day-001.txt)
  --usepp1         Have PivotPoint look 1 period backwards (default: False)
  --plot           Plot the result (default: False)
  --plot-on-daily  Plot the indicator on the daily data (default: False)

PivotPoint的代码

from __future__ import (absolute_import, division, print_function,)
#                        unicode_literals)
import backtrader as bt
class PivotPoint1(bt.Indicator):
    lines = ('p', 's1', 's2', 'r1', 'r2',)
    def __init__(self):
        h = self.data.high(-1)  # previous high
        l = self.data.low(-1)  # previous low
        c = self.data.close(-1)  # previous close
        self.lines.p = p = (h + l + c) / 3.0
        p2 = p * 2.0
        self.lines.s1 = p2 - h  # (p x 2) - high
        self.lines.r1 = p2 - l  # (p x 2) - low
        hilo = h - l
        self.lines.s2 = p - hilo  # p - (high - low)
        self.lines.r2 = p + hilo  # p + (high - low)
class PivotPoint(bt.Indicator):
    lines = ('p', 's1', 's2', 'r1', 'r2',)
    plotinfo = dict(subplot=False)
    def __init__(self):
        h = self.data.high  # current high
        l = self.data.low  # current high
        c = self.data.close  # current high
        self.lines.p = p = (h + l + c) / 3.0
        p2 = p * 2.0
        self.lines.s1 = p2 - h  # (p x 2) - high
        self.lines.r1 = p2 - l  # (p x 2) - low
        hilo = h - l
        self.lines.s2 = p - hilo  # p - (high - low)
        self.lines.r2 = p + hilo  # p + (high - low)

脚本的代码。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.utils.flushfile
from pivotpoint import PivotPoint, PivotPoint1
class St(bt.Strategy):
    params = (('usepp1', False),
              ('plot_on_daily', False))
    def __init__(self):
        if self.p.usepp1:
            self.pp = PivotPoint1(self.data1)
        else:
            self.pp = PivotPoint(self.data1)
        if self.p.plot_on_daily:
            self.pp.plotinfo.plotmaster = self.data0
    def next(self):
        txt = ','.join(
            ['%04d' % len(self),
             '%04d' % len(self.data0),
             '%04d' % len(self.data1),
             self.data.datetime.date(0).isoformat(),
             '%.2f' % self.pp[0]])
        print(txt)
def runstrat():
    args = parse_args()
    cerebro = bt.Cerebro()
    data = btfeeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)
    cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)
    cerebro.addstrategy(St,
                        usepp1=args.usepp1,
                        plot_on_daily=args.plot_on_daily)
    cerebro.run()
    if args.plot:
        cerebro.plot(style='bar')
def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for pivot point and cross plotting')
    parser.add_argument('--data', required=False,
                        default='../../datas/2005-2006-day-001.txt',
                        help='Data to be read in')
    parser.add_argument('--usepp1', required=False, action='store_true',
                        help='Have PivotPoint look 1 period backwards')
    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))
    parser.add_argument('--plot-on-daily', required=False, action='store_true',
                        help=('Plot the indicator on the daily data'))
    return parser.parse_args()
if __name__ == '__main__':
    runstrat()

同步不同的市场

原文:www.backtrader.com/blog/posts/2016-04-19-sync-different-markets/sync-different-markets/

使用越多,backtrader 面对的想法和意外情况就越多。随着每一个新的想法,都是一个挑战,看看平台是否能够实现开发时设定的期望,灵活性和易用性是目标,Python 被选择为基石。

Ticket #76 提出了一个问题,即是否可以同步具有不同交易日历的市场。直接尝试这样做会失败,问题的创建者想知道为什么 backtrader 没有查看日期。

在做出任何答复之前,必须考虑一些想法:

  • 对于不对齐的日子指标的行为

后者的答案是:

  • 该平台尽可能是日期时间不可知的,并且不会查看字段的内容来评估这些概念

考虑到股票市场价格是 datetime 系列,上述情况在一定程度上是成立的。在多个数据的情况下,以下设计考虑因素适用:

  • 添加到 cerebro 的第一条数据是 datamaster
  • 所有其他数据都必须与其进行时间对齐/同步,永远不能超过(在 datetime 方面)datamaster

将上述的三个要点组合在一起,得到了问题创建者所经历的混合体。情景如下:

  • 日历年份:2012
  • 数据 0: ^GSPC(或者朋友们称之为标普 500 指数)
  • 数据 1: ^GDAXI(或者朋友们称之为德国 DAX 指数)

运行一个自定义脚本,查看backtrader如何同步数据:

$ ./weekdaysaligner.py --online --data1 '^GSPC' --data0 '^GDAXI'

输出如下:

0001,  True, data0, 2012-01-03T23:59:59, 2012-01-03T23:59:59, data1
0002,  True, data0, 2012-01-04T23:59:59, 2012-01-04T23:59:59, data1
0003,  True, data0, 2012-01-05T23:59:59, 2012-01-05T23:59:59, data1
0004,  True, data0, 2012-01-06T23:59:59, 2012-01-06T23:59:59, data1
0005,  True, data0, 2012-01-09T23:59:59, 2012-01-09T23:59:59, data1
0006,  True, data0, 2012-01-10T23:59:59, 2012-01-10T23:59:59, data1
0007,  True, data0, 2012-01-11T23:59:59, 2012-01-11T23:59:59, data1
0008,  True, data0, 2012-01-12T23:59:59, 2012-01-12T23:59:59, data1
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010, False, data0, 2012-01-17T23:59:59, 2012-01-16T23:59:59, data1
0011, False, data0, 2012-01-18T23:59:59, 2012-01-17T23:59:59, data1
...

一旦到达 2012-01-16,交易日历就开始分歧。data0datamaster^GSPC),即使 data1^GDAXI)在 2012-01-16 有一根柱子要交付,这对于S&P 500 来说并不是一个交易日

^GSPC 的下一个交易日到来时,backtrader在上述设计限制下能做的最好的事情是,提供 ^GDAXI` 的下一个尚未处理的日期,即 `2012-01-16`。

随着每一天的分歧,同步问题逐渐累积。在 2012 年末,情况如下:

...
0249, False, data0, 2012-12-28T23:59:59, 2012-12-19T23:59:59, data1
0250, False, data0, 2012-12-31T23:59:59, 2012-12-20T23:59:59, data1

原因应该是显而易见的:欧洲人交易的日子比美国人多

在 Ticket #76 github.com/mementum/backtrader/issues/76 中,作者展示了 zipline 的操作。让我们来看看 2012-01-13 - 2012-01-17 的难题:

0009 : True : 2012-01-13 : close 1289.09 - 2012-01-13 :  close 6143.08
0010 : False : 2012-01-13 : close 1289.09 - 2012-01-16 :  close 6220.01
0011 : True : 2012-01-17 : close 1293.67 - 2012-01-17 :  close 6332.93

需要注意!2012-01-13 的数据已经被简单地复制,似乎没有征求用户的许可。在我看来,这不应该发生,因为平台的最终用户无法撤销这种自发添加。

注意

除了简要查看zipline,作者不知道这是否是标准行为,由脚本开发者配置,以及是否可以撤消。

一旦我们已经看到其他内容,让我们再次尝试使用累积的智慧,使用backtrader欧洲人比美国人交易频繁。让我们颠倒^GSPC^GDAXI的角色,看看结果:

$ ./weekdaysaligner.py --online --data1 '^GSPC' --data0 '^GDAXI'

输出(直接跳转到2012-01-13):

...
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010, False, data0, 2012-01-16T23:59:59, 2012-01-13T23:59:59, data1
0011,  True, data0, 2012-01-17T23:59:59, 2012-01-17T23:59:59, data1
...

该死的!backtrader复制了2012-01-13值作为data0(现在为^GDAXI)的交付,以匹配data1(在此情况下为^GSPC)的交付,这是2012-01-16

更好的是:

  • 同步已经在下一个日期2012-01-17中达到

不久之后再次看到相同的重新同步:

...
0034,  True, data0, 2012-02-17T23:59:59, 2012-02-17T23:59:59, data1
0035, False, data0, 2012-02-20T23:59:59, 2012-02-17T23:59:59, data1
0036,  True, data0, 2012-02-21T23:59:59, 2012-02-21T23:59:59, data1
...

紧接着是不那么容易的重新同步:

...
0068,  True, data0, 2012-04-05T23:59:59, 2012-04-05T23:59:59, data1
0069, False, data0, 2012-04-10T23:59:59, 2012-04-09T23:59:59, data1
...
0129, False, data0, 2012-07-04T23:59:59, 2012-07-03T23:59:59, data1
0130,  True, data0, 2012-07-05T23:59:59, 2012-07-05T23:59:59, data1
...

这种情节会一直重复,直到最后一个^GDAXI柱形图被提供:

...
0256,  True, data0, 2012-12-31T23:59:59, 2012-12-31T23:59:59, data1
...

这种同步问题的原因是backtrader不会复制数据。

  • 一旦datamaster提供了一个新的柱形图,其他数据就会被要求提供
  • 如果对于datamaster当前的datetime来说没有柱形图可提供(例如,因为它被超越了),那么就会提供下一个最佳数据,可以说是重新提供
    这是一个已经见过的日期的柱形图

正确的同步

但并非一切希望都已消失。backtrader 可以实现。让我们使用过滤器backtrader 中的这项技术允许在数据到达平台最深层之前对其进行操作,例如计算指标

注意

“交付”是一个感知问题,因此backtrader提供的可能不是接收者所期望的交付

实际代码如下

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import datetime
class WeekDaysFiller(object):
    '''Bar Filler to add missing calendar days to trading days'''
    # kickstart value for date comparisons
    lastdt = datetime.datetime.max.toordinal()
    def __init__(self, data, fillclose=False):
        self.fillclose = fillclose
        self.voidbar = [float('Nan')] * data.size()  # init a void bar
    def __call__(self, data):
        '''Empty bars (NaN) or with last close price are added for weekdays with no
        data
        Params:
          - data: the data source to filter/process
        Returns:
          - True (always): bars are removed (even if put back on the stack)
        '''
        dt = data.datetime.dt()  # current date in int format
        lastdt = self.lastdt + 1  # move the last seen data once forward
        while lastdt < dt:  # loop over gap bars
            if datetime.date.fromordinal(lastdt).isoweekday() < 6:  # Mon-Fri
                # Fill in date and add new bar to the stack
                if self.fillclose:
                    self.voidbar = [self.lastclose] * data.size()
                self.voidbar[-1] = float(lastdt) + data.sessionend
                data._add2stack(self.voidbar[:])
            lastdt += 1  # move lastdt forward
        self.lastdt = dt  # keep a record of the last seen date
        self.lastclose = data.close[0]
        data._save2stack(erase=True)  # dt bar to the stack and out of stream
        return True  # bars are on the stack (new and original)

测试脚本已经具备使用它的能力:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler

使用--fillerWeekDaysFiller被添加到data0data1。并且输出:

0001,  True, data0, 2012-01-03T23:59:59, 2012-01-03T23:59:59, data1
...
0009,  True, data0, 2012-01-13T23:59:59, 2012-01-13T23:59:59, data1
0010,  True, data0, 2012-01-16T23:59:59, 2012-01-16T23:59:59, data1
0011,  True, data0, 2012-01-17T23:59:59, 2012-01-17T23:59:59, data1
...

2012-01-132012-01-17期间的第 1 个难题已经解决。并且整个集合已经同步

...
0256,  True, data0, 2012-12-25T23:59:59, 2012-12-25T23:59:59, data1
0257,  True, data0, 2012-12-26T23:59:59, 2012-12-26T23:59:59, data1
0258,  True, data0, 2012-12-27T23:59:59, 2012-12-27T23:59:59, data1
0259,  True, data0, 2012-12-28T23:59:59, 2012-12-28T23:59:59, data1
0260,  True, data0, 2012-12-31T23:59:59, 2012-12-31T23:59:59, data1

值得注意的是:

  • 使用^GSPC作为data0,我们有250行(该指数在2012年交易了250天)
  • 使用^GDAXI,我们data0256行(该指数在2012年交易了256天)
  • 并且通过放置WeekDaysFiller,两个数据集的长度已经扩展到260
    添加52 * 2(周末和周末的天数),我们会得到364。一年中剩余的一天肯定是一个星期六或一个星期天

过滤器正在用NaN值进行填充,用于给定数据中没有交易发生的日子。让我们来绘制它:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot

填充的日子相当明显:

  • 条之间的间隙还在
  • 对于成交量图来说,这个间隙更加明显

第 2 个绘图将尝试回答顶部的问题:*指标会发生什么情况?*请记住,新的柱形图已经被赋予了NaN的值(这就是它们没有显示的原因):

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot --sma 10

重新贴上船壳!简单移动平均 打破了时空连续体,并跳过一些柱,没有连续解。这当然是 Not a NumberNaN)填充的效果:数学运算不再有意义

如果使用的不是 NaN 而是上次观察到的收盘价:

$ ./weekdaysaligner.py --online --data0 '^GSPC' --data1 '^GDAXI' --filler --plot --sma 10 --fillclose

整个 260 天使用正常 SMA 绘制的图表看起来更加漂亮。

结论

同步两个具有不同交易日历的工具是一个做出决策和妥协的问题。backtrader 需要时间对齐的数据来处理多个数据和不同的交易日历并不会有所帮助。

此处描述的 WeekDaysFiller 的使用可以缓解这种情况,但这绝不是一种普遍的灵丹妙药,因为用什么值来填充是一个长期而且长时间的考虑问题。

脚本代码和用法

backtrader 源码中可用作示例:

$ ./weekdaysaligner.py --help
usage: weekdaysaligner.py [-h] [--online] --data0 DATA0 [--data1 DATA1]
                          [--sma SMA] [--fillclose] [--filler] [--filler0]
                          [--filler1] [--fromdate FROMDATE] [--todate TODATE]
                          [--plot]
Sample for aligning with trade
optional arguments:
  -h, --help            show this help message and exit
  --online              Fetch data online from Yahoo (default: False)
  --data0 DATA0         Data 0 to be read in (default: None)
  --data1 DATA1         Data 1 to be read in (default: None)
  --sma SMA             Add a sma to the datas (default: 0)
  --fillclose           Fill with Close price instead of NaN (default: False)
  --filler              Add Filler to Datas 0 and 1 (default: False)
  --filler0             Add Filler to Data 0 (default: False)
  --filler1             Add Filler to Data 1 (default: False)
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format (default:
                        2012-01-01)
  --todate TODATE, -t TODATE
                        Ending date in YYYY-MM-DD format (default: 2012-12-31)
  --plot                Do plot (default: False)

代码:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import backtrader.utils.flushfile
# from wkdaysfiller import WeekDaysFiller
from weekdaysfiller import WeekDaysFiller
class St(bt.Strategy):
    params = (('sma', 0),)
    def __init__(self):
        if self.p.sma:
            btind.SMA(self.data0, period=self.p.sma)
            btind.SMA(self.data1, period=self.p.sma)
    def next(self):
        dtequal = (self.data0.datetime.datetime() ==
                   self.data1.datetime.datetime())
        txt = ''
        txt += '%04d, %5s' % (len(self), str(dtequal))
        txt += ', data0, %s' % self.data0.datetime.datetime().isoformat()
        txt += ', %s, data1' % self.data1.datetime.datetime().isoformat()
        print(txt)
def runstrat():
    args = parse_args()
    fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
    todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
    cerebro = bt.Cerebro(stdstats=False)
    DataFeed = btfeeds.YahooFinanceCSVData
    if args.online:
        DataFeed = btfeeds.YahooFinanceData
    data0 = DataFeed(dataname=args.data0, fromdate=fromdate, todate=todate)
    if args.data1:
        data1 = DataFeed(dataname=args.data1, fromdate=fromdate, todate=todate)
    else:
        data1 = data0.clone()
    if args.filler or args.filler0:
        data0.addfilter(WeekDaysFiller, fillclose=args.fillclose)
    if args.filler or args.filler1:
        data1.addfilter(WeekDaysFiller, fillclose=args.fillclose)
    cerebro.adddata(data0)
    cerebro.adddata(data1)
    cerebro.addstrategy(St, sma=args.sma)
    cerebro.run(runonce=True, preload=True)
    if args.plot:
        cerebro.plot(style='bar')
def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for aligning with trade ')
    parser.add_argument('--online', required=False, action='store_true',
                        help='Fetch data online from Yahoo')
    parser.add_argument('--data0', required=True, help='Data 0 to be read in')
    parser.add_argument('--data1', required=False, help='Data 1 to be read in')
    parser.add_argument('--sma', required=False, default=0, type=int,
                        help='Add a sma to the datas')
    parser.add_argument('--fillclose', required=False, action='store_true',
                        help='Fill with Close price instead of NaN')
    parser.add_argument('--filler', required=False, action='store_true',
                        help='Add Filler to Datas 0 and 1')
    parser.add_argument('--filler0', required=False, action='store_true',
                        help='Add Filler to Data 0')
    parser.add_argument('--filler1', required=False, action='store_true',
                        help='Add Filler to Data 1')
    parser.add_argument('--fromdate', '-f', default='2012-01-01',
                        help='Starting date in YYYY-MM-DD format')
    parser.add_argument('--todate', '-t', default='2012-12-31',
                        help='Ending date in YYYY-MM-DD format')
    parser.add_argument('--plot', required=False, action='store_true',
                        help='Do plot')
    return parser.parse_args()
if __name__ == '__main__':
    runstrat()


相关文章
|
19天前
|
存储 监控 网络协议
BackTrader 中文文档(二十四)(1)
BackTrader 中文文档(二十四)
18 0
|
19天前
|
索引 Python
BackTrader 中文文档(二十四)(3)
BackTrader 中文文档(二十四)
17 0
|
19天前
|
存储 编解码 数据可视化
BackTrader 中文文档(二十四)(2)
BackTrader 中文文档(二十四)
17 0
|
19天前
BackTrader 中文文档(二十五)(2)
BackTrader 中文文档(二十五)
15 0
|
19天前
|
C++
BackTrader 中文文档(二十五)(3)
BackTrader 中文文档(二十五)
14 0
|
19天前
|
数据可视化
BackTrader 中文文档(二十五)(4)
BackTrader 中文文档(二十五)
16 0
|
19天前
|
数据可视化 API Python
BackTrader 中文文档(二十五)(1)
BackTrader 中文文档(二十五)
14 0
|
19天前
|
测试技术 C语言 Python
BackTrader 中文文档(二十八)(4)
BackTrader 中文文档(二十八)
17 0
|
19天前
|
存储 算法 C++
BackTrader 中文文档(二十八)(3)
BackTrader 中文文档(二十八)
15 0
|
19天前
BackTrader 中文文档(二十八)(2)
BackTrader 中文文档(二十八)
16 0