Backtrader策略选择

  |  

最初的策略选择方法使用两个策略,手动注册和一个简单的[0, 1]列表来决定哪个是策略的目标。

因为 Python 为元类提供了许多自省的可能性,所以实际上可以自动化该方法。让我们使用decorator方法来完成,这在这种情况下可能是侵入性最小的(无需为策略定义元类)

现在的工厂:

  • 在策略之前声明

  • 有一个空的_STRATS类属性(它之前有返回的策略)

  • 有一个register类方法,它将用作装饰器并接受将添加到_STRATS的参数

  • 有一个COUNT类方法,它将返回一个迭代器(实际上是一个range ),其中包含要优化的可用策略的计数

  • 对实际工厂方法没有任何更改: __new__ ,它继续使用idx参数返回给定索引处的_STRATS类属性中的任何内容

class StFetcher(object):
    _STRATS = []

    @classmethod
    def register(cls, target):
        cls._STRATS.append(target)

    @classmethod
    def COUNT(cls):
        return range(len(cls._STRATS))

    def __new__(cls, *args, **kwargs):
        idx = kwargs.pop('idx')

        obj = cls._STRATS[idx](*args, **kwargs)
        return obj

像这样:

  • StFetcher策略工厂本身不再包含任何硬编码的策略

示例中的策略不需要重新设计。使用StFetcherregister方法进行装饰足以将它们添加到选择组合中。

@StFetcher.register
class St0(bt.SignalStrategy):

@StFetcher.register
class St1(bt.SignalStrategy):

过去使用optstrategy将策略工厂添加到系统时的手动[0, 1]列表可以完全替换为对StFetcher.COUNT()的透明调用。硬编码结束了。

    cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())

示例运行

$ ./stselection-revisited.py --optreturn
Strat 0 Name OptReturn:
  - analyzer: OrderedDict([(u'rtot', 0.04847392369449283), (u'ravg', 9.467563221580632e-05), (u'rnorm', 0.02414514457151587), (u'rnorm100', 2.414514457151587)])

Strat 1 Name OptReturn:
  - analyzer: OrderedDict([(u'rtot', 0.05124714332260593), (u'ravg', 0.00010009207680196471), (u'rnorm', 0.025543999840699633), (u'rnorm100', 2.5543999840699634)])

我们的 2 个策略已经运行并交付(如预期)不同的结果。

笔记

该示例很小,但已在所有可用 CPU 上运行。使用--maxpcpus=1运行它会更快。对于更复杂的场景,使用所有 CPU 将很有用。

结论

选择已完全自动化。和以前一样,可以设想像查找数据库以获取可用策略的数量,然后一一获取策略。

示例使用

$ ./stselection-revisited.py --help
usage: strategy-selection.py [-h] [--data DATA] [--maxcpus MAXCPUS]
                             [--optreturn]

Sample for strategy selection

optional arguments:
  -h, --help         show this help message and exit
  --data DATA        Data to be read in (default:
                     ../../datas/2005-2006-day-001.txt)
  --maxcpus MAXCPUS  Limit the numer of CPUs to use (default: None)
  --optreturn        Return reduced/mocked strategy object (default: False)

编码

已包含在backtrader的来源中

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse

import backtrader as bt
from backtrader.utils.py3 import range


class StFetcher(object):
    _STRATS = []

    @classmethod
    def register(cls, target):
        cls._STRATS.append(target)

    @classmethod
    def COUNT(cls):
        return range(len(cls._STRATS))

    def __new__(cls, *args, **kwargs):
        idx = kwargs.pop('idx')

        obj = cls._STRATS[idx](*args, **kwargs)
        return obj


@StFetcher.register
class St0(bt.SignalStrategy):
    def __init__(self):
        sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
        crossover = bt.ind.CrossOver(sma1, sma2)
        self.signal_add(bt.SIGNAL_LONG, crossover)


