BackTrader 中文文档(二十五)(2)https://developer.aliyun.com/article/1505458
用户定义的佣金
佣金方案实现不久前进行了重新设计。最重要的部分是:
- 保留原始的 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), )`
- 如上所述,百分比的默认解释(作为参数
commission
传递)为: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
的默认值意味着它适用于系统中的所有资产。
一个实际的示例
Ticket #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.broker.addcommission(comminfo)
如果默认值偏好格式为 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.broker.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 单位后,将提供期货往返佣金 50%的折扣。
在这种情况下,如果没有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 文档字符串和参数
这里是:
class CommInfoBase(with_metaclass(MetaParams)): '''Base Class for the Commission Schemes. Params: - commission (def: 0.0): base commission value in percentage or monetary units - mult (def 1.0): multiplier applied to the asset for value/profit - margin (def: None): amount of monetary units needed to open/hold an operation. It only applies if the final ``_stocklike`` attribute in the class is set to False - commtype (def: None): Supported values are CommInfoBase.COMM_PERC (commission to be understood as %) and CommInfoBase.COMM_FIXED (commission to be understood as monetary units) The default value of ``None`` is a supported value to retain compatibility with the legacy ``CommissionInfo`` object. If ``commtype`` is set to None, then the following applies: - margin is None: Internal _commtype is set to COMM_PERC and _stocklike is set to True (Operating %-wise with Stocks) - margin is not None: _commtype set to COMM_FIXED and _stocklike set to False (Operating with fixed rount-trip commission with Futures) If this param is set to something else than None, then it will be passed to the internal ``_commtype`` attribute and the same will be done with the param ``stocklike`` and the internal attribute ``_stocklike`` - stocklike (def: False): Indicates if the instrument is Stock-like or Futures-like (see the ``commtype`` discussion above) - percabs (def: False): when ``commtype`` is set to COMM_PERC, whether the parameter ``commission`` has to be understood as XX% or 0.XX If this param is True: 0.XX If this param is False: XX% Attributes: - _stocklike: Final value to use for Stock-like/Futures-like behavior - _commtype: Final value to use for PERC vs FIXED commissions This two are used internally instead of the declared params to enable the compatibility check described above for the legacy ``CommissionInfo`` object ''' COMM_PERC, COMM_FIXED = range(2) params = ( ('commission', 0.0), ('mult', 1.0), ('margin', None), ('commtype', None), ('stocklike', False), ('percabs', False), )
扩展佣金
原文:
www.backtrader.com/blog/posts/2015-11-05-commission-schemes-extended/commission-schemes-extended/
佣金和相关功能由一个单独的类CommissionInfo
管理,该类主要通过调用broker.setcommission
来实例化。
有一些帖子讨论了这种行为。
- 佣金:股票 vs 期货
- 改进佣金:股票 vs 期货
这个概念仅限于具有保证金的期货和具有基于价格/大小百分比的佣金的股票。 即使它已经达到了它的目的,它也不是最灵活的方案。
我自己的实现中只有一件事我不喜欢,那就是CommissionInfo
将百分比值以绝对值(0.xx)而不是相对值(xx%)的方式传递
GitHub 的增强请求#29导致一些重新设计,以:
- 保持
CommissionInfo
和broker.setcommission
与原始行为兼容 - 对代码进行清理
- 使佣金方案灵活以支持增强请求和进一步可能性
进入示例之前的实际工作:
class CommInfoBase(with_metaclass(MetaParams)): COMM_PERC, COMM_FIXED = range(2) params = ( ('commission', 0.0), ('mult', 1.0), ('margin', None), ('commtype', None), ('stocklike', False), ('percabs', False), )
引入了一个CommissionInfo
的基类,将新参数添加到混合中:
commtype
(默认:None)这是兼容性的关键。 如果值为None
,则CommissionInfo
对象和broker.setcommission
的行为将与以前相同。 即:
- 如果设置了
margin
,则佣金方案适用于具有固定合约佣金的期货 - 如果未设置
margin
,则佣金方案适用于具有基于百分比的股票方法
- 如果值为
COMM_PERC
或COMM_FIXED
(或派生类中的任何其他值),则这显然决定了佣金是固定的还是基于百分比的 stocklike
(默认:False)
如上所述,旧的CommissionInfo
对象中的实际行为由参数margin
决定
如上所述,如果将commtype
设置为除None
之外的其他值,则此值指示资产是否为类似期货的资产(将使用保证金,并执行基于条的现金调整)或者这是类似股票的资产percabs
(默认:False)
如果为False
,则百分比必须以相对术语传递(xx%)
如果为True
,则百分比必须以绝对值(0.xx)传递CommissionInfo
是从CommInfoBase
派生的,将此参数的默认值更改为True
以保持兼容的行为
所有这些参数也可以在broker.setcommission
中使用,现在看起来像这样:
def setcommission(self, commission=0.0, margin=None, mult=1.0, commtype=None, percabs=True, stocklike=False, name=None):
请注意以下内容:
percabs
设置为True
,以保持与旧调用的CommissionInfo
对象上述行为的兼容
重新设计了用于测试commissions-schemes
的旧样本,以支持命令行参数和新行为。 使用帮助:
$ ./commission-schemes.py --help usage: commission-schemes.py [-h] [--data DATA] [--fromdate FROMDATE] [--todate TODATE] [--stake STAKE] [--period PERIOD] [--cash CASH] [--comm COMM] [--mult MULT] [--margin MARGIN] [--commtype {none,perc,fixed}] [--stocklike] [--percrel] [--plot] [--numfigs NUMFIGS] Commission schemes optional arguments: -h, --help show this help message and exit --data DATA, -d DATA data to add to the system (default: ../../datas/2006-day-001.txt) --fromdate FROMDATE, -f FROMDATE Starting date in YYYY-MM-DD format (default: 2006-01-01) --todate TODATE, -t TODATE Starting date in YYYY-MM-DD format (default: 2006-12-31) --stake STAKE Stake to apply in each operation (default: 1) --period PERIOD Period to apply to the Simple Moving Average (default: 30) --cash CASH Starting Cash (default: 10000.0) --comm COMM Commission factor for operation, either apercentage or a per stake unit absolute value (default: 2.0) --mult MULT Multiplier for operations calculation (default: 10) --margin MARGIN Margin for futures-like operations (default: 2000.0) --commtype {none,perc,fixed} Commission - choose none for the old CommissionInfo behavior (default: none) --stocklike If the operation is for stock-like assets orfuture- like assets (default: False) --percrel If perc is expressed in relative xx{'const': True, 'help': u'If perc is expressed in relative xx% ratherthan absolute value 0.xx', 'option_strings': [u' --percrel'], 'dest': u'percrel', 'required': False, 'nargs': 0, 'choices': None, 'default': False, 'prog': 'commission-schemes.py', 'container': <argparse._ArgumentGroup object at 0x0000000007EC9828>, 'type': None, 'metavar': None}atherthan absolute value 0.xx (default: False) --plot, -p Plot the read data (default: False) --numfigs NUMFIGS, -n NUMFIGS Plot using numfigs figures (default: 1)
让我们进行一些运行,以重新创建原始佣金方案帖子的原始行为。
期货的佣金(固定和带保证金)
执行和图表:
$ ./commission-schemes.py --comm 2.0 --margin 2000.0 --mult 10 --plot
并且输出显示固定佣金为 2.0 货币单位(默认押注为 1):
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00 2006-04-12, TRADE PROFIT, GROSS 328.00, NET 324.00 ...
股票的佣金(百分比和无保证金)
执行和图表:
$ ./commission-schemes.py --comm 0.005 --margin 0 --mult 1 --plot
为了提高可读性,可以使用相对%值:
$ ./commission-schemes.py --percrel --comm 0.5 --margin 0 --mult 1 --plot
现在0.5
直接表示0.5%
输出在两种情况下都是:
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3754.13, Comm 18.93 2006-04-12, TRADE PROFIT, GROSS 32.80, NET -4.91 ...
期货的佣金(百分比和带保证金)
使用新参数,基于百分比的期货:
$ ./commission-schemes.py --commtype perc --percrel --comm 0.5 --margin 2000 --mult 10 --plot
改变佣金…最终结果发生变化并不奇怪
输出显示佣金现在是可变的:
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 18.77 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 18.93 2006-04-12, TRADE PROFIT, GROSS 328.00, NET 290.29 ...
在上一次运行中设置了 2.0 货币单位(默认押注为 1)
另一篇文章将详细介绍新的类别和自制佣金方案的实施。
示例代码
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind class SMACrossOver(bt.Strategy): params = ( ('stake', 1), ('period', 30), ) def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enougth cash if order.status in [order.Completed, order.Canceled, order.Margin]: if order.isbuy(): self.log( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) else: # Sell self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) def notify_trade(self, trade): if trade.isclosed: self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) def __init__(self): sma = btind.SMA(self.data, period=self.p.period) # > 0 crossing up / < 0 crossing down self.buysell_sig = btind.CrossOver(self.data, sma) def next(self): if self.buysell_sig > 0: self.log('BUY CREATE, %.2f' % self.data.close[0]) self.buy(size=self.p.stake) # keep order ref to avoid 2nd orders elif self.position and self.buysell_sig < 0: self.log('SELL CREATE, %.2f' % self.data.close[0]) self.sell(size=self.p.stake) def runstrategy(): args = parse_args() # Create a cerebro cerebro = bt.Cerebro() # Get the dates from the args fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') # Create the 1st data data = btfeeds.BacktraderCSVData( dataname=args.data, fromdate=fromdate, todate=todate) # Add the 1st data to cerebro cerebro.adddata(data) # Add a strategy cerebro.addstrategy(SMACrossOver, period=args.period, stake=args.stake) # Add the commission - only stocks like a for each operation cerebro.broker.setcash(args.cash) commtypes = dict( none=None, perc=bt.CommInfoBase.COMM_PERC, fixed=bt.CommInfoBase.COMM_FIXED) # Add the commission - only stocks like a for each operation cerebro.broker.setcommission(commission=args.comm, mult=args.mult, margin=args.margin, percabs=not args.percrel, commtype=commtypes[args.commtype], stocklike=args.stocklike) # And run it cerebro.run() # Plot if requested if args.plot: cerebro.plot(numfigs=args.numfigs, volume=False) def parse_args(): parser = argparse.ArgumentParser( description='Commission schemes', formatter_class=argparse.ArgumentDefaultsHelpFormatter,) parser.add_argument('--data', '-d', default='../../datas/2006-day-001.txt', help='data to add to the system') parser.add_argument('--fromdate', '-f', default='2006-01-01', help='Starting date in YYYY-MM-DD format') parser.add_argument('--todate', '-t', default='2006-12-31', help='Starting date in YYYY-MM-DD format') parser.add_argument('--stake', default=1, type=int, help='Stake to apply in each operation') parser.add_argument('--period', default=30, type=int, help='Period to apply to the Simple Moving Average') parser.add_argument('--cash', default=10000.0, type=float, help='Starting Cash') parser.add_argument('--comm', default=2.0, type=float, help=('Commission factor for operation, either a' 'percentage or a per stake unit absolute value')) parser.add_argument('--mult', default=10, type=int, help='Multiplier for operations calculation') parser.add_argument('--margin', default=2000.0, type=float, help='Margin for futures-like operations') parser.add_argument('--commtype', required=False, default='none', choices=['none', 'perc', 'fixed'], help=('Commission - choose none for the old' ' CommissionInfo behavior')) parser.add_argument('--stocklike', required=False, action='store_true', help=('If the operation is for stock-like assets or' 'future-like assets')) parser.add_argument('--percrel', required=False, action='store_true', help=('If perc is expressed in relative xx% rather' 'than absolute value 0.xx')) parser.add_argument('--plot', '-p', action='store_true', help='Plot the read data') parser.add_argument('--numfigs', '-n', default=1, help='Plot using numfigs figures') return parser.parse_args() if __name__ == '__main__': runstrategy()
BackTrader 中文文档(二十五)(4)https://developer.aliyun.com/article/1505461