扩展佣金#
佣金和相关功能由一个名为 CommissionInfo
的单个类来管理,它通常通过调用 broker.setcommission
来实例化。
这个概念仅适用于带有保证金和每份合约固定佣金的期货,以及基于价格/数量百分比的股票佣金。虽然它已经达到了它的目的,但这并不是最灵活的方案。
在GitHub上提出了一个增强请求 #29,导致了一些重构, 以便达到以下目标:
保持
CommissionInfo
和broker.setcommission
与原始行为兼容清理代码
使佣金方案灵活,以支持增强请求和进一步的可能性
在进入示例之前的实际工作
class CommInfoBase(with_metaclass(MetaParams)):
COMM_PERC, COMM_FIXED = range(2)针对参数已经引入了一个名为 ``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] 佣金方案
- 可选参数:
- -h, --help
显示帮助信息并退出
- --data DATA, -d DATA
要添加到系统中的数据(默认值:../../datas/2006-day-001.txt)
- --fromdate FROMDATE, -f FROMDATE
开始日期,格式为YYYY-MM-DD(默认值:2006-01-01)
- --todate TODATE, -t TODATE
结束日期,格式为YYYY-MM-DD(默认值:2006-12-31)
- --stake STAKE
每次操作要投入的资金(默认值:1)
- --period PERIOD
对简单移动平均线应用的期限(默认值:30)
- --cash CASH
初始资金(默认值:10000.0)
- --comm COMM
每笔交易的佣金因子,可以是百分比或每份资金的绝对值(默认值:2.0)
- --mult MULT
操作计算的乘数(默认值:10)
- --margin MARGIN
期货类操作的保证金(默认值:2000.0)
- –commtype {none,perc,fixed}
佣金设定 - 选择none以保留旧的CommissionInfo行为(默认值:none)
- --stocklike
操作是针对类似股票的资产还是类似期货的资产(默认值:False)
- --percrel
如果佣金以相对xx%的形式而不是绝对值0.xx的形式给出则为True(默认值:False)
- --plot, -p
绘制读取到的数据(默认值:False)
- --numfigs NUMFIGS, -n NUMFIGS
使用numfigs个图表进行绘制(默认值:1)
让我们运行一些程序来重新创建原始佣金方案帖子的原始行为。
期货佣金(固定佣金且含保证金)#
以下是执行和图表:
$ ./commission-schemes.py –comm 2.0 –margin 2000.0 –mult 10 –plot
下面是显示固定佣金为2.0货币单位(默认资金为1)的输出:
2006-03-09, 购买创建, 3757.59 2006-03-10, 购买执行, 价格:3754.13, 成本:2000.00, 佣金 2.00 2006-04-11, 卖出创建, 3788.81 2006-04-12, 卖出执行, 价格:3786.93, 成本:2000.00, 佣金 2.00 2006-04-12, 交易利润, 总收入 328.00, 净收入 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,购买创建,3757.59
2006-03-10,购买执行,价格:3754.13,成本:3754.13,佣金 18.77
2006-04-11,出售创建,3788.81
2006-04-12,出售执行,价格:3786.93,成本:3754.13,佣金 18.93
2006-04-12,交易利润,毛利 32.80,净利 -4.91
...
期货的佣金(百分比和保证金)#
使用新参数,基于百分比的期货方案:: $ ./commission-schemes.py –commtype perc –percrel –comm 0.5 –margin 2000 –mult 10 –plot
毫不奇怪,通过改变佣金…最终结果发生了改变
输出结果显示现在佣金是可变的:
2006-03-09, 创建买入, 3757.59
2006-03-10, 执行买入, 价格: 3754.13, 成本: 2000.00, 佣金 18.77
2006-04-11, 创建卖出, 3788.81
2006-04-12, 执行卖出, 价格: 3786.93, 成本: 2000.00, 佣金 18.93
2006-04-12, 交易利润, 总额 328.00, 净额 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()