操作平台#

行迭代器 *** ** ** **

为了进行操作,平台使用了“行迭代器”的概念。它们与Python的迭代器有一定的相似之处,但实际上与其无关。

策略和指标是“行迭代器”。

“行迭代器”概念试图描述以下内容:

  • 行迭代器 通知从属于它的 行迭代器 开始迭代。

  • 然后, 行迭代器 将迭代其自己声明的具有名称的行来设置数值。

与常规的Python迭代器一样,迭代的关键在于:

  • next 方法

    它将在每次迭代时调用。 行迭代器 具有的将作为逻辑/计算基础的 datas 数组已经被平台移动到了下一个索引位置(除非进行了“数据回放”)。

    当满足 行迭代器最小周期 时,将调用该方法。关于这一点稍后会详细解释。但是因为它们不是常规的迭代器,所以还有两个额外的方法:

  • prenext

    在`行迭代器``的 最小周期 满足之前调用。

  • nextstart

    当`行迭代器``的 最小周期 满足时,仅调用 一次

    默认行为是将调用转发给 next ,但如果需要的话,当然可以覆盖它。

针对 指示器 的额外方法#

为了加快操作速度, 指示器 支持了一个批处理操作模式,被称为 runonce 。这并不是严格需要的(只需要一个 next 方法就足够了),但它大大缩短了时间。

runonce 方法规则无效化了使用索引为0的 get/set 位置,并依赖于对保存数据的底层数组进行直接访问,并传递给每个状态的正确索引。

下面定义的方法遵循了 next 家族的命名方式: - once(self, start, end)

当满足最小周期时调用。在内部数组的从开始到结束的位置之间必须进行处理,这些位置是从内部数组的起始位置开始计算的。

  • preonce(self, start, end)

    在满足最小周期之前调用。

  • oncestart(self, start, end)

    当满足最小周期时,仅调用一次。

    默认行为是将调用转发给 once ,但如果需要,可以覆盖。

最小周期#

图片胜过千言万语,在这种情况下可能还有一个例子。SimpleMovingAverage能够解释它:

class SimpleMovingAverage(指标):

lines = (‘sma’,) params = dict(period=20) def __init__(self):

… # 不相关的解释

def prenext(self):

print(‘prenext:: 当前周期:’, len(self))

def nextstart(self):

print(‘nextstart:: 当前周期:’, len(self)) # 模拟默认操作…调用next self.next()

def next(self):

print(‘next:: 当前周期:’, len(self))

实例化可以如下所示:

sma = btind.SimpleMovingAverage(self.data, period=25)

