User Defined Commissions
The most important part of reworking the CommInfo object to the actual incarnation involved:
-
Retaining the original
CommissionInfo
class and behavior -
Opening the door for easy creation of user defined commissions
-
Making the format xx% the default for new commission schemes instead of 0.xx (just a matter of taste), keeping the behavior configurable
Note
See below the docstring of CommInfoBase
for parameters reference
Defining a Commission Scheme
It involves 1 or 2 steps
-
Subclassing
CommInfoBase
Simply changing the default parameters may be enough.
backtrader
already does this with some definitions present in the modulebacktrader.commissions
. The regular industry standard for Futures is a fixed amount per contract and per round. The definition can be done as:class CommInfo_Futures_Fixed(CommInfoBase): params = ( ('stocklike', False), ('commtype', CommInfoBase.COMM_FIXED), )
For stocks and perc-wise commissions:
class CommInfo_Stocks_Perc(CommInfoBase): params = ( ('stocklike', True), ('commtype', CommInfoBase.COMM_PERC), )
As stated above the default for the interpretation of the percentage here (passed as parameter
commission
) is that of: xx%. Should the old/other behavior be wished 0.xx, it can be easily done:class CommInfo_Stocks_PercAbs(CommInfoBase): params = ( ('stocklike', True), ('commtype', CommInfoBase.COMM_PERC), ('percabs', True), )
-
Overriding (if needed be) the
_getcommission
methodDefined as:
def _getcommission(self, size, price, pseudoexec): '''Calculates the commission of an operation at a given price pseudoexec: if True the operation has not yet been executed '''
More details in a practical example below
How to apply this to the platform
Once a CommInfoBase
subclass is in place the trick is to use
broker.addcommissioninfo
rather than the usual broker.setcommission
. The
latter will internally use the legacy CommissionInfoObject
.
Easier done than said:
... comminfo = CommInfo_Stocks_PercAbs(commission=0.005) # 0.5% cerebro.broker.addcommissioninfo(comminfo)
The addcommissioninfo
method is defined as follows:
def addcommissioninfo(self, comminfo, name=None): self.comminfo[name] = comminfo
Setting name
means that the comminfo
object will only apply to assets
with that name. The default value of None
means it applies to all assets in
the system.
A practical example
Ticket #45 asks about a commission scheme which applies to Futures, is percentage wise and uses the commission percentage on the entire “virtual” value of the contract. ie: includes the future multiplier in the commission calculation.
It should be easy:
import backtrader as bt class CommInfo_Fut_Perc_Mult(bt.CommInfoBase): params = ( ('stocklike', False), # Futures ('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission # ('percabs', False), # pass perc as xx% which is the default ) def _getcommission(self, size, price, pseudoexec): return size * price * self.p.commission * self.p.mult
Putting it into the system:
comminfo = CommInfo_Fut_Perc_Mult( commission=0.1, # 0.1% mult=10, margin=2000 # Margin is needed for futures-like instruments ) cerebro.addcommissioninfo(comminfo)
If the format 0.xx is preferred as the default, just set param percabs
to True
:
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase): params = ( ('stocklike', False), # Futures ('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission ('percabs', True), # pass perc as 0.xx ) comminfo = CommInfo_Fut_Perc_Mult( commission=0.001, # 0.1% mult=10, margin=2000 # Margin is needed for futures-like instruments ) cerebro.addcommissioninfo(comminfo)
This all should do the trick.
Explaining pseudoexec
Let’s recall the definition of _getcommission
:
def _getcommission(self, size, price, pseudoexec): '''Calculates the commission of an operation at a given price pseudoexec: if True the operation has not yet been executed '''
The purpose of the pseudoexec
arg may seem obscure but it serves a purpose.
-
The platform may call this method to do precalculation of available cash and some other tasks
-
This means that the method may (and it actually will) be called more than once with the same parameters
pseudoexec
indicates whether the call corresponds to the actual execution of
an order. Although at first sight this may not seem “relevant”, it is if
scenarios like the following are considered:
-
A broker offers a 50% discount on futures round-trip commission once the amount of negotiated contracts has exceeeded 5000 units
In such case and if
pseudoexec
was not there, the multiple non-execution calls to the method would quickly trigger the assumption that the discount is in place.
Putting the scenario to work:
import backtrader as bt class CommInfo_Fut_Discount(bt.CommInfoBase): params = ( ('stocklike', False), # Futures ('commtype', bt.CommInfoBase.COMM_FIXED), # Apply Commission # Custom params for the discount ('discount_volume', 5000), # minimum contracts to achieve discount ('discount_perc', 50.0), # 50.0% discount ) negotiated_volume = 0 # attribute to keep track of the actual volume def _getcommission(self, size, price, pseudoexec): if self.negotiated_volume > self.p.discount_volume: 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 if not pseudoexec: # keep track of actual real executed size for future discounts self.negotiated_volume += size return commvalue
The purpose and being of pseudoexec
are hopefully clear now.
CommInfoBase docstring and params
See Commissions: Stocks vs Futures for the reference of
CommInfoBase