佣金 #
中立性 #
在开始之前,让我们记住 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,它们作为覆盖方法的额外输入提供。