简要解释:

  • 假设传递给移动平均值的数据是标准数据源,其默认周期为 1 ,即数据源产生一根没有初始延迟的K线。

  • 然后, “period=25” 实例化的移动平均值将按以下方式调用其方法:

    • prenext 调用 24 次

    • nextstart 调用 1 次(随后调用 next

    • next 调用 n 次,直到 数据源 耗尽为止让我们来使用杀手指标:在另一个简单移动平均线(SimpleMovingAverage)上计算一个简单移动平均线(SimpleMovingAverage)。实例化的代码如下:

```python sma1 = btind.SimpleMovingAverage(self.data, period=25)

sma2 = btind.SimpleMovingAverage(sma1, period=20) ```

现在发生的情况是:

  • 对于 sma1 ,与上述相同

  • sma2 接收了一个 数据源 ,其 最小周期 为25,即我们的 sma1

  • 执行了以下 SMA2 方法:

    • prenext 方法首先执行了25 + 18次,总共43次 - 前25次用于产生 sma1 的第一个可靠值 - 后18次用于累计额外的 sma1 值- 以19个值的总数进行调用(在第25次调用之后还有18次)。

  • nextstart 会被调用1次(依次调用 next

  • next 会被调用n次,直到 数据流 用尽

当系统已经处理了44个条形图时,平台会调用 next

最小周期 已经自动调整为传入的`数据`。

策略和指标都遵循这种行为:

  • 只有当自动计算的最小周期达到之后,才会调用 next (除了首次调用 nextstart

备注

对于 runonce 批处理操作模式,对于 preonceoncestartonce 也适用相同的规则。

备注

最小周期 的行为可以被修改,尽管不建议这样做。如果希望修改,可以在策略或指标中使用 setminperiod(minperiod) 方法。

开始运行#

启动和运行至少需要 3 个 Lines 对象:- 一个数据源
  • 一个策略(实际上是从策略派生的类)

  • 一个Cerebro(西班牙语中的“大脑”)

数据源#

这些对象提供将通过应用计算(直接和/或通过指标)进行回测的数据

该平台提供了几种数据源:

  • 几个 CSV 格式和通用的 CSV 读取器

  • 雅虎在线获取器

  • 支持接收 Pandas DataFrames 和 blaze 对象

  • 使用 Interacive Brokers、Visual Chart 和 Oanda 的实时数据源

该平台对数据源的内容(如时间范围和压缩)不做任何假设。这些值和名称可以提供用于信息目的和高级操作,比如数据源重新采样(将例如一个5分钟的数据源转换为每日的数据源)

设置雅虎财经数据源的示例:

import backtrader as bt
import backtrader.feeds as btfeeds

...

datapath = 'path/to/your/yahoo/data.csv'``data = btfeeds.YahooFinanceCSVData(
    dataname=datapath,
    reversed=True)``

在Yahoo中,可选的 reversed 参数是显示的,因为从Yahoo直接下载的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’

)``

如果绘制数据,将使用这些值。

策略(派生)类#

备注

在继续之前,并为了更简化的方法,请检查文档中的*信号*部分,如果不希望子类化策略。使用本平台的目标是对数据进行回测,这是在策略(派生类)中完成的。

至少需要自定义的有两个方法:

  • __init__

  • next

在初始化过程中,对数据进行指标计算和其他计算的准备,以便后续应用逻辑。

接下来的方法会被调用来对每个数据条应用逻辑。

注意

如果传递了不同时间框架的数据源(因此有不同的数据条数),则 next 方法将被调用用于主数据源(在cerebro中传递的第一个数据源),它必须是时间框架较小的数据。

注意

如果使用了数据回放功能, next 方法将多次被调用,用于对同一数据条进行回放。

一个基本的策略派生类:

class MyStrategy(bt.Strategy):

def __init__(self):```python

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()

```

策略有其他可重写的方法(或挂钩点):

```python 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(‘即将开始回测’)

def stop(self):

print(‘回测已完成’)

def notify_order(self, order):

print(‘已接收到一个新的/更改的/执行的/撤销的订单’)

“start”和“stop”方法应该是不言自明的。按照打印函数中的文本,预计“notify_order”方法会在策略需要通知时被调用。使用案例:

  • 请求买入或卖出(如下所示)

    买入/卖出将返回一个“order”,该订单将被提交给经纪人。将提交的订单保留下来由调用者决定。

    例如,可用于确保在订单未决时不提交新订单。

  • 如果订单被接受/执行/取消/更改,经纪人将通过通知方法将状态更改(例如执行数量)发送回策略

在“notify_order”方法中,《快速入门指南》有一个完整且功能完备的订单管理示例。更多的功能可以通过其他的策略类来实现:

  • buy / sell / close

    使用底层的 经纪人 和*大小配置器*向经纪人发送买入/卖出订单。

    也可以手动创建一个订单并将其传递给经纪人来完成相同的操作。但是,这个平台旨在让使用者能够轻松使用。

    close 会获取当前的市场仓位并立即平仓。

  • getposition (或属性 “position”)

    返回当前的市场仓位。

  • setsizer / getsizer (或属性 “sizer”)

    这些允许设置/获取底层的仓位大小配置器。可以通过不同的仓位大小配置器来对同一情况进行不同的配置(固定大小、与资本成比例、指数增长)。

    有很多相关的文献,但Van K. Tharp在这个主题上有很多优秀的著作。策略是 Lines 对象,它们支持参数,这些参数使用标准的Python kwargs参数来收集。

```python class MyStrategy(bt.Strategy):

params = ((‘period’, 20),)

def __init__(self):

self.sma = btind.SimpleMovingAverage(self.data, period=self.params.period)

#

```

注意如何不再用固定值20实例化 SimpleMovingAverage ,而是用已为策略定义的参数”period”来实例化。

Cerebro *** ** **

一旦数据源可用并且策略已定义,Cerebro实例是将所有内容汇总并执行操作的关键。实例化一个很简单:

```python cerebro = bt.Cerebro() ```如果没有特殊要求,缺省值会生效。

  • 创建了一个默认的经纪人

  • 操作没有佣金

  • 数据源将被预加载

  • 默认的执行模式是batch(批量操作),速度更快

    所有指标都必须支持 runonce 模式以获得最快的速度。平台中包含的指标都支持。

    自定义指标不需要实现runonce功能。 Cerebro 将模拟它,这意味着那些不兼容runonce的指标将运行得较慢。但系统的大部分操作仍将在批处理模式下运行。

由于已经有了数据源和策略(之前创建的),将它们组合并使其运行的标准方式是:

cerebro.adddata(data) cerebro.addstrategy(MyStrategy, period=25) cerebro.run()

请注意以下事项:

  • 数据源“实例”被添加进来

  • MyStrategy “类”与参数(kwargs)一起被添加进来,这些参数将被传递给它。

    MyStrategy的实例化将由cerebro在后台完成,并且 addstrategy 中的任何kwargs都将被传递给它。用户可以根据需要添加任意多的策略和数据源。策略之间如何协调通信(如果需要的话)并没有受到平台的限制。

当然,Cerebro提供了额外的可能性:

  • 决定预加载和操作模式:

    cerebro = bt.Cerebro(runonce=True, preload=True)
    

    这里有一个限制: runonce 需要预加载(否则无法运行批量操作)。当然,预加载数据源不强制要求使用 runonce

  • setbroker / getbroker (和 broker 属性)

    如果需要,可以设置自定义的经纪人。也可以访问实际的经纪人实例。

  • 绘图。在普通情况下非常简单:

    cerebro.run()
    cerebro.plot()
    

    plot 函数可以接受一些参数用于自定义。- numfigs=1

如果图太密集,可以将其分解为多个图。

  • plotter=None

    可以传递一个自定义的绘图实例,cerebro 不会自动初始化一个默认的实例。

  • **kwargs - 标准关键字参数

    这些参数将传递给绘图实例。

更多信息,请参见绘图部分。

  • 策略的优化。

如上所述,Cerebro 接收一个派生自 Strategy 类的类(而不是实例)以及调用 “run” 时将传递给它的关键字参数。

这样做是为了实现优化。相同的 Strategy 类将根据需要实例化多次,并使用新的参数。如果一个实例被传递给 cerebro… 这是不可能的。如下所示,要求进行优化:

` cerebro.optstrategy(MyStrategy, period=xrange(10, 20)) `

optstrategy 方法和 addstrategy 具有相同的签名,但会执行额外的工作以确保优化如预期运行。 一个策略可以期望作为策略的一个普通参数的是一个 range ,而 addstrategy 不会对传递的参数进行任何假设。

另一方面, optstrategy 会理解可迭代对象是一组值,必须按照顺序传递给每个 Strategy 类的实例化。

需要注意的是,传递的是一组值而不是单个值。在这个简单的例子中,将尝试使用这个策略的 10 个值 10 -> 19(20 是上限)。

如果开发了一个具有额外参数的更复杂的策略,可以将它们全部传递给 optstrategy 。不需要进行优化的参数可以直接传递,而无需用户创建一个只包含一个值的假可迭代对象。例如:

` cerebro.optstrategy(MyStrategy, period=xrange(10, 20), factor=3.5) `

optstrategy 方法会创建 dummy iterable 来处理 factor(这是一个必需的),dummy iterable 只有一个元素(在这个例子中为 3.5)。

备注

交互式 Python Shell 和某些类型的冻结可执行文件在 Windows 下对 Python 的 multiprocessing 模块存在问题。

请阅读有关 multiprocessing 的 Python 文档。