You are here:  Home » 量化交易与机器学习 » backtrader » 动量策略 – backtrader中文教程

动量Strategy

In另一个伟大的职位,Teddy Koker,再次表明了一个路径algotrading研究首次申请

  • 回溯测试,然后使用pandas
  • 荣誉!backtrader

的后可以在这里找到:的

战略发展:

  • 的https:// teddykoker.com / 2019/05 /动量策略 – 从 – 股票 – 上的-布展的Python /

Teddy Koker把我一个消息,询问是否我可以在使用提示的backtrader。而我的opinion可以如下图所示。这只是我的个人愚见,因为笔者backtrader我有偏见至于如何该平台可能是最好的used.

And我关于如何制定某种构造个人的口味,没有以符合人们更喜欢如何等使用platform.

Note

其实,让平台开放堵塞几乎所有的东西,并用不同的方式做同样的事情,是一个非常明智的决定,让人们用它不过他们认为合适的(什么样的限制范围内平台旨在做,语言的可能性和失败的设计决定我做了)

在这里,我们将只专注于这可能已经在不同的被做的事情方式。无论“different”好与否始终是一个问题意见。和`笔者backtrader并不总是必须是正确的它实际上是什么“better”与发展“backtrader”(因为实际开发中必须满足开发商和的不是作者“backtrader”

PARAMS:dictVStuple of tuples

许多设置有样品的backtrader和在也可文档和/或博客,使用的所述tuple of tuples图案参数。例如从代码:

class Momentum(bt.Indicator):
    lines = ("trend",)
    params = (("period", 90),)

这种范式一起,一个一直有使用的机会dict.

class Momentum(bt.Indicator):
    lines = ("trend",)
    params = dict(period=90)  # or params = {"period": 90}

随着时间的推移这已经变成更轻使用,并成为首选对于author.

Note

The笔者模式更喜欢dict(period=90),是更容易输入,不需要报价。但在大括号标记,{"period": 90},是由许多others.

The底层差dicttuple接近之间优选的:

  • 随着tuple of tuples参数保留的顺序宣言,该宣言列举them.

    Tip

    The声明的顺序应该是默认没有问题,下单可能是重要的在Python3.7字典(和3.6如果使用CPython就算是一个实现细节)

在由以下作者修改的例子中,dict表示法将used.

TheMomentumindicator

In的文章,这是指标是如何defined

class Momentum(bt.Indicator):
    lines = ("trend",)
    params = (("period", 90),)

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

    def next(self):
        returns = np.log(self.data.get(size=self.p.period))
        x = np.arange(len(returns))
        slope, _, rvalue, _, _ = linregress(x, returns)
        annualized = (1 + slope) ** 252
        self.lines.trend[0] = annualized * (rvalue ** 2)

Use的force,即:使用的东西,这是已经存在像PeriodN指示器,其中:

  • 已经定义了一个period参数,并且知道如何将其传递到system

因此,这可能是better

class Momentum(bt.ind.PeriodN):
    lines = ("trend",)
    params = dict(period=50)

    def next(self):
        ...

We已经跳过需要定义__init__为唯一目的使用addminperiod,它仅应在特殊使用cases.

To进行,backtrader限定OperationN必须具有一个指示器属性func定义,这将让period作为参数传递棒而这将对返回值到line.

With考虑到这一点的定义,可以想见以下内容作为潜在code

def momentum_func(the_array):
    r = np.log(the_array)
    slope, _, rvalue, _, _ = linregress(np.arange(len(r)), r)
    annualized = (1 + slope) ** 252
    return annualized * (rvalue ** 2)


class Momentum(bt.ind.OperationN):
    lines = ("trend",)
    params = dict(period=50)
    func = momentum_func

Which意味着,我们所采取的指标之外的复杂性的指示符。我们甚至可以从外部库导入momentum_func和指标将不需要改变,以反映如果一个新的行为底层函数的变化。作为奖励,我们有purely声明指示符。没有__init__没有addminperiod没有next

Strategy

Let“先看一下__init__part.

class Strategy(bt.Strategy):
    def __init__(self):
        self.i = 0
        self.inds = {}
        self.spy = self.datas[0]
        self.stocks = self.datas[1:]

        self.spy_sma200 = bt.indicators.SimpleMovingAverage(self.spy.close,
                                                            period=200)
        for d in self.stocks:
            self.inds[d] = {}
            self.inds[d]["momentum"] = Momentum(d.close,
                                                period=90)
            self.inds[d]["sma100"] = bt.indicators.SimpleMovingAverage(d.close,
                                                                       period=100)
            self.inds[d]["atr20"] = bt.indicators.ATR(d,
                                                      period=20)

关于风格有些事情:

  • 使用参数,如果可能的话,而不是固定values
  • 使用短和更短的名称(进口为例),它会在大多数情况下,增加readability
  • Use Python来充分extent
  • Don“T使用close数据Feed。传递数据饲料一般和它将用接近。这似乎并不相关,但试图当它帮助保持代码的通用随处可见(如在指标)

一个会/应该考虑的第一件事:keep everything as a
parameter if possible
。Hence

class Strategy(bt.Strategy):
    params = dict(
        momentum=Momentum,  # parametrize the momentum and its period
        momentum_period=90,

        movav=bt.ind.SMA,  # parametrize the moving average and its periods
        idx_period=200,
        stock_period=100,

        volatr=bt.ind.ATR,  # parametrize the volatility and its period
        vol_period=20,
    )


    def __init__(self):
        # self.i = 0  # See below as to why the counter is commented out
        self.inds = collections.defaultdict(dict)  # avoid per data dct in for

        # Use "self.data0" (or self.data) in the script to make the naming not
        # fixed on this being a "spy" strategy. Keep things generic
        # self.spy = self.datas[0]
        self.stocks = self.datas[1:]

        # Again ... remove the name "spy"
        self.idx_mav = self.p.movav(self.data0, period=self.p.idx_period)
        for d in self.stocks:
            self.inds[d]["mom"] = self.p.momentum(d, period=self.momentum_period)
            self.inds[d]["mav"] = self.p.movav(d, period=self.p.stock_period)
            self.inds[d]["vol"] = self.p.volatr(d, period=self.p.vol_period)

By使用params并改变一对夫妇的命名规则,我们有取得了__init__(与它的战略),完全可定制和通用(没有spy引用anyhwere)

nextlen

backtrader试图尽可能使用Python的范例。它为肯定有时会失败,但它tries.

Let我们看看会发生什么next

    def next(self):
        if self.i % 5 == 0:
            self.rebalance_portfolio()
        if self.i % 10 == 0:
            self.rebalance_positions()
        self.i += 1

这里是Python的len范例帮助。让“的使用it

    def next(self):
        l = len(self)
        if l % 5 == 0:
            self.rebalance_portfolio()
        if l % 10 == 0:
            self.rebalance_positions()

As你可能会看到,没有必要保持self.i计数器。长度和大多数的对象,策略提供,计算和更新由系统中所有沿way.

nextprenext

的代码包含这个forwarding

    def prenext(self):
        # call next() even when data is not available for all tickers
        self.next()

And有在进入的时候`NO保障next

    def next(self):
        if self.i % 5 == 0:
            self.rebalance_portfolio()
        ...

