无论是回测还是交易,能够分析交易系统的性能是了解是否不仅获得了利润,而且还获得了太多的风险,或者与其他交易相比是否真的值得付出努力参考资产(或无风险资产)

这就是Analyzer对象系列的用武之地:提供对发生的事情甚至实际发生的事情的分析。

分析仪的性质

该接口是根据Lines对象的接口建模的,例如一个 next方法,但有一个主要区别:

  • Analyzers不要保持线。

    这意味着它们在内存方面并不昂贵,因为即使在分析了数千条价格柱之后,它们仍然可能只是在内存中保存一个结果。

在生态系统中的位置

Analyzer对象(如策略观察者数据cerebro)通过实例添加到系统中:

  • addanalyzer(ancls, *args, **kwargs)

但是当涉及到以下操作时,系统中存在的cerebro.run每个策略都会发生

  • ancls将被实例*args**kwargscerebro.run
  • 实例将ancls附加到策略

这意味着:

  • 如果回测运行包含例如3 个策略,则将创建3 个实例ancls并且每个实例都将附加到不同的策略。

底线:分析器分析单个策略的性能,而 不是整个系统的性能

附加位置

有些Analyzer对象实际上可能会使用其他分析器来完成其工作。例如:SharpeRatio使用 的输出TimeReturn进行计算。

这些分析器从分析器也将被插入到与创建它们的策略相同的策略中。但它们对用户是完全不可见的。

属性

为了执行预期的工作,Analyzer为对象提供了一些默认属性,这些属性会自动传递并在实例中设置以方便使用:

  • self.strategy: 引用分析器对象在其中运行的策略子类。分析器也可以访问策略可访问的任何内容
  • self.datas[x]:策略中存在的数据馈送数组。虽然这可以通过策略参考来访问,但快捷方式使工作更加舒适。
  • self.data:获得额外舒适感的捷径self.datas[0]
  • self.dataX: 不同的捷径self.datas[x]

其他一些别名是可用的:

* `self.dataX_Y` 其中 X 是对 `self.datas[X]` 的引用,`Y`是指行,最后指向:`self.datas[X].lines[Y]`

如果该行有名称,则以下内容也可用:
* `self.dataX_Name` 解析为 `self.datas[X].Name`按名称而不是索引返回行

对于第一个数据,最后两个快捷方式在没有初始 X数字引用的情况下可用。例如:
* `self.data_2` 指的是`self.datas[0].lines[2]`

返回分析

Analyzer基类创建一个self.rets(类型为 collections.OrderedDict)成员属性以返回分析。这是在create_analysis创建自定义分析器时可以被子类覆盖的方法中完成的。

操作模式

尽管Analyzer对象不是Lines对象,因此不会迭代行,但它们被设计为遵循相同的操作模式。

  1. 在系统启动之前实例化(因此调用 __init__
  2. start
  3. prenext//将在指标工作策略nextstart的计算最小周期之后调用。next

    prenextand的默认行为nextstart是调用 next,因为分析器可能从系统处于活动状态的第一刻起就开始分析。

    len(self)习惯上调用Lines对象来检查柱的实际数量。这也可以Analyzers通过返回值来实现self.strategy

  4. 订单和交易将像通知策略一样通过 notify_ordernotify_trade
  5. 现金和价值也将被通知,就像通过notify_cashvalue方法上的策略完成一样
  6. 现金、价值和基金价值以及基金份额也将被通知,就像通过notify_fund方法完成的策略一样
  7. stop将被调用以表示操作结束

完成常规操作周期后,分析仪具有用于提取/输出信息的其他方法

  • get_analysis: 理想情况下(不强制)返回一个dict包含分析结果的类似对象。
  • print使用标准backtrader.WriterFile(除非被覆盖)从get_analysis.
  • pprintpretty print ) 使用 Pythonpprint模块来打印 get_analysis结果。

最后:

  • get_analysis创建分析器写入分析结果的成员属性self.ret(类型 )。collections.OrderedDict

    Analyzer的子类可以重写此方法以更改此行为

分析器模式

平台中分析器对象的开发backtrader揭示了生成分析的 2 种不同使用模式:

  1. notify_xxx在执行期间通过在和 方法中收集信息next,并生成分析的当前信息next

    例如TradeAnalyzer,仅使用notify_trade方法来生成统计信息。

  2. 收集(或不收集)上述信息,但在stop方法期间一次性生成分析

    System Quality NumberSQN )在该方法期间收集交易信息, 但在该方法期间生成统计信息notify_tradestop

一个简单的例子

尽可能简单:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime

import backtrader as bt
import backtrader.analyzers as btanalyzers
import backtrader.feeds as btfeeds
import backtrader.strategies as btstrats

cerebro = bt.Cerebro()

# data
dataname = '../datas/sample/2005-2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=dataname)

cerebro.adddata(data)

# strategy
cerebro.addstrategy(btstrats.SMA_CrossOver)

# Analyzer
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='mysharpe')

thestrats = cerebro.run()
thestrat = thestrats[0]

print('Sharpe Ratio:', thestrat.analyzers.mysharpe.get_analysis())