@StFetcher.register
class St1(bt.SignalStrategy):
    def __init__(self):
        sma1 = bt.ind.SMA(period=10)
        crossover = bt.ind.CrossOver(self.data.close, sma1)
        self.signal_add(bt.SIGNAL_LONG, crossover)


def runstrat(pargs=None):
    args = parse_args(pargs)

    cerebro = bt.Cerebro()
    data = bt.feeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)

    cerebro.addanalyzer(bt.analyzers.Returns)
    cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())
    results = cerebro.run(maxcpus=args.maxcpus, optreturn=args.optreturn)

    strats = [x[0] for x in results]  # flatten the result
    for i, strat in enumerate(strats):
        rets = strat.analyzers.returns.get_analysis()
        print('Strat {} Name {}:\n  - analyzer: {}\n'.format(
            i, strat.__class__.__name__, rets))


def parse_args(pargs=None):

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for strategy selection')

    parser.add_argument('--data', required=False,
                        default='../../datas/2005-2006-day-001.txt',
                        help='Data to be read in')

    parser.add_argument('--maxcpus', required=False, action='store',
                        default=None, type=int,
                        help='Limit the numer of CPUs to use')

    parser.add_argument('--optreturn', required=False, action='store_true',
                        help='Return reduced/mocked strategy object')

    return parser.parse_args(pargs)


if __name__ == '__main__':
    runstrat()

推荐阅读

相关文章

Backtrader观察员和统计

在 backtrader 内部运行的策略主要处理数据 和 指针。 数据被添加到Cerebro 实例中,并最终成为策略输入的一部分(解析并用作实例的属性),而指针由策略本身声明和管理。

Backtrader教程:指针 - 开发

如果必须开发任何东西(除了一个或多个获胜策略之外),那么这个东西就是一个自定义指针。 根据作者的说法,平台内的这种开发很容易。 需要满足以下条件: 从指针派生的类(直接或从现有的子类派生) 定义它将保持lines 指针必须至少具有 1 line。

Backtrader标杆

backtrader 包括 2 种不同类型的对象,可帮助进行跟踪: Observers Analyzers 工单 #89 是关于添加资产基准测试的。明智的是,人们实际上可能有一个策略,即使积极,也低于简单地跟踪资产所能提供的策略。

Backtrader股票筛选

在寻找其他一些东西时,我在StackOverlow家族网站之一上遇到了一个问题:Quantitative Finance aka Quant StackExchange。问题: 它被标记为Python,因此值得一看的是 backtrader 是否能够胜任这项任务。 分析仪本身 该问题似乎适合用于简单的分析器。

Backtrader扩展数据馈送

GitHub 中的问题实际上是在推动文档部分的完成,或者说明我了解 backtrader 是否具有我从最初时刻就设想的易用性和灵活性以及在此过程中做出的决定。 在本例中为问题 #9。

Backtrader教程:筛检程序

此功能是 backtrader 的相对较新的补充,必须安装到已经存在的内部结构中。这使得它不像希望的那样灵活且100%功能齐全,但在许多情况下它仍然可以达到目的。 尽管该实现试图允许随插即用的筛检程序链接,但预先存在的内部结构使得很难确保始终可以实现。因此,某些筛选器可能是链接的,而其他一些筛选器可能不是。

Backtrader数据同步

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

Backtrader教程:观察者 - 统计

在内部backtrader 运行的策略主要处理 data feeds 和 指针。 Data feeds 被添加到Cerebro 实例中,并最终成为策略输入的一部分(解析并用作实例的属性),而指针则由策略本身声明和管理。

Backtrader 教程:Cerebro - 例外

设计目标之一是尽早退出,让用户完全了解错误发生的情况。目的是强迫自己拥有会因异常而中断的代码并强制重新访问受影响的部分。但是时机已经成熟,一些例外可能会慢慢添加到平台中。

Backtrader版本 1.2.1.88

将次要版本号从 1 更改为 2 需要一段时间,但旧的 DataResampler 和 DataReplayer 的弃用导致了这种情况。 readthedocs 的文档有 文档已更新为仅引用现代方法 resampling 和 replaying。