好了,我们知道,生存无偏差数据集是在使用,但一般不维护prenext => next转发的是不是一个好的idea.

  • backtrader调用next当所有的缓冲区(指标,数据输入)即可至少交付数据点。一个100-bar均线明显会只有当它有100个数据点从所述数据传递feed.

    This进入装置,当next,数据馈送将具有100 data
    points
    待检查,只是移动平均1 data point

  • backtrader报价prenext如钩,让开发人员能够访问的东西之前,上述担保都可以得到满足。这是非常有用例如,当几个数据源是在玩,他们开始日期为不同。开发人员可能需要一些检查或采取行动,之前对所有数据资讯提供的所有担保(及相关指标)是会见和next被称为第一个time.

In一般情况下prenext => next转发应有保护,如这样的:

    def prenext(self):
        # call next() even when data is not available for all tickers
        self.next()

    def next(self):
        d_with_len = [d for d in self.datas if len(d)]
        ...

这意味着只有子集d_with_lenself.datas可以使用与guarantees.

Note

A类似员有权使用indicators.

Because它似乎毫无意义,为整个生命做这个计算策略,优化是可能的,例如this

    def __init__(self):
        ...
        self.d_with_len = []


    def prenext(self):
        # Populate d_with_len
        self.d_with_len = [d for d in self.datas if len(d)]
        # call next() even when data is not available for all tickers
        self.next()

    def nextstart(self):
        # This is called exactly ONCE, when next is 1st called and defaults to
        # call `next`
        self.d_with_len = self.datas  # all data sets fulfill the guarantees now

        self.next()  # delegate the work to next

    def next(self):
        # we can now always work with self.d_with_len with no calculation
        ...

