佣金#
中立性#
在开始之前,让我们记住 backtrader 尝试保持数据代表内容的中立性。不同的佣金方案可以应用于相同的数据集。让我们看看如何做到这一点。
经纪商快捷方式#
这使得最终用户远离 CommissionInfo 对象,因为可以通过一次函数调用创建/设置佣金方案。在常规的 cerebro 创建/设置过程中,只需在经纪商成员属性上添加一个调用 setcommission 的调用即可。
以下调用设置了使用 Interactive Brokers 操作 Eurostoxx50 期货的常规佣金方案:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)由于大多数用户通常只测试单一工具,这已经足够。
如果你为你的数据馈送命名,因为在图表上同时考虑了多个工具,这个调用可以稍微扩展如下:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0, name='Eurostoxxx50')在这种情况下,此即时佣金方案将仅应用于名称匹配 Eurostoxx50 的工具。
setcommission 参数的含义#
commission(默认值:0.0)每次操作的货币单位,绝对值或百分比。在上述示例中,每份合约的买入和卖出费用分别为 2.0 欧元。 重要的是何时使用绝对值或百分比值。
如果 margin 为 False(例如,它是 False、0 或 None),则将视为佣金表示为价格乘以操作数量的百分比。
如果 margin 是其他值,则视为操作发生在类似期货的工具上,佣金是每张合约的固定价格。
margin(默认值:None)操作期货类工具时需要的保证金。如上所述:
如果没有设置 margin,则佣金将被视为百分比,并应用于买卖操作的价格 * 数量。
如果设置了 margin,则佣金将被视为固定值,并乘以买卖操作的数量。
mult(默认值:1.0)对于期货类工具,这决定了应用于损益计算的乘数。这使得期货同时具有吸引力和风险。
name(默认值:None)将佣金方案应用于名称匹配的工具。可以在创建数据馈送时设置此值。如果未设置,则方案将适用于系统中的任何数据。
两个示例:股票 vs 期货#
期货的示例:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)股票的示例:
cerebro.broker.setcommission(commission=0.005)  # 交易金额的 0.5%注意
第二种语法不设置 margin 和 mult,backtrader 试图通过将佣金视为百分比来进行智能处理。
要完全指定佣金方案,需要创建 CommissionInfo 的子类。
创建永久性佣金方案#
可以通过直接使用 CommissionInfo 类创建更永久的佣金方案。用户可以选择在某处定义:
import backtrader as bt
commEurostoxx50 = bt.CommissionInfo(commission=2.0, margin=2000.0, mult=10.0)然后在另一个 Python 模块中应用:
from mycomm import commEurostoxx50
...
cerebro.broker.addcommissioninfo(commEuroStoxx50, name='Eurostoxxx50')CommissionInfo 是一个对象,使用与 backtrader 环境中的其他对象类似的 params 声明。因此上述内容也可以表示为:
import backtrader as bt
class CommEurostoxx50(bt.CommissionInfo):
    params = dict(commission=2.0, margin=2000.0, mult=10.0)然后:
from mycomm import CommEurostoxx50
...
cerebro.broker.addcommissioninfo(CommEuroStoxx50(), name='Eurostoxxx50')使用 SMA 交叉的实际比较#
使用简单移动平均交叉作为进出信号,使用相同的数据集来测试期货类佣金方案和股票类佣金方案。
注意
期货头寸不仅可以给出进出行为,还可以在每次机会时进行反转行为。但这个示例是关于比较佣金方案的。
代码(见底部完整策略)相同,可以在定义策略之前选择方案。
futures_like = True
if futures_like:
    commission, margin, mult = 2.0, 2000.0, 10.0
else:
    commission, margin, mult = 0.005, None, 1只需将 futures_like 设置为 false 即可使用股票类方案运行。
一些日志代码已被添加以评估不同佣金方案的影响。让我们集中在前两个操作。
对于期货:
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, OPERATION PROFIT, GROSS 328.00, NET 324.00
2006-04-20, BUY CREATE, 3860.00
2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 2000.00, Comm 2.00
2006-04-28, SELL CREATE, 3839.90
2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 2000.00, Comm 2.00
2006-05-02, OPERATION PROFIT, GROSS -243.30, NET -247.30对于股票:
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: 3786.93, Comm 18.93
2006-04-12, OPERATION PROFIT, GROSS 32.80, NET -4.91
2006-04-20, BUY CREATE, 3860.00
2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 3863.57, Comm 19.32
2006-04-28, SELL CREATE, 3839.90
2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 3839.24, Comm 19.20
2006-05-02, OPERATION PROFIT, GROSS -24.33, NET -62.84第一个操作的价格如下:
- 买入(执行):3754.13
 - 卖出(执行):3786.93
 
期货的损益(含佣金):324.0
股票的损益(含佣金):-4.91
佣金完全吞噬了股票操作的利润,而期货操作的利润只受到轻微影响。
第二个操作:
- 买入(执行):3863.57
 - 卖出(执行):3389.24
 
期货的损益(含佣金):-247.30
股票的损益(含佣金):-62.84
对于此负操作,期货的影响明显更大。
但:
期货累计净利润和损失:324.00 + (-247.30) = 76.70
股票累计净利润和损失:(-4.91) + (-62.84) = -67.75
在下图中可以看到累计效果,还可以看到在全年结束时,期货产生了更大的利润,但也遭受了更大的回撤(更深的水下)
但重要的是:无论是期货还是股票……都可以进行回测。
代码#
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
futures_like = True
if futures_like:
    commission, margin, mult = 2.0, 2000.0, 10.0
