BackTrader 中文文档(二)(1)https://developer.aliyun.com/article/1489214
运算符,使用自然构造
为了实现“易用性”目标,该平台允许(在 Python 的约束条件下)使用运算符。为了进一步增强这一目标,运算符的使用已分为两个阶段。
第 1 阶段 - 运算符创建对象
即使没有明确指定,示例已经展示了这一点。在像指标和策略这样的对象的初始化阶段(__init__
方法)中,运算符创建可以进行操作、分配或保留作为后续在策略逻辑评估阶段使用的对象。
再次展示了一个简单移动平均的潜在实现,进一步分解为步骤。
简单移动平均指标__init__
中的代码可能如下所示:
def __init__(self): # Sum N period values - datasum is now a *Lines* object # that when queried with the operator [] and index 0 # returns the current sum datasum = btind.SumN(self.data, period=self.params.period) # datasum (being *Lines* object although single line) can be # naturally divided by an int/float as in this case. It could # actually be divided by anothr *Lines* object. # The operation returns an object assigned to "av" which again # returns the current average at the current instant in time # when queried with [0] av = datasum / self.params.period # The av *Lines* object can be naturally assigned to the named # line this indicator delivers. Other objects using this # indicator will have direct access to the calculation self.line.sma = av
在策略初始化期间展示了一个更完整的用例:
class MyStrategy(bt.Strategy): def __init__(self): sma = btind.SimpleMovinAverage(self.data, period=20) close_over_sma = self.data.close > sma sma_dist_to_high = self.data.high - sma sma_dist_small = sma_dist_to_high < 3.5 # Unfortunately "and" cannot be overridden in Python being # a language construct and not an operator and thus a # function has to be provided by the platform to emulate it sell_sig = bt.And(close_over_sma, sma_dist_small)
在上述操作完成后,sell_sig是一个Lines对象,可以在策略的逻辑中稍后使用,指示条件是否满足。
第二阶段 - 自然的运算符
让我们首先记住,策略有一个next
方法,系统处理每个柱状图时都会调用该方法。这就是运算符实际处于第 2 阶段模式的地方。在前面的示例基础上构建:
class MyStrategy(bt.Strategy): def __init__(self): self.sma = sma = btind.SimpleMovinAverage(self.data, period=20) close_over_sma = self.data.close > sma self.sma_dist_to_high = self.data.high - sma sma_dist_small = sma_dist_to_high < 3.5 # Unfortunately "and" cannot be overridden in Python being # a language construct and not an operator and thus a # function has to be provided by the platform to emulate it self.sell_sig = bt.And(close_over_sma, sma_dist_small) def next(self): # Although this does not seem like an "operator" it actually is # in the sense that the object is being tested for a True/False # response if self.sma > 30.0: print('sma is greater than 30.0') if self.sma > self.data.close: print('sma is above the close price') if self.sell_sig: # if sell_sig == True: would also be valid print('sell sig is True') else: print('sell sig is False') if self.sma_dist_to_high > 5.0: print('distance from sma to hig is greater than 5.0')
不是一个非常有用的策略,只是一个例子。在第 2 阶段,运算符返回预期的值(如果测试真实性则返回布尔值,如果与浮点数比较则返回浮点数),算术运算也是如此。
注意
请注意,比较实际上并没有使用[]运算符。这是为了进一步简化事情。
if self.sma > 30.0:
… 比较self.sma[0]
和30.0
(第 1 行和当前值)
if self.sma > self.data.close:
… 比较self.sma[0]
和self.data.close[0]
一些未被覆盖的运算符/函数
Python 不允许覆盖所有内容,因此提供了一些函数来处理这些情况。
注意
仅在阶段 1 中使用,以创建稍后提供值的对象。
运算符:
and
->And
or
->Or
逻辑控制:
if
->If
函数:
any
->Any
all
->All
cmp
->Cmp
max
->Max
min
->Min
sum
->Sum
Sum
实际上使用math.fsum
作为底层操作,因为平台使用浮点数,并且应用常规的sum
可能会影响精度。reduce
->Reduce
这些实用运算符/函数操作可迭代对象。可迭代对象中的元素可以是常规的 Python 数值类型(整数,浮点数,…)也可以是具有Lines的对象。
一个生成非常愚蠢买入信号的例子:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SMA(self.data.close, period=15) self.buysig = bt.And(sma1 > self.data.close, sma1 > self.data.high) def next(self): if self.buysig[0]: pass # do something here
很明显,如果sma1
高于最高价,那么它必须高于收盘价。但重点是说明bt.And
的用法。
使用bt.If
:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SMA(self.data.close, period=15) high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high) sma2 = btind.SMA(high_or_low, period=15)
分解:
- 在
data.close
上生成一个SMA
,周期为15
- 然后
bt.If
如果sma的值大于close
,则返回low
,否则返回high
- 请记住,当调用
bt.If
时并不会返回实际值。它返回一个Lines对象,就像一个SimpleMovingAverage一样。
值将在系统运行时计算 - 生成的
bt.If
Lines对象然后被传递给第 2 个SMA
,有时会使用low
价格,有时会使用high
价格进行计算
这些函数也接受数值。同样的例子,稍作修改:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SMA(self.data.close, period=15) high_or_30 = bt.If(sma1 > self.data.close, 30.0, self.data.high) sma2 = btind.SMA(high_or_30, period=15)
现在第 2 个移动平均值使用30.0
或high
价格执行计算,取决于sma
与close
的逻辑状态
注意
值30
在内部转换为一个伪可迭代对象,始终返回30
操作平台
线迭代器
为了参与操作,平台使用线迭代器的概念。它们松散地模仿了 Python 的迭代器,但实际上与它们毫不相关。
策略和指标都是线迭代器。
线迭代器的概念试图描述以下内容:
- 一条线迭代器踢动从属线迭代器,告诉它们迭代
- 然后,线迭代器遍历其自己声明的命名行并设置值
迭代的关键,就像普通的 Python 迭代器一样,是:
next
方法
它将被每次迭代调用。 线迭代器具有的并作为逻辑/计算基础的datas
数组已经被平台移动到下一个索引(除了数据重播)
在满足线迭代器的最小周期时调用。稍后将详细介绍。
但因为它们不是常规迭代器,所以还存在两种额外的方法:
prenext
在满足最小周期的条件之前调用线迭代器。nextstart
当满足最小周期的条件时仅调用一次线迭代器。
默认行为是将调用转发给next
,但如果需要的话当然可以重写。
指标的额外方法
为了加快操作速度,指标支持一种称为 runonce 的批处理操作模式。这不是严格必需的(一个next
方法就足够了),但它极大地减少了时间。
runonce 方法规则使得与索引 0 的点的获取/设置无效,并依赖于直接访问保存数据的底层数组,并为每个状态传递正确的索引。
定义的方法遵循 next 系列的命名:
once(self, start, end)
在满足最小周期的条件时调用。必须在从内部数组的起始位置为零开始的 start 和 end 之间处理内部数组preonce(self, start, end)
在满足最小周期之前调用。oncestart(self, start, end)
当满足最小周期的条件时仅调用一次。
默认行为是将调用转发给once
,但如果需要的话当然可以重写。
最小周期
一张图值千言,而在这种情况下可能还有一个例子。一个简单移动平均能够解释它:
class SimpleMovingAverage(Indicator): lines = ('sma',) params = dict(period=20) def __init__(self): ... # Not relevant for the explanation def prenext(self): print('prenext:: current period:', len(self)) def nextstart(self): print('nextstart:: current period:', len(self)) # emulate default behavior ... call next self.next() def next(self): print('next:: current period:', len(self))
实例化可能如下所示:
sma = btind.SimpleMovingAverage(self.data, period=25)
简要解释:
- 假设传递给移动平均的数据是标准数据源,默认周期为
1
,即数据源生成一个没有初始延迟的条形图。 - 然后,**“period=25”**实例化的移动平均将按以下方式调用其方法:
prenext
24 次nextstart
1 次(依次调用next
)next
再次直到数据源耗尽
让我们来看一个致命的指标:另一个简单移动平均值的a SimpleMovingAverage。实例化可能如下所示:
sma1 = btind.SimpleMovingAverage(self.data, period=25) sma2 = btind.SimpleMovingAverage(sma1, period=20)
现在发生的情况是:
- 对于
sma1
也是如上所述 sma2
正在接收一个数据源,其最小周期为 25,这是我们的sma1
,因此sma2
方法的调用方式如下:
prenext
首次调用 25 + 18 次,总共 43 次- 25 次让
sma1
产生其第 1 个合理值。 - 18 次累积额外的
sma1
值 - 总共 19 个值(在 25 次调用后再加 1 次,然后再加 18 次)
nextstart
然后调用 1 次(轮流调用next
)next
另外调用 n 次,直到数据源耗尽
当系统已经处理了 44 个柱状时,平台会调用next
。
最小周期已自动调整为传入数据。
策略和指标遵循这种行为:
- 只有当自动计算的最小周期已达到时,才会调用
next
(除了对nextstart
的初始钩子调用)
注意。
相同的规则适用于runonce批量操作模式的preonce
、oncestart
和once
注意。
最小周期行为可以被操纵,尽管不建议这样做。如果希望使用,在策略或指标中使用setminperiod(minperiod)
方法
已启动并运行。
启动和运行至少涉及 3 个Lines对象:
- 一个数据源
- 一个策略(实际上是从策略派生的类)
- 一个 Cerebro(西班牙语中的“大脑”)。
数据源
这些对象显然提供将通过应用计算(直接和/或带有指标)进行回测的数据。
该平台提供了几个数据源:
- 几种 CSV 格式和一个通用的 CSV 阅读器
- 雅虎在线获取器
- 支持接收Pandas DataFrames和blaze对象
- 与Interacive Brokers、Visual Chart和Oanda一起的实时数据源。
该平台不对数据源的内容(如时间段和压缩)做任何假设。这些值,连同名称,可以供信息用途和高级操作(例如数据源重采样,将例如 5 分钟数据源转换为每日数据源)。
设置 Yahoo Finance 数据源的示例:
import backtrader as bt import backtrader.feeds as btfeeds ... datapath = 'path/to/your/yahoo/data.csv' data = btfeeds.YahooFinanceCSVData( dataname=datapath, reversed=True)
显示了雅虎的可选reversed
参数,因为直接从雅虎下载的 CSV 文件从最新日期开始,而不是从最旧日期开始。
如果您的数据跨越了大的时间范围,则实际加载的数据可以限制如下:
data = btfeeds.YahooFinanceCSVData( dataname=datapath, reversed=True fromdate=datetime.datetime(2014, 1, 1), todate=datetime.datetime(2014, 12, 31))
如果存在,fromdate和todate都将被包含在数据源中。
如已提及的时间段、压缩和名称可添加:
data = btfeeds.YahooFinanceCSVData( dataname=datapath, reversed=True fromdate=datetime.datetime(2014, 1, 1), todate=datetime.datetime(2014, 12, 31) timeframe=bt.TimeFrame.Days, compression=1, name='Yahoo' )
如果数据已绘制,则将使用这些值。
一个策略(派生)类
注意。
在继续之前,并且为了更简化的方法,请检查文档的Signals部分,如果不希望子类化策略。
使用该平台的任何人的目标都是对数据进行回测,这是在策略(派生类)内完成的。
至少有 2 种方法需要定制:
__init__
next
在初始化过程中,对数据和其他计算进行了创建和准备,以后将应用逻辑。
稍后将调用next
方法来应用每个数据条的逻辑。
注意
如果传递了不同时间框架(因此具有不同的条形计数)的数据源,则将调用next
方法以获取主数据(传递给 cerebro 的第一个数据,见下文),它必须是具有较小时间框架的数据
注意
如果使用数据重播功能,则将为相同的条形进行多次调用next
方法,因为条形的发展正在重播。
一个基本的派生自类的策略:
class MyStrategy(bt.Strategy): def __init__(self): self.sma = btind.SimpleMovingAverage(self.data, period=20) def next(self): if self.sma > self.data.close: self.buy() elif self.sma < self.data.close: self.sell()
策略具有其他方法(或挂钩点)可以重写:
class MyStrategy(bt.Strategy): def __init__(self): self.sma = btind.SimpleMovingAverage(self.data, period=20) def next(self): if self.sma > self.data.close: submitted_order = self.buy() elif self.sma < self.data.close: submitted_order = self.sell() def start(self): print('Backtesting is about to start') def stop(self): print('Backtesting is finished') def notify_order(self, order): print('An order new/changed/executed/canceled has been received')
start
和stop
方法应该是不言而喻的。与预期的一样,并且遵循打印函数中的文本,当策略需要通知时,将调用notify_order
方法。用例:
- 请求购买或出售(如下所示)
购买/出售将返回一个order,该订单将提交给经纪人。保留对此已提交订单的引用由调用者自行决定。
例如,可以用它来确保如果订单仍未处理,则不会提交新订单。 - 如果订单被接受/执行/取消/更改,则经纪人将通过 notify 方法将状态更改(例如执行大小)通知回策略
快速入门指南在notify_order
方法中有一个完整而实用的订单管理示例。
还可以使用其他策略类做更多事情:
buy
/sell
/close
使用底层的broker和sizer向经纪人发送购买/出售订单
也可以通过手动创建订单并将其传递给经纪人来完成相同的操作。但是该平台旨在使使用它的人更容易。close
将获取当前的市场持仓并立即关闭它。getposition
(或属性“position”)
返回当前的市场持仓setsizer
/getsizer
(或属性“sizer”)
这些允许设置/获取底层的 Stake Sizer。可以对提供相同情况的不同 Stake 的 Sizer 检查相同的逻辑(固定大小,与资本成比例,指数)
有很多文献,但 Van K. Tharp 在这个领域有出色的书籍。
BackTrader 中文文档(二)(3)https://developer.aliyun.com/article/1489218