执行它(已将其存储在analyzer-test.py

$ ./analyzer-test.py
Sharpe Ratio: {'sharperatio': 11.647332609673256}

没有绘图,因为SharpeRatio是计算结束时的单个值。

分析仪的取证分析

再说一遍,Analyzers不是 Lines 对象,而是为了将它们无缝集成到backtrader生态系统中,遵循几个 Lines 对象的内部 API 约定(实际上是它们的混合

Tips:SharpeRatio的代码已经发展到例如考虑到年化,这里的版本应该只是一个参考。

请查看分析器参考

另外还有一个SharpeRatio_A直接以年化形式提供价值,无论寻求的时间范围如何

SharpeRatio作为基础的代码(简化版)

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import operator

from backtrader.utils.py3 import map
from backtrader import Analyzer, TimeFrame
from backtrader.mathsupport import average, standarddev
from backtrader.analyzers import AnnualReturn


class SharpeRatio(Analyzer):
    params = (('timeframe', TimeFrame.Years), ('riskfreerate', 0.01),)

    def __init__(self):
        super(SharpeRatio, self).__init__()
        self.anret = AnnualReturn()

    def start(self):
        # Not needed ... but could be used
        pass

    def next(self):
        # Not needed ... but could be used
        pass

    def stop(self):
        retfree = [self.p.riskfreerate] * len(self.anret.rets)
        retavg = average(list(map(operator.sub, self.anret.rets, retfree)))
        retdev = standarddev(self.anret.rets)

        self.ratio = retavg / retdev

    def get_analysis(self):
        return dict(sharperatio=self.ratio)

代码可以分解为:

  • params宣言

    尽管未使用已声明的对象(仅作为示例),但分析器backtrader与支持参数 中的大多数其他对象一样

  • __init__方法

    就像Strategies在 中声明指标__init__一样,带有支持对象的分析器也是如此。

    在这种情况下:SharpeRatio使用年回报率计算。计算将是自动的,并可SharpeRatio用于自己的计算。

    Tips:实际实现SharpeRatio使用更通用和后来开发的TimeReturn分析器

  • next方法

    SharpeRatio不需要,但是每次调用父策略后都会调用这个方法next

  • start方法

    在回测开始之前调用。可用于额外的初始化任务。Sharperatio不需要它

  • stop方法

    在回测结束后立即调用。像SharpeRatio这样,它可以用来完成/进行计算

  • get_analysis方法(返回字典)

    外部调用者访问生成的分析

    返回:带有分析的字典。

 

参考

class backtrader.Analyzer()

分析器基类。所有分析器都是这个的子类

Analyzer 实例在策略框架中运行,并为该策略提供分析。

自动设置成员属性:

  • self.strategy(提供对策略的访问权限以及从中可访问的任何内容)
  • self.datas[x]允许访问系统中存在的数据源数组,也可以通过策略参考访问
  • self.data, 允许访问self.datas[0]
  • self.dataX->self.datas[X]
  • self.dataX_Y->self.datas[X].lines[Y]
  • self.dataX_name->self.datas[X].name
  • self.data_name->self.datas[0].name
  • self.data_Y->self.datas[0].lines[Y]

这不是Lines对象,但方法和操作遵循相同的设计

  • __init__在实例化和初始设置期间
  • start/stop表示操作的开始和结束
  • prenext / nextstart / nextnext相同方法的方法族
  • notify_trade / notify_order / notify_cashvalue / notify_fund 接收与策略的等效方法相同的通知

操作模式是开放的,没有模式是首选。因此,可以通过next调用生成分析,在操作结束时stop,甚至使用单个方法,如notify_trade

重要的是重写get_analysis以返回包含分析结果的类似dict的 对象(实际格式取决于实现)

start()

调用以指示操作的开始,让分析器有时间设置所需的东西

stop()

调用以指示操作结束,让分析器有时间关闭所需的东西

prenext()

为策略的每个 prenext 调用调用,直到达到策略的最短周期

分析器的默认行为是调用next

nextstart()

为策略的 nextstart 调用仅调用一次,此时已首次达到最短周期

next()

一旦达到策略的最小周期,每次下一次调用策略时调用

notify_cashvalue(cash, value)

在每个下一个周期之前收到现金/价值通知

notify_fund(cash, value, fundvalue, shares)

收到当前现金、价值、基金价值和基金份额

notify_order(order)

在每个下一个周期之前接收订单通知

notify_trade(trade)

在每个下一个周期之前接收交易通知

get_analysis()

返回带有分析结果的类字典对象

字典中分析结果的键和格式取决于实现。

甚至没有强制结果是一个类似字典的对象,只是约定

默认实现返回默认方法rets 创建的默认 OrderedDictcreate_analysis

create_analysis()

意味着被子类覆盖。有机会创建进行分析的结构。

默认行为是创建一个OrderedDict命名的rets

print(*args, **kwargs)

打印get_analysis通过标准 Writerfile对象返回的结果,默认将内容写入标准输出

pprint(*args, **kwargs)

get_analysis使用漂亮的打印 Python 模块 ( pprint )打印返回的结果

len()

支持len通过实际返回分析器操作的策略的当前长度来调用分析器