用户自定义佣金
原文:
www.backtrader.com/docu/user-defined-commissions/commission-schemes-subclassing/
重塑 CommInfo 对象到实际形式的最重要部分涉及:
- 保留原始的
CommissionInfo
类和行为 - 为轻松创建用户定义的佣金打开大门
- 将格式 xx% 设为新佣金方案的默认值而不是 0.xx(只是一种品味问题),保持行为可配置
注意
请参阅下面的 CommInfoBase
的文档字符串以获取参数参考
定义佣金方案
这涉及到 1 或 2 个步骤
- 子类化
CommInfoBase
简单地更改默认参数可能就足够了。backtrader
已经在模块backtrader.commissions
中的一些定义中这样做了。期货的常规行业标准是每个合同和每轮的固定金额。定义如下:
class CommInfo_Futures_Fixed(CommInfoBase): params = ( ('stocklike', False), ('commtype', CommInfoBase.COMM_FIXED), )`
- 对于股票和百分比佣金:
class CommInfo_Stocks_Perc(CommInfoBase): params = ( ('stocklike', True), ('commtype', CommInfoBase.COMM_PERC), )`
- 如上所述,这里对百分比的解释的默认是:xx%。如果希望使用旧的/其他行为 0.xx,可以轻松实现:
class CommInfo_Stocks_PercAbs(CommInfoBase): params = ( ('stocklike', True), ('commtype', CommInfoBase.COMM_PERC), ('percabs', True), )`
- 覆盖(如果需要的话)
_getcommission
方法
定义如下:
def _getcommission(self, size, price, pseudoexec): '''Calculates the commission of an operation at a given price pseudoexec: if True the operation has not yet been executed '''`
- 更多详细信息请参见下面的实际示例
如何应用到平台上
一旦 CommInfoBase
的子类就位,关键是使用 broker.addcommissioninfo
而不是通常的 broker.setcommission
。后者将在内部使用传统的 CommissionInfoObject
。
说起来容易做起来难:
... comminfo = CommInfo_Stocks_PercAbs(commission=0.005) # 0.5% cerebro.broker.addcommissioninfo(comminfo)
addcommissioninfo
方法定义如下:
def addcommissioninfo(self, comminfo, name=None): self.comminfo[name] = comminfo
设置 name
意味着 comminfo
对象仅适用于具有该名称的资产。默认值 None
意味着它适用于系统中的所有资产。
一个实际的例子
票号 #45 询问适用于期货的佣金方案,是百分比方式,并在佣金计算中使用合同的“虚拟”价值的佣金百分比。即:在佣金计算中包括未来合约的倍数。
这应该很容易:
import backtrader as bt class CommInfo_Fut_Perc_Mult(bt.CommInfoBase): params = ( ('stocklike', False), # Futures ('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission # ('percabs', False), # pass perc as xx% which is the default ) def _getcommission(self, size, price, pseudoexec): return size * price * self.p.commission * self.p.mult
将其加入系统:
comminfo = CommInfo_Fut_Perc_Mult( commission=0.1, # 0.1% mult=10, margin=2000 # Margin is needed for futures-like instruments ) cerebro.addcommissioninfo(comminfo) • 1 • 2 • 3 • 4 • 5 • 6 • 7
如果格式 0.xx 被偏好为默认值,只需将参数 percabs
设置为 True
:
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase): params = ( ('stocklike', False), # Futures ('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission ('percabs', True), # pass perc as 0.xx ) comminfo = CommInfo_Fut_Perc_Mult( commission=0.001, # 0.1% mult=10, margin=2000 # Margin is needed for futures-like instruments ) cerebro.addcommissioninfo(comminfo)
这一切都应该行得通。
解释 pseudoexec
让我们回顾一下 _getcommission
的定义:
def _getcommission(self, size, price, pseudoexec): '''Calculates the commission of an operation at a given price pseudoexec: if True the operation has not yet been executed '''
pseudoexec
参数的目的可能看起来很模糊,但它确实有其作用。
- 平台可能调用此方法来预先计算可用现金和一些其他任务
- 这意味着该方法可能(而且实际上会)使用相同的参数调用多次
pseudoexec
表示调用是否对应于订单的实际执行。虽然乍一看这可能似乎“不相关”,但如果考虑以下情景,它就很重要:
- 一家经纪人在合同数量超过 5000 单位后会给期货来回佣金打 5 折
在这种情况下,如果没有pseudoexec
,对该方法的多次非执行调用将迅速触发折扣已生效的假设。
将情景付诸实践:
import backtrader as bt class CommInfo_Fut_Discount(bt.CommInfoBase): params = ( ('stocklike', False), # Futures ('commtype', bt.CommInfoBase.COMM_FIXED), # Apply Commission # Custom params for the discount ('discount_volume', 5000), # minimum contracts to achieve discount ('discount_perc', 50.0), # 50.0% discount ) negotiated_volume = 0 # attribute to keep track of the actual volume def _getcommission(self, size, price, pseudoexec): if self.negotiated_volume > self.p.discount_volume: actual_discount = self.p.discount_perc / 100.0 else: actual_discount = 0.0 commission = self.p.commission * (1.0 - actual_discount) commvalue = size * price * commission if not pseudoexec: # keep track of actual real executed size for future discounts self.negotiated_volume += size return commvalue
现在,pseudoexec
的目的和存在应该清楚了。
CommInfoBase 文档字符串和参数
参见佣金:股票 vs 期货以获取CommInfoBase
的参考。
佣金:信用
在某些情况下,真实经纪人的现金金额可能会减少,因为资产操作包括利率。例如:
- 股票空头交易
- ETF 即多头又空头
收费直接影响经纪账户的现金余额。但它仍然可以被视为佣金方案的一部分。因此,它已经在backtrader中建模。
CommInfoBase
类(以及与之相关的CommissionInfo
主接口对象)已经扩展了:
- 两个新参数,允许设置利率,并确定是否仅应用于空头或同时适用于多头和空头
参数
interest
(默认:0.0
)
如果这个值不为零,则这是持有空头头寸的年利息。这主要是针对股票空头交易的
应用的默认公式:days * price * size * (interest / 365)
必须以绝对值指定:0.05 -> 5%
注意
可以通过重写方法get_credit_interest
来改变行为interest_long
(默认:False
)
一些产品(如 ETF)在空头和多头头寸上都会被收取利息。如果这是True
,并且interest
不为零,利息将在两个方向上都收取
公式
默认实现将使用以下公式:
days * abs(size) * price * (interest / 365)
其中:
days
:自仓位开启或上次计算信用利息以来经过的天数
重写公式
为了改变CommissionInfo
的公式子类化是必需的。需要被重写的方法是:
def _get_credit_interest(self, size, price, days, dt0, dt1): ''' This method returns the cost in terms of credit interest charged by the broker. In the case of ``size > 0`` this method will only be called if the parameter to the class ``interest_long`` is ``True`` The formulat for the calculation of the credit interest rate is: The formula: ``days * price * abs(size) * (interest / 365)`` Params: - ``data``: data feed for which interest is charged - ``size``: current position size. > 0 for long positions and < 0 for short positions (this parameter will not be ``0``) - ``price``: current position price - ``days``: number of days elapsed since last credit calculation (this is (dt0 - dt1).days) - ``dt0``: (datetime.datetime) current datetime - ``dt1``: (datetime.datetime) datetime of previous calculation ``dt0`` and ``dt1`` are not used in the default implementation and are provided as extra input for overridden methods '''
可能是经纪人在计算利率时不考虑周末或银行假日。在这种情况下,这个子类会奏效
import backtrader as bt class MyCommissionInfo(bt.CommInfo): def _get_credit_interest(self, size, price, days, dt0, dt1): return 1.0 * abs(size) * price * (self.p.interest / 365.0) • 1 • 2 • 3 • 4 • 5 • 6
在这种情况下,在公式中:
days
已被1.0
替代
因为如果周末/银行假日不计入,下一次计算将始终在上次计算后的1
个交易日后发生
分析器
分析器
无论是回测还是交易,能够分析交易系统的性能对于了解是否仅仅获得了利润以及是否存在过多风险或者与参考资产(或无风险资产)相比是否真的值得努力至关重要。
这就是 Analyzer
对象族的作用:提供已发生情况或实际正在发生情况的分析。
分析器的性质
接口模仿了 Lines 对象的接口,例如包含一个 next
方法,但有一个主要区别:
Analyzers
不保存线条。
这意味着它们在内存方面并不昂贵,因为即使在分析了成千上万个价格条之后,它们仍然可能只保存单个结果在内存中。
生态系统中的位置
Analyzer
对象(像 strategies、observers 和 datas 一样)通过 cerebro
实例添加到系统中:
addanalyzer(ancls, *args, **kwargs)
但是当在 cerebro.run
过程中进行操作时,对于系统中每个 策略,将会发生以下情况
ancls
将在cerebro.run
过程中以*args
和**kwargs
实例化。ancls
实例将被附加到策略上。
这意味着:
- 如果回测运行包含例如 3 个策略,那么将会创建 3 个
ancls
实例,并且每个实例都将附加到不同的策略上。
底线是:分析器分析单个策略的性能,而不是整个系统的性能
额外的位置
一些 Analyzer
对象实际上可能使用其他分析器来完成其工作。例如:SharpeRatio
使用 TimeReturn
的输出进行计算。
这些 子分析器 或 从属分析器 也将被插入到创建它们的同一策略中。但对用户来说完全看不见。
属性
为了执行预期的工作,Analyzer
对象提供了一些默认属性,这些属性被自动传递和设置在实例中以便使用:
self.strategy
:策略子类的引用,分析器对象正在操作其中。任何 strategy 可访问的内容也可以被 analyzer 访问。self.datas[x]
:策略中存在的数据源数组。尽管这可以通过 strategy 引用访问,但这个快捷方式使工作更加方便。self.data
:为了额外的便利而设置的快捷方式。self.dataX
:快捷方式到不同的self.datas[x]
还提供了一些其他别名,尽管它们可能是多余的:
* `self.dataX_Y` where X is a reference to `self.datas[X]` and `Y` refers to the line, finally pointing to: `self.datas[X].lines[Y]
如果线条有名称,还可以获得以下内容:
* `self.dataX_Name` which resolves to `self.datas[X].Name` returning the line by name rather than by index
对于第一个数据,最后两个快捷方式也可用,无需初始的 X
数字引用。例如:
* `self.data_2` refers to `self.datas[0].lines[2]
和
* `self.data_close` refers to `self.datas[0].close
返回分析结果
Analyzer 基类创建一个 self.rets
(类型为 collections.OrderedDict
)成员属性来返回分析结果。 这是在方法 create_analysis
中完成的,如果创建自定义分析器,子类可以覆盖此方法。
操作模式
虽然 Analyzer
对象不是 Lines 对象,因此不会迭代线条,但它们被设计为遵循相同的操作模式。
- 在系统启动之前实例化(因此调用
__init__
) - 使用
start
标志操作的开始 - 将调用
prenext
/nextstart
/next
,遵循 策略 正在运行的最短周期的计算结果。prenext
和nextstart
的默认行为是调用 next,因为分析器可能从系统启动的第一刻就开始分析。
在 Lines 对象中调用len(self)
来检查实际的条数可能是习惯的。 这在Analyzers
中也适用,通过为self.strategy
返回值。 - 订单和交易将像对策略一样通过
notify_order
和notify_trade
进行通知 - 现金和价值也将像对策略一样通过
notify_cashvalue
方法进行通知 - 现金、价值、基金价值和基金份额也将像对策略一样通过
notify_fund
方法进行通知 stop
将被调用以信号操作结束
一旦常规操作周期完成,分析器 就会提供用于提取/输出信息的附加方法
get_analysis
:理想情况下(不强制要求)返回一个类似于dict
的对象,其中包含分析结果。print
使用标准的backtrader.WriterFile
(除非被覆盖)来从get_analysis
写入分析结果。pprint
(漂亮打印)使用 Pythonpprint
模块打印get_analysis
的结果。
最后:
get_analysis
创建一个成员属性self.ret
(类型为collections.OrderedDict
),分析器将分析结果写入其中。
Analyzer 的子类可以重写此方法以更改此行为
分析器模式
在 backtrader
平台上开发 Analyzer 对象揭示了两种不同的用法模式用于生成分析:
- 通过在
notify_xxx
和next
方法中收集信息并在next
中生成分析的当前信息进行执行
例如,TradeAnalyzer
只使用notify_trade
方法生成统计信息。 - 在
stop
方法期间一次性生成分析结果,收集(或不收集)如上信息SQN
(系统质量数)在notify_trade
期间收集交易信息,但在stop
方法中生成统计信息
一个快速的示例
尽可能简单:
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
是在计算结束时的单个值。
分析器的法证分析
让我们重申一下,分析器
不是线对象,但为了无缝地将它们整合到backtrader
生态系统中,遵循了几个线对象的内部 API 约定(实际上是一个混合)
注意
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__
方法
就像策略在__init__
中声明指标一样,分析器也是使用支持对象的。
在这种情况下:使用年度收益计算夏普比率
。计算将自动进行,并且将对夏普比率
进行自己的计算。
注意夏普比率
的实际实现使用了更通用和后来开发的TimeReturn
分析器next
方法夏普比率
不需要它,但是此方法将在每次调用父策略的next
后调用start
方法
在回测开始之前调用。可用于额外的初始化任务。夏普比率
不需要它stop
方法
在回测结束后立即调用。像SharpeRatio
一样,它可用于完成/进行计算get_analysis
方法(返回一个字典)
外部调用者对生成的分析的访问
返回:带有分析结果的字典。
参考资料
类 backtrader.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]
这不是一个线对象,但是方法和操作遵循相同的设计
- 在实例化和初始设置期间的
__init__
start
/stop
用于信号开始和结束操作- 遵循与策略中相同方法调用后的
prenext
/nextstart
/next
方法系列 notify_trade
/notify_order
/notify_cashvalue
/notify_fund
,它们接收与策略的等效方法相同的通知
操作模式是开放的,没有首选模式。因此,分析可以通过next
调用,在stop
期间的操作结束时甚至通过单个方法notify_trade
生成。
重要的是要重写get_analysis
以返回包含分析结果的类似于字典的对象(实际格式取决于实现)。
start()
表示开始操作,使分析器有时间设置所需的东西。
stop()
表示结束操作,使分析器有时间关闭所需的东西。
prenext()
对策略的每次 prenext 调用都会调用,直到策略的最小周期已达到。
分析器的默认行为是调用next
。
nextstart()
为下一次策略的 nextstart 调用精确调用一次,当首次达到最小周期时。
next()
对策略的每次 next 调用进行调用,一旦策略的最小周期已达到。
notify_cashvalue(cash, value)
在每次下一周期之前接收现金/价值通知。
notify_fund(cash, value, fundvalue, shares)
在每次下一周期之前接收当前现金、价值、基金价值和基金份额。
notify_order(order)
在每次下一周期之前接收订单通知。
notify_trade(trade)
在每次下一周期之前接收交易通知。
get_analysis()
返回一个类似于字典的对象,其中包含分析结果。
字典中分析结果的键和格式取决于具体实现。
甚至不强制结果是类似于字典对象,只是约定。
默认实现返回由默认的create_analysis
方法创建的默认OrderedDict``rets
。
create_analysis()
应由子类重写。给予创建保存分析的结构的机会。
默认行为是创建一个名为rets
的OrderedDict
。
print(*args, **kwargs)
通过标准的Writerfile
对象打印get_analysis
返回的结果,默认情况下将其写入标准输出。
pprint(*args, **kwargs)
使用 Python 的漂亮打印模块(pprint)打印get_analysis
返回的结果。
len()
通过实际返回分析器所操作的策略的当前长度来支持对分析器进行len
调用。
PyFolio 概述
注意
截至至少 2017-07-25,pyfolio
的 API 已更改,create_full_tear_sheet
不再具有gross_lev
作为命名参数。
因此,集成的示例无法运行
引用主要pyfolio
页面上的内容quantopian.github.io/pyfolio/
:
pyfolio is a Python library for performance and risk analysis of financial portfolios developed by Quantopian Inc. It works well with the Zipline open source backtesting library
现在它也与backtrader很好地配合。需要什么:
- 显然是
pyfolio
- 以及它的依赖项(例如
pandas
,seaborn
…)
注意
在与版本0.5.1
集成期间,需要更新依赖项的最新软件包,例如从先前安装的0.7.0-dev
到0.7.1
的seaborn
,显然是由于缺少swarmplot
方法
用法
- 将
PyFolio
分析器添加到cerebro
混合中:
cerebro.addanalyzer(bt.analyzers.PyFolio)`
- 运行并检索第 1 个策略:
strats = cerebro.run() strat0 = strats[0]`
- 使用您指定的名称或默认名称
pyfolio
检索分析器。例如:
pyfolio = strats.analyzers.getbyname('pyfolio')`
- 使用分析器方法
get_pf_items
检索后续需要用于pyfolio
的 4 个组件:
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()`
- !!! 注意
The integration was done looking at test samples available with `pyfolio` and the same headers (or absence of) has been replicated`
- 使用
pyfolio
(这已经超出了backtrader生态系统)
一些与backtrader无直接关系的使用说明
pyfolio
自动绘图功能在Jupyter Notebook之外也可以工作,但在内部效果最佳。pyfolio
数据表的输出似乎在Jupyter Notebook之外几乎无法工作。它在Notebook内部工作
如果希望使用pyfolio
,结论很简单:在 Jupyter Notebook 内部工作
示例代码
代码如下所示:
... cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') ... results = cerebro.run() strat = results[0] pyfoliozer = strat.analyzers.getbyname('pyfolio') returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items() ... ... # pyfolio showtime import pyfolio as pf pf.create_full_tear_sheet( returns, positions=positions, transactions=transactions, gross_lev=gross_lev, live_start_date='2005-05-01', # This date is sample specific round_trips=True) # At this point tables and chart will show up
参考
查看PyFolio
分析器的参考资料以及它内部使用的分析器
Pyfolio 集成
原文:
www.backtrader.com/docu/analyzers/pyfolio-integration/pyfolio-integration/
portfolio 工具的集成,即 pyfolio
,是在 Ticket #108 中提出的。
对教程的第一次尝试被认为很难,考虑到 zipline
和 pyfolio
之间的紧密集成,但是 pyfolio
提供的用于其他用途的样本测试数据实际上非常有用,可以解码背后发生的事情,从而实现了集成的奇迹。
大部分组件已经在 backtrader 中就位:
- 分析器基础设施
- 子分析器
- 一个 TimeReturn 分析器
只需要一个主PyFolio
分析器和 3 个简单的子分析器。再加上一个依赖于pyfolio
的依赖项中的方法,即pandas
。
最具挑战性的部分…“确保所有依赖项正确”
pandas
的更新numpy
的更新scikit-learn
的更新seaborn
的更新
在类 Unix 环境下使用 C 编译器,一切都取决于时间。在 Windows 下,即使安装了特定的 Microsoft 编译器(在这种情况下是用于 Python 2.7 的链),事情也会失败。但是一个众所周知的拥有最新包的 Windows 站点却有所帮助。如果你需要的话,请访问它:
如果没有经过测试,集成就不完整,这就是为什么通常的样本总是存在。
没有 PyFolio
样本使用random.randint
来决定何时买入/卖出,所以这只是一个检查是否工作的简单检查:
$ ./pyfoliotest.py --printout --no-pyfolio --plot • 1
输出:
Len,Datetime,Open,High,Low,Close,Volume,OpenInterest 0001,2005-01-03T23:59:59,38.36,38.90,37.65,38.18,25482800.00,0.00 BUY 1000 @%23.58 0002,2005-01-04T23:59:59,38.45,38.54,36.46,36.58,26625300.00,0.00 BUY 1000 @%36.58 SELL 500 @%22.47 0003,2005-01-05T23:59:59,36.69,36.98,36.06,36.13,18469100.00,0.00 ... SELL 500 @%37.51 0502,2006-12-28T23:59:59,25.62,25.72,25.30,25.36,11908400.00,0.00 0503,2006-12-29T23:59:59,25.42,25.82,25.33,25.54,16297800.00,0.00 SELL 250 @%17.14 SELL 250 @%37.01
有 3 个数据,几个买入和卖出操作被随机选择并散布在测试运行的默认 2 年生命周期内
BackTrader 中文文档(十)(2)https://developer.aliyun.com/article/1505299