QSTrader Fee Model Class Hierarchy

In this new article series we are going to continue looking at the classes within the QSTrader open source backtesting engine. This particular article will discuss the FeeModel class hierarchy.

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: return self.commission_pct * abs(consideration). The implementation is almost identical for the taxation method.

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.

Related Articles