Skip to content

规范指标与非规范指标

原文: https://www.backtrader.com/blog/2019-07-08-canonical-or-not/canonical-or-not/

这个问题或多或少地出现了好几次:

  • 反向交易者如何最好地/规范地实现这个或那个?

backtrader的目标之一是灵活支持尽可能多的情况和用例,答案很简单:“至少有两种方式”。针对指标进行总结,最常出现的问题是:

  • __init__方法中的 100%声明性

  • next方法中 100%逐步

  • 对于声明性部分无法覆盖所有所需计算的复杂场景,混合上述两种方法

快速查看backtrader中的内置指标可以发现,它们都是以声明性方式实现的。原因

  1. 容易做

  2. 易读

  3. 更优雅

  4. 矢量化和基于偶数的实现是自动管理的

什么自动实现的矢量化??

对如果指示器完全在__init_方法中实现,Python 中元类和运算符重载的魔力将带来以下结果

  • 矢量化实现(运行回测时的默认设置)

  • 基于事件的实现(例如,实时交易)

另一方面,如果在next方法中实施的指标的任何部分:

  • 这是直接用于基于事件的运行的代码。

  • 通过在后台为每个数据点调用next方法模拟矢量化

    笔记

    这意味着,即使某个特定指标没有矢量化的实现,所有其他指标都将运行矢量化的实现

货币流量指数:一个例子

社区用户@Rodrigo Brito发布了一个版本的“资金流指数”指标,该指标采用next方法实施。

代码

class MFI(bt.Indicator):
    lines = ('mfi', 'money_flow_raw', 'typical', 'money_flow_pos', 'money_flow_neg')

    plotlines = dict(
        money_flow_raw=dict(_plotskip=True),
        money_flow_pos=dict(_plotskip=True),
        money_flow_neg=dict(_plotskip=True),
        typical=dict(_plotskip=True),
    )

    params = (
        ('period', 14),
    )

    def next(self):
        typical_price = (self.data.close[0] + self.data.low[0] + self.data.high[0]) / 3
        money_flow_raw = typical_price * self.data.volume[0]

        self.lines.typical[0] = typical_price
        self.lines.money_flow_raw[0] = money_flow_raw

        self.lines.money_flow_pos[0] = money_flow_raw if self.lines.typical[0] >= self.lines.typical[-1] else 0
        self.lines.money_flow_neg[0] = money_flow_raw if self.lines.typical[0] <= self.lines.typical[-1] else 0

        pos_period = math.fsum(self.lines.money_flow_pos.get(size=self.p.period))
        neg_period = math.fsum(self.lines.money_flow_neg.get(size=self.p.period))

        if neg_period == 0:
            self.lines.mfi[0] = 100
            return

        self.lines.mfi[0] =  100 - 100 / (1 +  pos_period / neg_period) 

笔记

保持原样,包括必须水平滚动的长线

@Rodrigo Brito已经注意到临时行的使用(除mfi之外的所有行)可能允许优化。事实上,但以作者的拙见*,事实上每件事都承认有点优化。

为了有共同的工作基础,可以使用股票图表中的“资金流指数”定义,看看上面的实现是好的。以下是链接:

有了这一点,MFI指标的快速规范实现

class MFI_Canonical(bt.Indicator):
    lines = ('mfi',)
    params = dict(period=14)

    def __init__(self):
        tprice = (self.data.close + self.data.low + self.data.high) / 3.0
        mfraw = tprice * self.data.volume

        flowpos = bt.ind.SumN(mfraw * (tprice > tprice(-1)), period=self.p.period)
        flowneg = bt.ind.SumN(mfraw * (tprice < tprice(-1)), period=self.p.period)

        mfiratio = bt.ind.DivByZero(flowpos, flowneg, zero=100.0)
        self.l.mfi = 100.0 - 100.0 / (1.0 + mfiratio) 

人们应该能够立即注意到

  • 定义了一行mfi。那里没有临时人员。

  • 事情看起来更干净,不需要[0]数组索引

  • 这里或那里没有单身if

  • 更紧凑,同时更具可读性

如果其中一个绘制了两个运行在同一数据集上的图形,它将如下所示

!MFI

图表显示,除了在开头的之外,规范的非规范的版本都显示了相同的价值和发展。

  • 非规范版本从一开始就在传递价值

  • 它提供了无意义的价值(100.0,直到它提供了 1 个额外的价值,这也是不好的),因为它不能正确地提供

相反:

  • 标准版本在达到最小预热期后自动开始传递值。

  • 不需要人工干预(必须是“人工智能”“机器学习”双关语

请参见受影响区域的特写图片

!MFI Start Close up

笔记

当然,在非规范版本中,人们可以通过这样做来缓解这种情况:

  • 从已经有一个period参数并且知道如何处理它的bt.ind.PeriodN子类化(并在__init__期间调用super

还要注意的是,规范的版本也说明了这一点,就像公式中可能的除以零的情况的逐步next代码一样。

 if neg_period == 0:
            self.lines.mfi[0] = 100
            return

        self.lines.mfi[0] =  100 - 100 / (1 +  pos_period / neg_period) 

而这是另一种方法

 mfiratio = bt.ind.DivByZero(flowpos, flowneg, zero=100.0)
        self.l.mfi = 100.0 - 100.0 / (1.0 + mfiratio) 

对于输出,没有多行、return语句和不同的赋值,而是对mfiratio计算进行了一次声明,并对输出行mfi进行了一次赋值(遵循股票图表公式)。

结论

希望这能让我们了解在规范的(即__init_中的声明性非规范的方式(逐步使用next中的数组索引)中实现某些内容时可能存在的差异


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组