User Defined Commissions
The commission schemes implementation was reworked not so long ago. The most important: part of the rework 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
The basics were outlined in Extending Commissions.
Note
See below he 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.broker.addcommission(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.broker.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
Here it is:
class CommInfoBase(with_metaclass(MetaParams)):
'''Base Class for the Commission Schemes.
Params:
- commission (def: 0.0): base commission value in percentage or monetary
units
- mult (def 1.0): multiplier applied to the asset for value/profit
- margin (def: None): amount of monetary units needed to open/hold an
operation. It only applies if the final ``_stocklike`` attribute in the
class is set to False
- commtype (def: None): Supported values are CommInfoBase.COMM_PERC
(commission to be understood as %) and CommInfoBase.COMM_FIXED
(commission to be understood as monetary units)
The default value of ``None`` is a supported value to retain
compatibility with the legacy ``CommissionInfo`` object. If
``commtype`` is set to None, then the following applies:
- margin is None: Internal _commtype is set to COMM_PERC and
_stocklike is set to True (Operating %-wise with Stocks)
- margin is not None: _commtype set to COMM_FIXED and _stocklike set
to False (Operating with fixed rount-trip commission with Futures)
If this param is set to something else than None, then it will be
passed to the internal ``_commtype`` attribute and the same will be
done with the param ``stocklike`` and the internal attribute
``_stocklike``
- stocklike (def: False): Indicates if the instrument is Stock-like or
Futures-like (see the ``commtype`` discussion above)
- percabs (def: False): when ``commtype`` is set to COMM_PERC, whether
the parameter ``commission`` has to be understood as XX% or 0.XX
If this param is True: 0.XX
If this param is False: XX%
Attributes:
- _stocklike: Final value to use for Stock-like/Futures-like behavior
- _commtype: Final value to use for PERC vs FIXED commissions
This two are used internally instead of the declared params to enable the
compatibility check described above for the legacy ``CommissionInfo``
object
'''
COMM_PERC, COMM_FIXED = range(2)
params = (
('commission', 0.0), ('mult', 1.0), ('margin', None),
('commtype', None),
('stocklike', False),
('percabs', False),
)