In the previous article in this series we discussed the Asset
class hierarchy that forms the basis of the assets to be traded within the QSTrader backtesting simulation system.
In this article we are going to begin our examination of the classes that make up the simulated brokerage component of the system. This aspect of QSTrader models a broker, the job of which is to keep track of all the accounting associated with transacting in assets. It is also responsible for keeping track of corporate actions that materially affect the holdings, such as cash disbursements or dividends on a common stock.
Another advantage of encapsulating this logic within a class is that if the interface is defined appropriately, it is possible to switch-out the simulated brokerage component with a live trading component, without the need to modify any of the remaining quant trading logic. Hebce the quant trading algorithm encapsulation will be agnostic to whether the broker is simulated or live traded.
The brokerage is a complex component and relies on various sub-components to function, namely a FeeModel
class hierarchy, a Portfolio
component and a Transaction
component. In this article we are going to concentrate on the FeeModel
classes.
Transaction Costs
One of the most important aspects of simulating a systematic trading strategy is modeling and keeping track of transaction costs. These costs can take many forms including brokerage commission and taxes as well as indirect trading costs such as slippage and market impact. Modeling slippage and market impact is a deep topic and will be the subject of future articles. For this article we will be looking at brokerage commission and taxes, both of which can be modelled using the FeeModel
class hierarchy.
The FeeModel
is designed to encapsulate the specifics of a brokerage's cost structure used for trading. These can become fairly complex depending upon trading region, asset class, volume traded and time in the past when the trade is being simulated.
In addition to the broker's own fees there are regionally specific taxes that need to be paid. A key example is UK Stamp Duty on share transactions.
In its current form QSTrader supports two simple fee models. The first is ZeroFeeModel
, which simply applies no commission or tax to any transactions. This is useful for creating a baseline simulation that can be compared against other, more realistic fee models. The second is PercentFeeModel
, which applies a percentage commission and/or percentage taxation to the provided traded amount (known as the 'consideration'). We will now describe all of these in turn.
FeeModel
FeeModel
is the abstract base class from which all derived subclass fee models inherit. It provides three abstract method specifications: _calc_commission
, _calc_tax
and calc_total_cost
. The underscore prefixing the first two methods indicates that they are pseudo-private methods with functionality needed by the class itself. The last method is the pseudo-public interface method and is used to calculate the total brokerage transaction cost when applied to a traded consideration.
Note that the Python language does not have the concept of private or public methods, unlike C++ or Java. Instead the single underscore prefixing a method is designed to tell other clients of the modules which methods should be called as the interface (no underscore), while which are implementation specific methods (prefixed with an unerscore).
The _calc_commission
and _calc_tax
methods are separated to allow logic for each type of cost to be calculated separately. This would be the case, for instance where a 'sliding scale' commission may be utilised but the tax itself might be a specific fixed percentage irrespective of traded consideration amount.
The calc_total_cost
method has three mandatory arguments and one optional keyword argument. The first three include asset
, quantity
and consideration
. The asset is required as different asset classes will have differing commission values. The consideration parameter is effectively the price of the asset multiplied by the quantity and provides an idea of 'traded amount' in currency, independent of the asset class.
While it might seem that only the asset and consideration parameters are needed (as commission is generally calculated on the traded amount) there are certain brokerages that base their commission on the quantity of an asset being traded (that is, not its price necessarily) rather than its "dollar value". For this reason it has been included in QSTrader's design in order to support these types of situations.
The final parameter is a handle to the broker instance (either simulated or live). This is needed to potentially obtain additional information that might be relevant in regards to calculating the commission. One example might be to obtain the actual date of the trade, since brokerages change their commission structure over time and to realistically model this through time it would be necessary to know the actual traded date.
The code for FeeModel
follows. It is a fairly straightforward abstract base class implementation:
from abc import ABCMeta, abstractmethod
class FeeModel(object):
"""
Abstract class to handle the calculation of brokerage
commission, fees and taxes.
"""
__metaclass__ = ABCMeta
@abstractmethod
def _calc_commission(self, asset, quantity, consideration, broker=None):
raise NotImplementedError(
"Should implement _calc_commission()"
)
@abstractmethod
def _calc_tax(self, asset, quantity, consideration, broker=None):
raise NotImplementedError(
"Should implement _calc_tax()"
)
@abstractmethod
def calc_total_cost(self, asset, quantity, consideration, broker=None):
raise NotImplementedError(
"Should implement calc_total_cost()"
)
ZeroFeeModel
The first class that implements this abstract interface is the ZeroFeeModel
derived subclass. It is an exceedingly simple class that returns 0.0
for both _calc_tax
and _calc_commission
. These two values are then added in calc_total_cost
to produce 0.0
for the total traded cost.
Why would such a class be used in a real simulation? The main reason for its inclusion is to allow comparison between various fee models within a backtest simulation. Using ZeroFeeModel
allows a quant researcher to see the effect of the backtest without costs and compare it to various models of brokerage commission and tax. Thus it is possible to see if a strategy remains profitable even above its transaction costs.
The code for ZeroFeeModel
follows. The majority of the lines of code for this model are docstrings. The actual implementation is minimal:
from qstrader.broker.fee_model.fee_model import FeeModel
class ZeroFeeModel(FeeModel):
"""
A FeeModel subclass that produces no commission, fees
or taxes. This is the default fee model for simulated
brokerages within QSTrader.
"""
def _calc_commission(self, asset, quantity, consideration, broker=None):
"""
Returns zero commission.
Parameters
----------
asset : `str`
The asset symbol string.
quantity : `int`
The quantity of assets (needed for InteractiveBrokers
style calculations).
consideration : `float`
Price times quantity of the order.
broker : `Broker`, optional
An optional Broker reference.
Returns
-------
`float`
The zero-cost commission.
"""
return 0.0
def _calc_tax(self, asset, quantity, consideration, broker=None):
"""
Returns zero tax.
Parameters
----------
asset : `str`
The asset symbol string.
quantity : `int`
The quantity of assets (needed for InteractiveBrokers
style calculations).
consideration : `float`
Price times quantity of the order.
broker : `Broker`, optional
An optional Broker reference.
Returns
-------
`float`
The zero-cost tax.
"""
return 0.0
def calc_total_cost(self, asset, quantity, consideration, broker=None):
"""
Calculate the total of any commission and/or tax
for the trade of size 'consideration'.
Parameters
----------
asset : `str`
The asset symbol string.
quantity : `int`
The quantity of assets (needed for InteractiveBrokers
style calculations).
consideration : `float`
Price times quantity of the order.
broker : `Broker`, optional
An optional Broker reference.
Returns
-------
`float`
The zero-cost total commission and tax.
"""
commission = self._calc_commission(asset, quantity, consideration, broker)
tax = self._calc_tax(asset, quantity, consideration, broker)
return commission + tax
PercentFeeModel
A slightly more realistic model of transaction costs is provided by the PercentFeeModel
class. This class provides an initialisation __init__
method with two parameters. The first is the commission_pct
, which is the fixed percentage brokerage commission fee to apply to the consideration. The second is tax_pct
, which is the fixed percentage taxation to apply to the consideration. Both of these values are specified in the range [0.0, 1.0]. That is, 1.0 equals 100%.
Both methods simply apply this percentage to the absolute value of the consideration. This avoids negative commissions when selling assets! For instance the calculation for brokerage commission looks like:
The code for PercentFeeModel
follows. As with ZeroFeeModel
the majority of the lines of code for this model are docstrings. The actual implementation is minimal:
from qstrader.broker.fee_model.fee_model import FeeModel
class PercentFeeModel(FeeModel):
"""
A FeeModel subclass that produces a percentage cost
for tax and commission.
Parameters
----------
commission_pct : `float`, optional
The percentage commission applied to the consideration.
0-100% is in the range [0.0, 1.0]. Hence, e.g. 0.1% is 0.001
tax_pct : `float`, optional
The percentage tax applied to the consideration.
0-100% is in the range [0.0, 1.0]. Hence, e.g. 0.1% is 0.001
"""
def __init__(self, commission_pct=0.0, tax_pct=0.0):
super().__init__()
self.commission_pct = commission_pct
self.tax_pct = tax_pct
def _calc_commission(self, asset, quantity, consideration, broker=None):
"""
Returns the percentage commission from the consideration.
Parameters
----------
asset : `str`
The asset symbol string.
quantity : `int`
The quantity of assets (needed for InteractiveBrokers
style calculations).
consideration : `float`
Price times quantity of the order.
broker : `Broker`, optional
An optional Broker reference.
Returns
-------
`float`
The percentage commission.
"""
return self.commission_pct * abs(consideration)
def _calc_tax(self, asset, quantity, consideration, broker=None):
"""
Returns the percentage tax from the consideration.
Parameters
----------
asset : `str`
The asset symbol string.
quantity : `int`
The quantity of assets (needed for InteractiveBrokers
style calculations).
consideration : `float`
Price times quantity of the order.
broker : `Broker`, optional
An optional Broker reference.
Returns
-------
`float`
The percentage tax.
"""
return self.tax_pct * abs(consideration)
def calc_total_cost(self, asset, quantity, consideration, broker=None):
"""
Calculate the total of any commission and/or tax
for the trade of size 'consideration'.
Parameters
----------
asset : `str`
The asset symbol string.
quantity : `int`
The quantity of assets (needed for InteractiveBrokers
style calculations).
consideration : `float`
Price times quantity of the order.
broker : `Broker`, optional
An optional Broker reference.
Returns
-------
`float`
The total commission and tax.
"""
commission = self._calc_commission(asset, quantity, consideration, broker)
tax = self._calc_tax(asset, quantity, consideration, broker)
return commission + tax
Note that this class as implemented does not allow for a 'sliding scale' percentage based on the sie of the consideration. In most real world brokerages different percentages would apply as the size of the consideration increases. This sliding scale would also likely vary by asset class and geographic region.
This logic is usually highly specific to the brokerage in question and as such attempting to create all varieties of broker commission structure would be a difficult task. However the class logic is generic enough to support a wide range of brokerage costs structures. Future articles will cover the implementation of more realistic trading costs using the same class hierarchy as above.
Note: All code for the fee model can be found at the relevant QSTrader fee model section on GitHub here: QSTrader Fee Model.