警卫计算移到prenext这将不再是所谓的当符合保证条件。nextstart将被调用,然后通过重写它,我们可以重置list持有该数据集的工作,是完整的数据集,即:self.datas

而与此,所有的后卫已经从next.

nexttimers

虽然笔者在这里的目的是要平衡(证券/位置)中5/10天,这可能意味着作为周/双周rebalancing.

Thelen(self) % period方法将失败,如果:

  • 该数据集没有在Monday
  • During交易开始放假,这将会使经济再平衡搬出alignment

To克服这一点,我们可以在backtrader

  • 使用文件 -Timers

Using他们将确保再平衡发生在它的目的是发生。让我们想象一下这样做的目的是为了重新平衡Fridays

让“个位的魔术增加了params__init__我们现在strategy

class Strategy(bt.Strategy):
    params = dict(
       ...
       rebal_weekday=5,  # rebalance 5 is Friday
    )

    def __init__(self):
        ...
        self.add_timer(
            when=bt.Timer.SESSION_START,
            weekdays=[self.p.rebal_weekday],
            weekcarry=True,  # if a day isn"t there, execute on the next
        )
        ...

And我们已经准备好知道什么时候是星期五。即使周五发生是交易假期,加上weekcarry=True确保我们不会被通知周一(或周二,如果周一也放假或…)

定时器的通知采取notify_timer

def notify_timer(self, timer, when, *args, **kwargs):
    self.rebalance_portfolio()

因为也有其发生的每rebalance_positions酒吧10原代码,一个可能:

  • 计数器添加2nd计时器,也为Fridays
  • Use只作用于每个2nd打电话,甚至可以在计时器本身使用allow=callableargument

Note

Timers甚至可以更好地用于实现模式,比如:

  • rebalance_portfolio每次在2nd只有对4th周五month
  • rebalance_positions的4th周五每个month

Some Extras

Some其他的东西可能是和纯粹的个人品味taste.

Personal的1

Use事总是预建在比较过程中,而不是比较的东西next。例如从代码(使用一次以上)

        if self.spy < self.spy_sma200:
            return

我们可以做到以下几点。首先在__init__

    def __init__(self):
        ...
        self.spy_filter = self.spe < self.spy_sma200

而且later

        if self.spy_filter:
            return

With考虑到这一点,如果我们想改变spy_filter条件下,我们将只需要在一次做到这一点__init__,而不是在多个位置该code.

同样可以适用于本等比较d < self.inds[d]["sma100"]在这里:

        # sell stocks based on criteria
        for i, d in enumerate(self.rankings):
            if self.getposition(self.data).size:
                if i > num_stocks * 0.2 or d < self.inds[d]["sma100"]:
                    self.close(d)

这也可以在预先内置__init__,因此改变像this

        # sell stocks based on criteria
        for i, d in enumerate(self.rankings):
            if self.getposition(self.data).size:
                if i > num_stocks * 0.2 or self.inds[d]["sma_signal"]:
                    self.close(d)

Personal味道2

Make一切的参数。在上述我们的线例如看到一个0.2这在代码的几个部分使用:使它parameter。相同与像其他值0.001100(这实际上已经建议作为创建均线的参数)

拥有一切作为一个参数允许打包代码,并尝试不同事情的只是改变的strategy而不是实例化战略itself.

评论被关闭。