BacktraderPercentRank 重新加载

  |  

社区用户@randyt已经能够将反向交易者扩展到其极限。找到一些晦涩难懂的角落,甚至在这里和那里添加pdb语句,这一直是获得更精细的重采样流同步的驱动力。

最近, @randyt添加了一个拉取请求以集成一个名为PercentRank的新指针。这是原始代码

class PercentRank(bt.Indicator):
    lines = ('pctrank',)
    params = (('period', 50),)

    def __init__(self):
        self.addminperiod(self.p.period)

    def next(self):
        self.lines.pctrank[0] = \
            (math.fsum([x < self.data[0]
                       for x in self.data.get(size=self.p.period)])
            / self.p.period)
        super(PercentRank, self).__init__()

它确实显示了有人如何进入backtrader的源代码,提出一些问题并掌握一些概念。这真的很好:

self.addminperiod(self.p.period)

出乎意料,因为最终用户甚至不会知道有人可以在行对像中使用该 API 调用。此调用告诉机器确保指针至少有可用的数据馈送self.p.period样本,因为它们是计算所必需的。

在原始代码中可以看到self.data.get(size=self.p.period) ,只有在后台引擎确保在进行第一次计算之前有这么多样本可用时(并且如果exactbars用于减少内存使用,那些许多样本总是在那里)

初始重新加载

可以重新编写代码以利用旨在缓解开发的预先存在的实用进程。最终用户无需注意任何事情,但很高兴知道一个人是否在不断地开发或原型设计指针。

class PercentRank_PeriodN1(bt.ind.PeriodN):
    lines = ('pctrank',)
    params = (('period', 50),)

    def next(self):
        d0 = self.data[0]  # avoid dict/array lookups each time
        dx = self.data.get(size=self.p.period)
        self.l.pctrank[0] = math.fsum((x < d0 for x in dx)) / self.p.period

