上一篇文章中,介绍了交易系统的详细信息,但是对于commission的介绍还是比较简单,尤其是系统缺省的方案并不适用于中国。本文增加增加更多的信息,提供自定义开发的方法,并提供一个适用于国内的佣金方案代码。
佣金(CommInfoBase)
佣金方案的基本信息
佣金是交易系统中成本模型的一部分。不过,这种成本是固定且可预计的,因此较为简单,只要根据和券商的协议填写清楚即可,由于这个类过程并不复杂,不解读代码了,看几个关键点。
看过之前Cerebro代码就会知道,咱们在实例化Cerebro之后,就可以直接设置佣金方案了,如下:
cerebro.broker.setcommission(commission=0.001)#设定交易费用(买卖都收)Cerebro获取broker(broker的实例化和初始化在2.1节描述),然后调用setcommission设置佣金:
def setcommission(self,
commission=0.0, margin=None, mult=1.0,
commtype=None, percabs=True, stocklike=False,
interest=0.0, interest_long=False, leverage=1.0,
automargin=False,
name=None):
comm = CommInfoBase(commission=commission, margin=margin, mult=mult,
commtype=commtype, stocklike=stocklike,
percabs=percabs,
interest=interest, interest_long=interest_long,
leverage=leverage, automargin=automargin)
self.comminfo[name] = commBroker首先会实例化一个CommInfoBase,然后记录到commoninfo字典中,这里要注意,一个Broker可以拥有多个CommInfoBase,通过name来区分,name和Data类创建时候使用的名字一致,也就是不同的数据源(对应不同的资产)佣金方案可以不同。在相同的券商中,针对不同的资产不同的佣金,例如对于股票和ETF佣金不同,这里就可以设置不同佣金方案。
CommInfoBase就是设置相关参数,关键参数具体解释如下表:
参数 | 缺省值 | 含义 | commission | 0.0 | 就是基本佣金数据,按照百分比或者绝对值。百分比就是按照交易的金额(size*price)的百分比提取佣金。绝对值,就是针对size提取固定佣金。比如一笔交易(或者一个合约)收取2元钱。至于具体是采取百分比还是固定值,参见下面参数说明。 | commtype | None | 就是用来指定佣金数据是百分比还是固定值。
ommInfoBase.COMM_PERC:按照百分比解释。
CommInfoBase.COMM_FIXED:按照固定值解释。
None:这种情况下系统根据资产情况自动判决,判决方法参见参数margin。为啥搞得这么复杂,主要是为了兼容原来老的类(CommissionInfo),原来是通过margin来判断是否百分比。 | percabs | False | 当 commtype 设置为 COMM_PERC 时,指定百分比数字的填写方法。比如说,你要设置0.1%,这个值设置为True,那么commission参数填写为0.001,为False的话,只要填写为0.1.为啥搞得怎么麻烦?还是兼容性问题。因为老版本中commission填写值不包括百分号,还记得咱们之前的例子要求“0.1% ,需要除以100然后去掉%”,这个就是老的处理方式,在新版本中,如果保持老的处理方式,percaps需要填写为TRUE。 | stocklike | False | 这里用来指示股票类资产还是期权类资产,在老的CommissionInfo,是通过margin来确定是否期权。在新的类中,当commtype为None的时候,可以通过这个参数来指示是否股票类资产。 | margin | None | 保证金,港股称为孖展。Margin为0或者None,那么commission就是按照百分比解释。否则按照固定值解释。主要是因为Margin主要针对的期货/期权产品,通常按合约计价。这个是之前类的使用方法,新版本不再采用。 | mult | 1.0 | 杠杆比,这个主要应用于期权等可以加杠杆的资产,Backtrader据此计算盈利和亏损。 | name | 空 | 前面已叙及。 | interest | 0.0 | 利息。有些情况下,需要对所持资产计算利息,例如借入证券卖空,由于这些对于我们个人交易者使用不多,咱们先忽略。 | interest_long | False | 是否对多头仓位收取利息,和上面一样,是否对做多的资产收取利息,先忽略。 | 系统佣金方案还有缺点,从代码来看,有些常用的场景就无法支持,比如常用的一笔交易最小5块,这个咋设置,还有除了佣金费用之外,港股/美股还需要平台费,也没法模拟。这就需要我们进行定制。
佣金方案的开发
如前所述,在不同国家,甚至不同券商,都可能有不同的佣金方案,Backtrader提供了定制佣金方案,而且由于采用了面向对象的机制,所以定制方案也很很简单。
如何定制呢?首先,继承CommInfoBase类。然后根据你的需求修改参数和重载函数。
先说简单的自定义方法,修改参数。比如,对于股票和期权,参数不一样,那么,我们可以分别定义参数重写针对股票和期权的佣金方案。
比如,对于期权,我们定义固定值的佣金方案:
class CommInfo_Futures_Fixed(CommInfoBase):
params = (
('stocklike', False),
('commtype', CommInfoBase.COMM_FIXED),
)这样的话,使用的时候只需要输入commision就行了,其他参数就不要关注了。
同样的,也可以定义百分比的股票佣金方案:
class CommInfo_Stocks_Perc(CommInfoBase):
params = (
('stocklike', True),
('commtype', CommInfoBase.COMM_PERC),
)假如你希望按照老方法输入0.001表示0.1%,那么还可以这样定义:
class CommInfo_Stocks_PercAbs(CommInfoBase):
params = (
('stocklike', True),
('commtype', CommInfoBase.COMM_PERC),
('percabs', True),
)如果你还需要修改佣金的计算方法,那么你可以重载_getcommission 函数,定义如下,这个后面会给你一个详细的实例。
def _getcommission(self, size, price, pseudoexec):
'''根据规模以及价格计算佣金
'''定义好佣金方案之后,下一步就是通过Cerebro调用broker.addcommissioninfo添加这个方案。注意不是使用setcommission,因为这个函数只是针对老把能的CommissionInfo对象。如下示例:
...
comminfo = CommInfo_Stocks_PercAbs(commission=0.001) # 0.1%
cerebro.broker.addcommissioninfo(comminfo)addcommissioninfo函数定义如下:
def addcommissioninfo(self, comminfo, name=None):
'''Adds a ``CommissionInfo`` object that will be the default for all assets if
``name`` is ``None``'''
self.comminfo[name] = comminfo注意,可以设定佣金方案对应的资产,通过data的name来指定。
好了,来一个实战的案例,比如在咱们国内,佣金方案可能如下:
- 佣金按照百分比。
- 每一笔交易有一个最低值,比如5块,当然有些券商可能会免5.
- 卖出股票还需要收印花税。
- 可能有的平台还需要收平台费。
按照这个需求,可以定义佣金方案如下:
class MyStockCommissionScheme(bt.CommInfoBase):
'''
1.佣金按照百分比。
2.每一笔交易有一个最低值,比如5块,当然有些券商可能会免5.
3.卖出股票还需要收印花税。
4.可能有的平台还需要收平台费。
'''
params = (
('stampduty', 0.005), # 印花税率
('commission', 0.005), # 佣金率
('stocklike', True),#股票类资产,不考虑保证金
('commtype', bt.CommInfoBase.COMM_PERC),#按百分比
('minCommission', 5),#最小佣金
('platFee', 0),#平台费用
)
def _getcommission(self, size, price, pseudoexec):
'''
size>0,买入操作。
size<0,卖出操作。
&#39;&#39;&#39;
if size > 0: # 买入,不考虑印花税,需要考虑最低收费
return max(size * price * self.p.commission,self.p.minCommission)+platFee
elif size < 0: # 卖出,考虑印花税。
return max(abs(size) * price * self.p.commission,self.p.minCommission)+abs(size) * price * self.p.stampduty+platFee
else:
return 0 # 防止特殊情况下size为0.注释很清楚,就不解释了。
有的同学眼尖,_getcommission看到还有一个参数pseudoexec,这个干嘛的? 这个参数用于指示当前的调用是否用于真正的订单执行过程。那么怎么还会有不是订单的执行过程呢?大家还记得前面订单有一个参数checksubmit,这个参数就是要求在提交订单前检查现金是否足够,这个检查过程中也会调用_getcommission计算佣金,那么,在一次买/卖交易中,可能多次计算佣金。为了区分哪一次调用是真正的为了订单实际执行的调用,就需要使用这个参数。
区分订单实际执行有啥作用呢?比如这样一个场景,你和券商商量好了,如果交易量(规模)超过5000,就给我佣金50%的折扣。这交易量的计算就得使用这个参数来区分,否则的话,系统将一些并未执行的佣金计算也计入的话,券商也不认啊。
下面给个例子(官网)来看看。
import backtrader as bt
class CommInfo_Fut_Discount(bt.CommInfoBase):
params = (
(&#39;stocklike&#39;, False), # 期权类资产
(&#39;commtype&#39;, bt.CommInfoBase.COMM_FIXED), # 固定佣金
# 优惠方案参数
(&#39;discount_volume&#39;, 5000), # 优惠所需最小合约数量
(&#39;discount_perc&#39;, 50.0), # 50.0% 的折扣
)
negotiated_volume = 0 # 跟踪实际交易合约规模的参数
def _getcommission(self, size, price, pseudoexec):
if self.negotiated_volume > self.p.discount_volume:#超过最小次数,这设置折扣50%
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#计算折扣后的佣金。这里的size应该是abs(size),不然有负数。
if not pseudoexec:
# 跟踪实际交易合约规模
self.negotiated_volume += size #如果实际执行(非伪执行),这需要累加本次实际交易的规模。
return commvalue通过上述例子,应该就很容易理解pseudoexec参数的使用了。 |
|