这个问题或多或少地出现了几次:这样:
- backtrader如何最好/规范地实现这一点或那样?
作为 backtrader 的目标之一,可以灵活地 支持尽可能多的情况和用例,答案很简单:“至少在几种方式上”。针对指针进行汇总,对于这些问题最常发生的情况:
-
方法中
__init__
100% 声明性 -
100%逐步
next
的方法 -
对于声明性部分无法涵盖所有必需计算的复杂方案,混合上述两者
快速查看 backtrader 中的内置指针可发现所有这些指针都是以声明性方式实现的。原因
-
更容易做到
-
更易于阅读
-
更优雅
-
自动管理矢量化和基于偶数的实现
什么?!?!自动实现的矢量化??
是的。如果指针完全在方法内部__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)
注意
保持原样,包括长 lines ,必须水平滚动
@Rodrigo布里托已经注意到,临时lines的使用(除了mfi
)lines除外)可能是承认优化的东西。确实,但以作者*的拙见,实际上一切都承认一些优化。
为了有共同的工作基础,可以使用StockCharts的「资金流量指数」定义,并看到上面的实现是好的。这是链接:
有了这个,指针的快速规范实现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)
人们应该能够立即注意到
-
定义了单个 line
mfi
。没有临时工。 -
事情看起来更干净,不需要
[0]
数组索引 -
这里或那里没有单曲
if
-
更紧凑,更易读
如果一个人绘制一个图表,两者都针对相同的数据集运行,它看起来像这样
该图表显示,规范版本和非规范版本显示相同的值和发展,除了在开头。
-
非规范版本从一开始就提供价值
-
它传递的是无意义的值(100.0 直到它传递 1 个额外的值,这也是不好的),因为它不能正确传递
相比之下:
-
规范版本在达到最小预热期 后自动开始提供值。
-
不需要人工干预(它必须肯定是「人工智能」或「机器学习」,...双关语 )
查看受影响区域的 close图片
注意
当然,在非规范版本中,可以通过这样做来缓解这种情况:
- 子类化,其中
bt.ind.PeriodN
已经有一个period
参数,并且知道如何处理它(并在 期间__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)
没有许多lines,一个return
语句和对输出line的不同赋值,有计算的单个声明mfiratio
和单个赋值(遵循StockCharts公式)到输出linemfi
。
结论
希望这能揭示一些在以规范(即:声明性__init_
)或非规范方式(逐步与数组索引) next
实现某些内容时的差异。