else:
    commission, margin, mult = 0.
005, None, 1
class SMACrossOver(bt.Strategy):
    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
    def notify(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 enough 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))
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
                self.opsize = order.executed.size
            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
                gross_pnl = (order.executed.price - self.buyprice) * \
                    self.opsize
                if margin:
                    gross_pnl *= mult
                net_pnl = gross_pnl - self.buycomm - order.executed.comm
                self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                         (gross_pnl, net_pnl))
    def __init__(self):
        sma = btind.SMA(self.data)
        # > 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()  # 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()
if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()
    # Add a strategy
    cerebro.addstrategy(SMACrossOver)
    # Create a Data Feed
    datapath = ('../../datas/2006-day-001.txt')
    data = bt.feeds.BacktraderCSVData(dataname=datapath)
    # Add the Data Feed to Cerebro
    cerebro.adddata(data)
    # set commission scheme -- CHANGE HERE TO PLAY
    cerebro.broker.setcommission(
        commission=commission, margin=margin, mult=mult)
    # Run over everything
    cerebro.run()
    # Plot the result
    cerebro.plot()参考#
class backtrader.CommInfoBase()基础类用于佣金方案。
参数:
commission(默认值:0.0):基础佣金值,百分比或货币单位mult(默认值:1.0):应用于资产价值/利润的乘数margin(默认值:None):开/持仓所需的货币单位,仅当类中的_stocklike属性设置为 False 时适用automargin(默认值:False):用于方法get_margin,自动计算所需的保证金/保证金commtype(默认值:None):支持的值为 CommInfoBase.COMM_PERC(佣金理解为 %)和 CommInfoBase.COMM_FIXED(佣金理解为货币单位)
默认值 None 是一个支持的值,用于保留与旧的 CommissionInfo 对象的兼容性。如果 commtype 设置为 None,则以下适用:
margin 为 None:内部 _commtype 设置为 COMM_PERC,_stocklike 设置为 True(按百分比操作股票)
margin 不是 None:_commtype 设置为 COMM_FIXED,_stocklike 设置为 False(按固定回合佣金操作期货)
如果此参数设置为其他值,则将传递给内部 _commtype 属性,并且相同将适用于参数 stocklike 和内部属性 _stocklike
stocklike(默认值:False): 指示工具是类似股票还是类似期货(请参阅上述 commtype 讨论)percabs(默认值:False): 当 commtype 设置为 COMM_PERC 时,参数 commission 是否理解为 XX% 或 0.XX如果此参数为 True:0.XX
如果此参数为 False:XX%
interest(默认值:0.0):如果这是非零值,则这是持有空头头寸的年利率。这主要是指股票空头卖空公式:天数 * 价格 * 绝对值(大小)*(利息 / 365)
必须以绝对值表示:0.05 -> 5%
注意,可以通过覆盖方法
_get_credit_interest更改行为interest_long(默认值:False):某些产品如 ETF 对空头和多头头寸收取利息。如果为 True 并且 interest 为非零,则对两个方向都收取利息leverage(默认值:1.0):与所需现金相比的杠杆比例
CommissionInfo 类#
基础类用于实际佣金方案。CommInfoBase 是为了保持对 backtrader 提供的原始不完整支持。新的佣金方案从此类派生。默认的 percabs 值也更改为 True .
参数:
percabs(默认值:True):当 commtype 设置为 COMM_PERC 时,参数 commission 是否理解为 XX% 或 0.XX如果此参数为 True:0.XX
如果此参数为 False:XX%
返回此佣金方案允许的杠杆水平
get_leverage()返回在给定价格下满足现金操作所需的大小
getsize(price, cash)返回操作所需的现金量
getoperationcost(size, price)返回给定价格的大小值。对于类似期货的对象,固定为大小 * 保证金
getvaluesize(size, price)返回给定价格的头寸价值。对于类似期货的对象,固定为大小 * 保证金
getvalue(position, price)返回在给定价格下单个资产所需的实际保证金/保证金。默认实现有以下策略:
使用参数 margin 如果参数 automargin 评估为 False
使用参数 mult,即 mult * price 如果 automargin < 0
使用参数 automargin,即 automargin * price 如果 automargin > 0
get_margin(price)计算在给定价格下的操作佣金
getcommission(size, price)计算在给定价格下的操作佣金
pseudoexec:如果为 True,则操作尚未执行
_getcommission(size, price, pseudoexec)返回头寸的实际损益
profitandloss(size, price, newprice)计算价格差异的现金调整
cashadjust(size, price, newprice)计算股票卖空或特定产品的信用费用
get_credit_interest(data, pos, dt)此方法返回由经纪商收取的信用利息费用。
在 size > 0 的情况下,仅当类的参数 interest_long 为 True 时才调用此方法
计算信用利率的公式是:公式:天数 * 价格 * 绝对值(大小)*(利息 / 365)
_get_credit_interest(data, size, price, days, dt0, dt1)参数:
data:收取利息的数据馈送size:当前头寸大小。> 0 表示多头头寸,< 0 表示空头头寸(此参数不会为 0)price:当前头寸价格days:自上次信用计算以来经过的天数(这是(dt0 - dt1).days)dt0:(datetime.datetime)当前日期时间dt1:(datetime.datetime)上次计算的日期时间
默认实现中不使用 dt0 和 dt1,它们作为覆盖方法的额外输入提供。