重用PeriodN是消除self.addminperiod魔法并使指针在某种程度上更适合的关键。 PeriodN已经具有一个period参数,并将为用户进行调用(如果__init__被覆盖,请记住调用super(cls, self).__init__()

计算分为 3,首先缓存字典和数组查找并使其更具可读性(尽管后者只是口味问题)

代码也从 13 行减少到 8。这通常在阅读时有所帮助。

通过 OperationN 重新加载

现有的指针,如SumN ,它对一段时间内数据源的值求和,不像上面那样直接创建在PeriodN上,而是创建在它的一个名为OperationN的子类上。像它的父类一样,它仍然没有定义任何,并且有一个名为func的类属性。

func将使用一个数组调用,该数组包含主机函数必须操作的时间段的数据。签名基本上是: func(data[0:period])并返回适合存储在line中的内容,即:浮点值。

知道了,我们可以试试显而易见的

class PercentRank_OperationN1(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda d: math.fsum((x < d[-1] for x in d)) / self.p.period)

降到 4。但这将失败(只需要最后一行):

TypeError: <lambda>() takes 1 positional argument but 2 were given

(使用--strat n1= True使样本失败)

通过将我们未命名的函数放在func中,它似乎已经变成了一个方法,因为它需要两个参数。这可以很快治愈。

class PercentRank_OperationN2(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda self, d: math.fsum((x < d[-1] for x in d)) / self.p.period)

它有效。但是有一些丑陋的东西:这不是人们大多数时候期望的传递函数的方式,即:将self作为参数。在这种情况下,我们可以控制函数,但情况可能并非总是如此(需要一个包装器来解决它)

Python 中的语法糖用staticmethod来拯救,但在我们这样做之前,我们知道在staticmethod中不再可能引用self.p.period ,失去像以前一样进行平均计算的能力。

但是由于func接收一个固定长度的可迭代对象,所以可以使用len

现在是新代码。

class PercentRank_OperationN3(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = staticmethod(lambda d: math.fsum((x < d[-1] for x in d)) / len(d))

一切都很好,但这让人们思考了为什么以前没有考虑过让用户有机会传递他们自己的功能。子类化OperationN是一个不错的选择,但可以避免使用staticmethod或将self作为参数并在backtrader的机制上构建更好的方法。

让我们定义一个方便的OperationN子类。

class ApplyN(bt.ind.OperationN):
    lines = ('apply',)
    params = (('func', None),)

    def __init__(self):
        self.func = self.p.func
        super(ApplyN, self).__init__()

这可能早就应该在平台上。这里唯一真正的辨别是是否必须有lines = ('apply',)或者用户应该可以自由定义该和其他一些行。在集成之前需要考虑的事情。

有了ApplyNPercentRank的最终版本完全符合我们的预期。一是手动平均计算的版本。

class PercentRank_ApplyN(ApplyN):
    params = (
        ('period', 50),
        ('func', lambda d: math.fsum((x < d[-1] for x in d)) / len(d)),
    )

在不破坏PEP-8情况下,我们仍然可以重新格式化两者以适应 3……好!

让我们运行示例

下面可以看到的示例具有通常的骨架样板,但旨在显示不同PercentRank实现的视觉比较。

笔记

使用--strat n1= True运行它以尝试不起作用的PercentRank_OperationN1版本

图形输出。

示例使用

$ ./percentrank.py --help
usage: percentrank.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
                      [--todate TODATE] [--cerebro kwargs] [--broker kwargs]
                      [--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Sample Skeleton

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 math

import backtrader as bt



class PercentRank(bt.Indicator):
    lines = ('pctrank',)
    params = (('period', 50),)

    def __init__(self):
        self.addminperiod(self.p.period)

    def next(self):
        self.lines.pctrank[0] = \
            (math.fsum([x < self.data[0]
                       for x in self.data.get(size=self.p.period)])
            / self.p.period)
        super(PercentRank, self).__init__()


class PercentRank_PeriodN1(bt.ind.PeriodN):
    lines = ('pctrank',)
    params = (('period', 50),)

    def next(self):
        d0 = self.data[0]  # avoid dict/array lookups each time
        dx = self.data.get(size=self.p.period)
        self.l.pctrank[0] = math.fsum((x < d0 for x in dx)) / self.p.period


class PercentRank_OperationN1(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda d: math.fsum((x < d[-1] for x in d)) / self.p.period)


class PercentRank_OperationN2(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda self, d: math.fsum((x < d[-1] for x in d)) / self.p.period)


class PercentRank_OperationN3(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = staticmethod(lambda d: math.fsum((x < d[-1] for x in d)) / len(d))


class ApplyN(bt.ind.OperationN):
    lines = ('apply',)
    params = (('func', None),)

    def __init__(self):
        self.func = self.p.func
        super(ApplyN, self).__init__()


class PercentRank_ApplyN(ApplyN):
    params = (
        ('period', 50),
        ('func', lambda d: math.fsum((x < d[-1] for x in d)) / len(d)),
    )


class St(bt.Strategy):
    params = (
        ('n1', False),
    )

    def __init__(self):
        PercentRank()
        PercentRank_PeriodN1()
        if self.p.n1:
            PercentRank_OperationN1()
        PercentRank_OperationN2()
        PercentRank_OperationN3()
        PercentRank_ApplyN()

    def next(self):
        pass


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=(
            'Sample Skeleton'
        )
    )

    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()

推荐阅读

相关文章

Backtrader教程:观察者 - 参考

基准 backtrader类 .observers.基准() 此 observer 存储策略的回报和参考资产的回报,参考资产是传递到系统的数据之一。

Backtrader规范与非规范

这个问题或多或少地出现了几次:这样: backtrader如何最好/规范地实现这一点或那样? 作为 backtrader 的目标之一,可以灵活地 支持尽可能多的情况和用例,答案很简单:“至少在几种方式上”。

Backtrader教程:分析仪

无论是回溯测试还是交易,能够分析交易系统的性能是了解是否不仅获得了利润,而且是否在风险太大的情况下实现了利润,或者与参考资产(或无风险资产)相比,是否真的值得付出努力的关键。 这就是对象家族的用武之Analyzer 地:提供对所发生事件甚至实际发生的事情的分析。

Backtrader数据同步

在最新版本中,次要编号已从 8 移至 9,以指示即使已考虑兼容性,也可能会对行为产生一些影响。 在 1.9.0.99 版中,使用 datetime 同步多个数据的整个机制已经重新设计(适用于下一个和一次模式)。

Backtrader教程:尺寸调整器

智能质押 策略提供了交易方法,即:buy和 sell close。

Backtrader 教程:策略 - 参考

内置策略的参考MA_CrossOver别名: * SMA_CrossOver 这是一个多头策略,

Backtrader 教程:Cerebro - 节省内存

版本 1.3.1.92重新设计并完全实现了以前存在的内存节省方案,尽管没有太多吹捧和较少使用。 backtrader是(并将进一步)在具有大量 RAM 的机器上开发的,再加上通过绘图的视觉反馈是一个很好的拥有并且几乎是必须拥有的事实,mde 很容易做出设计决策:保留所有内容记忆。

Backtrader信号策略

操作 backtrader 也是可能的,而无需编写策略。虽然这是首选方式,但由于构成机器的对象层次结构,使用信号也是可能的。

Backtrader熊猫数据馈送

在一些小的增强功能和一些OrderDict调整中,以获得更好的Python 2.6支持, backtrader 的最新版本增加了对分析Pandas Dataframe或Time Series数据的支持。

Backtrader数据馈送开发

添加新的基于 CSV 的数据馈送很容易。