扩展佣金#

佣金和相关功能由一个名为 CommissionInfo 的单个类来管理,它通常通过调用 broker.setcommission 来实例化。

这个概念仅适用于带有保证金和每份合约固定佣金的期货,以及基于价格/数量百分比的股票佣金。虽然它已经达到了它的目的,但这并不是最灵活的方案。

在GitHub上提出了一个增强请求 #29,导致了一些重构, 以便达到以下目标:

  • 保持 CommissionInfobroker.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()