策划
尽管回溯测试是一个基于数学计算的自动化过程,但通常情况下,人们希望实际可视化正在发生的事情。无论是使用经过回溯测试运行的现有算法,还是查看指标(内置或自定义)随数据交付的内容。
因为每件事背后都有一个人,绘制数据源、指标、操作、现金和投资组合价值的演变图可以帮助人们更好地了解正在发生的事情,抛弃/修改/创造想法,以及看图表的人对视觉信息可能做的任何事情。
这就是为什么backtrader使用matplotlib
提供的工具,提供内置的制图工具。
如何策划
任何回溯测试运行都可以通过调用单个方法来绘制:
cerebro.plot()
当然,这通常是最后发出的命令,就像在这个简单的代码中一样,它使用来自backtrader源的一个样本数据。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
class St(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data)
data = bt.feeds.BacktraderCSVData(dataname='../../datas/2005-2006-day-001.txt')
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(St)
cerebro.run()
cerebro.plot()
这就得到了下面的图表。
该图表包括 3 名观察者,在这种情况下,考虑到缺乏任何交易,这些观察者大多毫无意义
-
顾名思义,一名
CashValue
观察员在回测运行期间跟踪Cash
和Value
总波尔图(包括现金) -
Trade
观察者,在交易结束时显示实际损益交易定义为打开一个头寸并将该头寸带回
0
(直接或从多头到空头或从空头到多头的交叉) -
一个
BuySell
观察者,在买入和卖出操作发生的位置(在价格之上)进行绘图
这些3 个观察者由cerebro
自动添加,并由stdstats
参数控制(默认值:True
。如果需要,请执行以下操作以禁用它们:
cerebro = bt.Cerebro(stdstats=False)
或以后当运行时,如:
cerebro = bt.Cerebro()
...
cerebro.run(stdstats=False)
标绘元素
尽管Observers
在导言中已经提到,但它们并不是绘制的唯一元素。这三件事是有计划的:
-
用
adddata
、replaydata
和resampledata
向大脑添加数据源 -
指标在策略层面上声明(或添加到大脑中的
addindicator
纯粹用于实验目的,并将指标添加到虚拟策略中) -
观察者添加到大脑中
addobserver
观察者是与策略同步运行的线路对象,可以访问整个生态系统,能够跟踪
Cash
和Value
等事物
打印选项
指示器和观察者有几个选项,可以控制它们在图表上的绘制方式。有三大集团:
-
影响整个对象打印行为的选项
-
影响各条线打印行为的选项
-
影响系统范围打印选项的选项
对象范围打印选项
这些由指示器和观察者中的该数据集控制:
plotinfo = dict(plot=True,
subplot=True,
plotname='',
plotskip=False,
plotabove=False,
plotlinelabels=False,
plotlinevalues=True,
plotvaluetags=True,
plotymargin=0.0,
plotyhlines=[],
plotyticks=[],
plothlines=[],
plotforce=False,
plotmaster=None,
plotylimited=True,
)
虽然plotinfo
在类定义时显示为dict
,但backtrader的元类机制将其转化为一个被继承的对象,甚至可以经历多重继承。这意味着:
- 如果子类将
subplot=True
之类的值更改为subplot=False
,则层次结构较低的子类将后者作为subplot
的默认值
有两种方法可以为这些参数赋值。让我们看看 1st方法的SimpleMovingAverage
实例化:
sma = bt.indicators.SimpleMovingAverage(self.data, period=15, plotname='mysma')
从示例中可以推断,SimpleMovingAverage
构造函数未使用的任何**kwargs
都将被解析为plotinfo
值(如果可能)。SimpleMovingAverage
定义了一个参数period
。这意味着plotname
将与plotinfo
中同名的参数匹配。
2nd方法:
sma = bt.indicators.SimpleMovingAverage(self.data, period=15)
sma.plotinfo.plotname = 'mysma'
可以访问沿着SimpleMovingAverage实例化的plotinfo
对象,也可以使用标准的Python点符号访问其中的参数。比语法 abve 更简单、更清晰。
期权的含义
-
plot
:是否需要打印对象 -
subplot
:是沿着数据还是在独立的子图中绘图。移动平均线是在数据上绘制的示例。随机和RSI是以不同比例绘制在子图表中的事物的示例。 -
plotname
:在图表上使用的名称,而不是类名称。如上例所示,用mysma
代替SimpleMovingAverage
-
plotskip
(弃用):旧别名plot
-
plotabove
:是否在数据上方绘制。否则,请在下面绘制。只有在subplot=True
的情况下才有效 -
plotlinelabels
:当subplot=False
时,是否沿图表上图例中的数据绘制各条线的名称示例:波林格带有 3 条线,但指示器绘制在数据顶部。让图例只显示一个名称,如
BollingerBands
,而不是显示 3 个单独行的名称(mid
、top
、bot
)似乎是明智的这方面的一个用例是
BuySell
观察者,对于该观察者,显示两条线的名称及其标记是有意义的:Buy
和Sell
,以便最终用户清楚地知道这是什么。 -
plotlinevalues
:控制指示器和观察者中线条的图例是否具有最后绘制的值。每行可通过_plotvalue
控制 -
plotvaluetags
:控制是否在行的右侧绘制带有最后一个值的值标记。每行可通过_plotvaluetag
控制 -
plotymargin
:添加到图表上各个子图表顶部和底部的边距这是一个百分比,但以 1 为基础。例如:0.05->5%
-
plothlines
:一个可编辑的,包含水平线必须绘制的值(在刻度内)。例如,这有助于具有超买、超卖区域的经典指标,如
RSI
,通常在70
和30
处绘制线条 -
plotyticks
:一个可编辑的,包含数值(在刻度内),在该数值处,刻度上必须特别放置数值记号例如,强制电子秤有一个
50
来标识电子秤的中点。虽然这看起来很明显,但指示器使用自动缩放机制,如果带有0-100
刻度的指示器定期在 30-95 之间移动,则50
可能不会明显位于中心。 -
plotyhlines
:一个可编辑的,包含水平线必须绘制的值(在刻度内)。这可以同时接管
plothlines
和plotyticks
。如果以上都没有定义,那么放置水平线和刻度的位置将完全由该值控制
如果定义了上述任何一项,则其优先于此选项中的值
-
plotforce
:有时,因此将数据源与指标和 bla、bla、bla 匹配的复杂过程……自定义指标可能无法绘制。这是尝试强制打印的最后手段。如果其他一切都失败了,就使用它
-
plotmaster
:一个指示器/观察者有一个主设备,该主设备是数据正在工作。在某些情况下,可能需要使用不同的母版绘制。一个用例是
PivotPoint
指标,它根据月数据计算,但用于日数据。只有将其绘制在每日数据上才有意义,而该数据正是该指示器有意义的地方。 -
plotylimited
:目前仅适用于数据源。如果True
(默认),数据图上的其他行不会改变比例。示例:布林带(顶部和底部)可能远离数据馈送的实际绝对最小值/最大值。使用\
plotlimited=True, those bands remain out of the chart, because the data controls the scaling. If set to
False`,条带会影响 y 刻度,并在图表上可见一个用例是
PivotPoint
指标,它根据月数据计算,但用于日数据。只有将其绘制在每日数据上才有意义,而该数据正是该指示器有意义的地方。
特定于线的打印选项
指标/观察者有线且该线的绘制方式会受到plotlines
对象的影响。plotlines
中指定的大多数选项在绘图时直接传递给matplotlib
。因此,文档依赖于已经完成的事情的示例。
重要:选项是按行指定的。
部分期权由反向交易者直接控制。这些都以下划线(_
开头):
-
_plotskip
(布尔值),表示如果设置为True
,则必须跳过特定直线的绘制 -
_plotvalue
(布尔值)控制此行图例是否包含最后打印的值(默认为True
) -
_plotvaluetag
(布尔值)用于控制是否绘制带有最后一个值的右手侧标签(默认为True
) -
_name
(字符串)用于更改特定行的绘图名称 -
_skipnan
(bool,默认值:False):打印时跳过NaN
值,例如允许在指示器生成的两个距离点之间绘制一条线,其所有中间值为NaN
(新创建数据点的默认值) -
_samecolor
(布尔值)这将强制下一行具有与上一行相同的颜色,从而避免matplotlib
默认机制,即为每个新打印的元素循环使用颜色贴图 -
选择绘图方法
matplotlib
的_method
(字符串将用于元素。如果未指定,则将选择最基本的plot
方法。来自
MACDHisto
的示例。此处histo
线绘制为bar
,这是行业事实上的标准。在MACDHisto
的定义中可以找到以下定义:py lines = ('histo',) plotlines = dict(histo=dict(_method='bar', alpha=0.50, width=1.0))
alpha
和width
是matplotlib的选项 -
_fill_gt
/_fill_lt
允许在给定线和以下线之间填充:
-
另一行
-
数值
参数是由 2 个元素组成的 iterable,其中:
-
1st参数是字符串(参考线名称)或数值
填充将在自身值与行值或数值之间进行
-
2nd参数为:
- 具有颜色名称(matplotlib兼容)或十六进制规范的字符串(参见matloplit示例)
或
- 一个 iterable,其中 1st元素是颜色的字符串/十六进制值,第二个元素是指定 alpha 透明度的数值(默认值:
0.20
在打印方案中由fillalpha
控制)
示例:
```py
Fill for myline when above other_line with colour red
plotlines = dict( myline=dict(_fill_gt('other_line', 'red')) )
Fill for myline when above 50 with colour red
plotlines = dict( myline=dict(_fill_gt(50, 'red)) )
Fill for myline when above other_line with colour red and 50%
transparency (1.0 means "no transparency")
plotlines = dict( myline=dict(_fill_gt('other_line', ('red', 0.50))) ) ```
-
将选项传递到未知行
- Ue 名称
_X
,其中X
表示零基索引中的数字。这意味着选项用于第X
行
来自OscillatorMixIn
的用例:
plotlines = dict(_0=dict(_name='osc'))
顾名思义,这是一个mixin类,用于多重继承方案(特别是在右侧)。mixin不知道 1st行的实际名称(索引以零为基础),而另一个指标将是多重继承混合的一部分。
这就是为什么将选项指定为:_0
。子类化完成后,结果类的 1st行将在绘图中显示名称osc
。
一些绘图线示例
BuySell
观察者具有以下特征:
plotlines = dict(
buy=dict(marker='^', markersize=8.0, color='lime', fillstyle='full'),
sell=dict(marker='v', markersize=8.0, color='red', fillstyle='full')
)
buy
和sell
行具有直接传递给matplotlib
的选项,用于定义标记、标记化、颜色和填充样式。所有这些选项都在matplotlib中定义
Trades
观察者具有以下特征:
...
lines = ('pnlplus', 'pnlminus')
...
plotlines = dict(
pnlplus=dict(_name='Positive',
marker='o', color='blue',
markersize=8.0, fillstyle='full'),
pnlminus=dict(_name='Negative',
marker='o', color='red',
markersize=8.0, fillstyle='full')
)
这里,通过使用_name
将行的名称从例如pnlplus
重新定义为Positive
。其余选项用于matplotlib
DrawDown
观察员:
lines = ('drawdown', 'maxdrawdown',)
...
plotlines = dict(maxdrawdown=dict(_plotskip='True',))
这个定义了两行,让最终用户不仅可以访问当前drawdown
的值,还可以访问其最大值(maxdrawdown
。但由于_plotskip=True
的原因,后者未绘制
BollingerBands
指示器:
plotlines = dict(
mid=dict(ls='--'),
top=dict(_samecolor=True),
bot=dict(_samecolor=True),
)
此处mid
线将具有虚线样式,top
和bot
线将具有与mid
线相同的颜色。
Stochastic
(在_StochasticBase
中定义并继承):
lines = ('percK', 'percD',)
...
plotlines = dict(percD=dict(_name='%D', ls='--'),
percK=dict(_name='%K'))
较慢的线percD
以虚线样式绘制。并且这些行的名称被更改为包含花式的%
符号(%K
和%D
,这些符号不能用于Python中的名称定义中
控制标绘的方法
在处理指标和观察者时,支持以下方法进一步控制标绘:
-
_plotlabel(self)
它应该返回一个符合标签的事物列表,该列表将放在指示器或观察者名称后的括号中
RSI
指示器的一个示例:py def _plotlabel(self): plabels = [self.p.period] plabels += [self.p.movav] * self.p.notdefault('movav') return plabels
可以看出,此方法返回:
-
一个
int
,表示为RSI
配置的时段,如果默认移动平均值已更改,则表示特定类别在后台,两者都将转换为字符串。在类的情况下,将努力只打印类的名称,而不是完整的
module.name
组合。
-
-
_plotinit(self)
在绘图开始时调用,以执行指示器可能需要的任何特定初始化。同样,来自
RSI
的示例:py def _plotinit(self): self.plotinfo.plotyhlines = [self.p.upperband, self.p.lowerband]
在这里,代码为
plotyhlines
指定一个值,以在特定y
值处绘制水平线(hlines
部分)。参数
upperband
和lowerband
的值用于此目的,由于最终用户可以更改参数,因此无法事先知道
系统范围打印选项
首先是大脑内plot
的签名:
def plot(self, plotter=None, numfigs=1, iplot=True, **kwargs):
这意味着:
-
plotter
:一个对象/类,包含控制系统范围打印的选项作为属性如果传递了
None
,将实例化一个默认的PlotScheme
对象(见下文) -
numfigs
:在多少独立图表中,一个情节必须被打破有时一张图表包含太多的条形图,如果放在一个单独的图形中,将很难阅读。这会根据需要将其分解为多个部分
-
iplot
:如果在 Jupyter 笔记本中运行,则自动内联打印 -
**kwargs
:参数用于更改plotter
的属性值,如果没有传递plotter
,则更改创建的默认PlotScheme
对象的属性值。
绘图方案
此对象包含控制系统范围打印的所有选项。这些选项记录在代码中:
class PlotScheme(object):
def __init__(self):
# to have a tight packing on the chart wether only the x axis or also
# the y axis have (see matplotlib)
self.ytight = False
# y-margin (top/bottom) for the subcharts. This will not overrule the
# option plotinfo.plotymargin
self.yadjust = 0.0
# Each new line is in z-order below the previous one. change it False
# to have lines paint above the previous line
self.zdown = True
# Rotation of the date labes on the x axis
self.tickrotation = 15
# How many "subparts" takes a major chart (datas) in the overall chart
# This is proportional to the total number of subcharts
self.rowsmajor = 5
# How many "subparts" takes a minor chart (indicators/observers) in the
# overall chart. This is proportional to the total number of subcharts
# Together with rowsmajor, this defines a proportion ratio betwen data
# charts and indicators/observers charts
self.rowsminor = 1
# Distance in between subcharts
self.plotdist = 0.0
# Have a grid in the background of all charts
self.grid = True
# Default plotstyle for the OHLC bars which (line -> line on close)
# Other options: 'bar' and 'candle'
self.style = 'line'
# Default color for the 'line on close' plot
self.loc = 'black'
# Default color for a bullish bar/candle (0.75 -> intensity of gray)
self.barup = '0.75'
# Default color for a bearish bar/candle
self.bardown = 'red'
# Level of transparency to apply to bars/cancles (NOT USED)
self.bartrans = 1.0
# Wether the candlesticks have to be filled or be transparent
self.barupfill = True
self.bardownfill = True
# Wether the candlesticks have to be filled or be transparent
self.fillalpha = 0.20
# Wether to plot volume or not. Note: if the data in question has no
# volume values, volume plotting will be skipped even if this is True
self.volume = True
# Wether to overlay the volume on the data or use a separate subchart
self.voloverlay = True
# Scaling of the volume to the data when plotting as overlay
self.volscaling = 0.33
# Pushing overlay volume up for better visibiliy. Experimentation
# needed if the volume and data overlap too much
self.volpushup = 0.00
# Default colour for the volume of a bullish day
self.volup = '#aaaaaa' # 0.66 of gray
# Default colour for the volume of a bearish day
self.voldown = '#cc6073' # (204, 96, 115)
# Transparency to apply to the volume when overlaying
self.voltrans = 0.50
# Transparency for text labels (NOT USED CURRENTLY)
self.subtxttrans = 0.66
# Default font text size for labels on the chart
self.subtxtsize = 9
# Transparency for the legend (NOT USED CURRENTLY)
self.legendtrans = 0.25
# Wether indicators have a leged displaey in their charts
self.legendind = True
# Location of the legend for indicators (see matplotlib)
self.legendindloc = 'upper left'
# Plot the last value of a line after the Object name
self.linevalues = True
# Plot a tag at the end of each line with the last value
self.valuetags = True
# Default color for horizontal lines (see plotinfo.plothlines)
self.hlinescolor = '0.66' # shade of gray
# Default style for horizontal lines
self.hlinesstyle = '--'
# Default width for horizontal lines
self.hlineswidth = 1.0
# Default color scheme: Tableau 10
self.lcolors = tableau10
# strftime Format string for the display of ticks on the x axis
self.fmt_x_ticks = None
# strftime Format string for the display of data points values
self.fmt_x_data = None
绘图方案中的颜色
PlotScheme
类定义了一个可以在子类中重写的方法,该子类返回要使用的下一种颜色:
def color(self, idx)
其中idx
是绘制在单个子图上的线的当前索引。例如,MACD
绘制了 3 条线,因此idx
变量将仅具有以下值:0
、1
和2
。下一张图表(可能是另一个指示器)将在0
处再次显示计数。
反向交易者使用的默认配色方案(如上所示)为Tableau 10 Color Palette
,索引修改为:
tab10_index = [3, 0, 2, 1, 2, 4, 5, 6, 7, 8, 9]
通过重写color
方法或将lcolors
变量传递给plot
(或在PlotScheme
的子类中),可以完全改变颜色。
源代码还包含对Tableau 10 Light
和Tableau 20
调色板的定义。