Advanced Trading Infrastructure - Portfolio Handler Class

Advanced Trading Infrastructure - Portfolio Handler Class

In the current series on Advanced Trading Infrastructure we have described both the Position Class and the Portfolio Class - two essential components of a robust backtesting and live trading system. In this article we are going to extend our discussion to the Portfolio Handler Class, which will round out the description of the portfolio Order Management System (OMS).

The OMS is the backbone of any quantitative trading infrastructure. It needs to keep track of open (and closed) positions in assets, it needs to group those positions together into a portfolio (with cash) and must modify that portfolio with new trading signals, risk management overlays and position sizing rules.

In this article we will discuss the PortfolioHandler. This particular class is tasked with managing a Portfolio object, telling it whether to open/close positions based on information it receives from the Strategy, PositionSizer, RiskManager and ExecutionHandler. This class is extremely important as it ties the rest of the components together.

The following code presented in this article comes from the QSTrader open-source backtesting and live trading engine. I have released it under a liberal open-source MIT license and the latest version can always be found at https://github.com/mhallsmoore/qstrader. If you would like to keep up fully to date with the project, please take a look at that link.

In the previous article I listed out a current Component Reminder that detailed how all of the components of QSTrader fit together. Please take a look at it to remind yourself of how they all interact.

Let's now turn our attention to the PortfolioHandler class and see how it interacts with the Portfolio object.

PortfolioHandler

The first issue to discuss is why the older style of Portfolio class from QSForex has now been replaced with a calculation-heavy Portfolio class carrying out Position tracking, as well as the lighter PortfolioHandler class.

I made this choice because I felt it was cleaner to have a separate Portfolio object that was only tasked with keeping track of cash and open positions. The primary purpose of such an approach is to allow "theoretical" portfolio objects to be created (i.e. by the PositionSizer or RiskManager), and subsequently create a set of necessary trades to get from the current portfolio to the theoretically desired one.

This process is a lot more straightforward if the Portfolio is simply a grouping of Position objects and a cash balance.

This leaves the interaction with the events queue, the PositionSizer, RiskManager and PriceHandler. These interactions are handled by the new object, the PortfolioHandler.

I've created the PortfolioHandler in the file portfolio_handler.py and I've provided the full listing below. I'll break down the listing afterwards.

Note that any of these listings are subject to change, since I will be continually making changes to this project. Eventually I hope others will collaborate by providing Pull Requests to the codebase.

portfolio_handler.py

from qstrader.order.order import SuggestedOrder
from qstrader.portfolio.portfolio import Portfolio


class PortfolioHandler(object):
    def __init__(
        self, initial_cash, events_queue, 
        price_handler, position_sizer, risk_manager
    ):
        """
        The PortfolioHandler is designed to interact with the 
        backtesting or live trading overall event-driven
        architecture. It exposes two methods, on_signal and
        on_fill, which handle how SignalEvent and FillEvent
        objects are dealt with.

        Each PortfolioHandler contains a Portfolio object,
        which stores the actual Position objects. 

        The PortfolioHandler takes a handle to a PositionSizer
        object which determines a mechanism, based on the current
        Portfolio, as to how to size a new Order.

        The PortfolioHandler also takes a handle to the 
        RiskManager, which is used to modify any generated 
        Orders to remain in line with risk parameters.
        """
        self.initial_cash = initial_cash
        self.events_queue = events_queue
        self.price_handler = price_handler
        self.position_sizer = position_sizer
        self.risk_manager = risk_manager
        self.portfolio = Portfolio(price_handler, initial_cash)

    def _create_order_from_signal(self, signal_event):
        """
        Take a SignalEvent object and use it to form a
        SuggestedOrder object. These are not OrderEvent objects,
        as they have yet to be sent to the RiskManager object.
        At this stage they are simply "suggestions" that the
        RiskManager will either verify, modify or eliminate.
        """
        order = SuggestedOrder(
            signal_event.ticker, signal_event.action
        )
        return order

    def _place_orders_onto_queue(self, order_list):
        """
        Once the RiskManager has verified, modified or eliminated
        any order objects, they are placed onto the events queue,
        to ultimately be executed by the ExecutionHandler.
        """
        for order_event in order_list:
            self.events_queue.put(order_event)

    def _convert_fill_to_portfolio_update(self, fill_event):
        """
        Upon receipt of a FillEvent, the PortfolioHandler converts
        the event into a transaction that gets stored in the Portfolio
        object. This ensures that the broker and the local portfolio
        are "in sync".

        In addition, for backtesting purposes, the portfolio value can
        be reasonably estimated in a realistic manner, simply by 
        modifying how the ExecutionHandler object handles slippage,
        transaction costs, liquidity and market impact.
        """
        action = fill_event.action       
        ticker = fill_event.ticker
        quantity = fill_event.quantity
        price = fill_event.price
        commission = fill_event.commission
        # Create or modify the position from the fill info
        self.portfolio.transact_position(
            action, ticker, quantity, 
            price, commission
        )

    def on_signal(self, signal_event):
        """
        This is called by the backtester or live trading architecture
        to form the initial orders from the SignalEvent. 

        These orders are sized by the PositionSizer object and then
        sent to the RiskManager to verify, modify or eliminate.

        Once received from the RiskManager they are converted into
        full OrderEvent objects and sent back to the events queue.
        """
        # Create the initial order list from a signal event
        initial_order = self._create_order_from_signal(signal_event)
        # Size the quantity of the initial order
        sized_order = self.position_sizer.size_order(
            self.portfolio, initial_order
        )
        # Refine or eliminate the order via the risk manager overlay
        order_events = self.risk_manager.refine_orders(
            self.portfolio, sized_order
        )
        # Place orders onto events queue
        self._place_orders_onto_queue(order_events)

    def on_fill(self, fill_event):
        """
        This is called by the backtester or live trading architecture
        to take a FillEvent and update the Portfolio object with new
        or modified Positions.

        In a backtesting environment these FillEvents will be simulated
        by a model representing the execution, whereas in live trading
        they will come directly from a brokerage (such as Interactive
        Brokers).
        """
        self._convert_fill_to_portfolio_update(fill_event)

The PortfolioHandler requires the SuggestedOrder object as well as the Portfolio. The former is a separate object to an OrderEvent as it has not been through a position sizing or risk management process. Once an order has been through both of those processes it becomes a full OrderEvent.

To initialise a PortfolioHandler we require an initial cash balance and references to the events queue, the price handler, the position sizer and the risk manager. Finally we create the inner associated Portfolio object. Notice that it itself requires access to the price handler and the initial cash balance:

from qstrader.order.order import SuggestedOrder
from qstrader.portfolio.portfolio import Portfolio


class PortfolioHandler(object):
    def __init__(
        self, initial_cash, events_queue, 
        price_handler, position_sizer, risk_manager
    ):
        """
        The PortfolioHandler is designed to interact with the 
        backtesting or live trading overall event-driven
        architecture. It exposes two methods, on_signal and
        on_fill, which handle how SignalEvent and FillEvent
        objects are dealt with.

        Each PortfolioHandler contains a Portfolio object,
        which stores the actual Position objects. 

        The PortfolioHandler takes a handle to a PositionSizer
        object which determines a mechanism, based on the current
        Portfolio, as to how to size a new Order.

        The PortfolioHandler also takes a handle to the 
        RiskManager, which is used to modify any generated 
        Orders to remain in line with risk parameters.
        """
        self.initial_cash = initial_cash
        self.events_queue = events_queue
        self.price_handler = price_handler
        self.position_sizer = position_sizer
        self.risk_manager = risk_manager
        self.portfolio = Portfolio(price_handler, initial_cash)

In the following method, _create_order_from_signal, we simply create the SuggestedOrder from the ticker and action. At this stage we are only supporting market orders. Limit orders, and more exotic forms of execution, will come later:

# portfolio_handler.py    

    def _create_order_from_signal(self, signal_event):
        """
        Take a SignalEvent object and use it to form a
        SuggestedOrder object. These are not OrderEvent objects,
        as they have yet to be sent to the RiskManager object.
        At this stage they are simply "suggestions" that the
        RiskManager will either verify, modify or eliminate.
        """
        order = SuggestedOrder(
            signal_event.ticker, signal_event.action
        )
        return order

_place_orders_onto_queue is a simple helper method that takes in a list of OrderEvent objects and adds them to the events queue:

# portfolio_handler.py    

    def _place_orders_onto_queue(self, order_list):
        """
        Once the RiskManager has verified, modified or eliminated
        any order objects, they are placed onto the events queue,
        to ultimately be executed by the ExecutionHandler.
        """
        for order_event in order_list:
            self.events_queue.put(order_event)

The following method, _convert_fill_to_portfolio_update, takes in a FillEvent message and then adjusts the inner Portfolio object to account for the fill transaction. As can be seen, it shows that the PortfolioHandler does no mathematical calculation of its own, rather it delegates the calculations to the Portfolio class:

# portfolio_handler.py    

    def _convert_fill_to_portfolio_update(self, fill_event):
        """
        Upon receipt of a FillEvent, the PortfolioHandler converts
        the event into a transaction that gets stored in the Portfolio
        object. This ensures that the broker and the local portfolio
        are "in sync".

        In addition, for backtesting purposes, the portfolio value can
        be reasonably estimated in a realistic manner, simply by 
        modifying how the ExecutionHandler object handles slippage,
        transaction costs, liquidity and market impact.
        """
        action = fill_event.action       
        ticker = fill_event.ticker
        quantity = fill_event.quantity
        price = fill_event.price
        commission = fill_event.commission
        # Create or modify the position from the fill info
        self.portfolio.transact_position(
            action, ticker, quantity, 
            price, commission
        )

The on_signal method ties together some of the previous methods. It creates the initial suggested order, then sends it to the PositionSizer object (along with the portfolio) to be refined. Once the sized order is returned, the RiskManager is then sent the order to manage any risk associated with how the new order will affect the current portfolio.

The risk manager then sends back a list of orders. Why a list? Well, consider the fact that a generated trade may induce the risk manager to create a hedging order in another security. Hence there is a need to possibly return more than one order.

Once the list of orders has been created they are all placed onto the events queue:

# portfolio_handler.py    

    def on_signal(self, signal_event):
        """
        This is called by the backtester or live trading architecture
        to form the initial orders from the SignalEvent. 

        These orders are sized by the PositionSizer object and then
        sent to the RiskManager to verify, modify or eliminate.

        Once received from the RiskManager they are converted into
        full OrderEvent objects and sent back to the events queue.
        """
        # Create the initial order list from a signal event
        initial_order = self._create_order_from_signal(signal_event)
        # Size the quantity of the initial order
        sized_order = self.position_sizer.size_order(
            self.portfolio, initial_order
        )
        # Refine or eliminate the order via the risk manager overlay
        order_events = self.risk_manager.refine_orders(
            self.portfolio, sized_order
        )
        # Place orders onto events queue
        self._place_orders_onto_queue(order_events)

The final method in the PortfolioHandler is on_fill. It simply calls the previous method _convert_fill_to_portfolio_update. These two methods have been separated, as in later versions of QSTrader there might be a need for more sophisticated logic to exist. We don't wish to change the on_fill interface to the PortfolioHandler unless absolutely necessary. This helps maintain backward compatibility:

# portfolio_handler.py    

    def on_fill(self, fill_event):
        """
        This is called by the backtester or live trading architecture
        to take a FillEvent and update the Portfolio object with new
        or modified Positions.

        In a backtesting environment these FillEvents will be simulated
        by a model representing the execution, whereas in live trading
        they will come directly from a brokerage (such as Interactive
        Brokers).
        """
        self._convert_fill_to_portfolio_update(fill_event)

This completes the PortfolioHandler class description. For completeness you can find the full code for the PortfolioHandler class on Github at portfolio_handler.py.

portfolio_handler_test.py

Now that we've created the PortfolioHandler we need to test it. Thankfully, the majority of the mathematical tests occur in the Position and Portfolio classes. However, it is still necessary to test that the PortfolioHandler "does the right thing" when it receives strategy-generated signals and execution-generated fills.

While the following tests may seem "trivial", I can assure you that even though it can be quite tedious to write out unit testing code, it is absolutely vital to ensure that you have a functioning system as more complexity is added. One of the most frustrating aspects of software development is not unit testing to "get an answer quickly" and then realising you have a bug and no idea where it is in a large collection of modules!

By carrying out unit testing while we write the individual modules we avoid this problem as much as possible. If a bug is discovered, it is usually much more straightforward to track it down. Time spent unit testing is never wasted!

The following is a full listing of portfolio_handler_test.py. After the listing I will break down the individual objects and methods, as before:

import datetime
from decimal import Decimal
import queue
import unittest

from qstrader.event.event import FillEvent, OrderEvent, SignalEvent
from qstrader.portfolio_handler.portfolio_handler import PortfolioHandler


class PriceHandlerMock(object):
    def __init__(self):
        pass

    def get_best_bid_ask(self, ticker):
        prices = {
            "MSFT": (Decimal("50.28"), Decimal("50.31")),
            "GOOG": (Decimal("705.46"), Decimal("705.46")),
            "AMZN": (Decimal("564.14"), Decimal("565.14")),
        }
        return prices[ticker]


class PositionSizerMock(object):
    def __init__(self):
        pass

    def size_order(self, portfolio, initial_order):
        """
        This PositionSizerMock object simply modifies
        the quantity to be 100 of any share transacted.
        """
        initial_order.quantity = 100
        return initial_order


class RiskManagerMock(object):
    def __init__(self):
        pass

    def refine_orders(self, portfolio, sized_order):
        """
        This RiskManagerMock object simply lets the
        sized order through, creates the corresponding
        OrderEvent object and adds it to a list.
        """
        order_event = OrderEvent(
            sized_order.ticker,
            sized_order.action,
            sized_order.quantity
        )
        return [order_event]


class TestSimpleSignalOrderFillCycleForPortfolioHandler(unittest.TestCase):
    """
    Tests a simple Signal, Order and Fill cycle for the
    PortfolioHandler. This is, in effect, a sanity check.
    """
    def setUp(self):
        """
        Set up the PortfolioHandler object supplying it with
        $500,000.00 USD in initial cash.
        """
        initial_cash = Decimal("500000.00")
        events_queue = queue.Queue()
        price_handler = PriceHandlerMock()
        position_sizer = PositionSizerMock()
        risk_manager = RiskManagerMock()
        # Create the PortfolioHandler object from the rest
        self.portfolio_handler = PortfolioHandler(
            initial_cash, events_queue, price_handler, 
            position_sizer, risk_manager
        )

    def test_create_order_from_signal_basic_check(self):
        """
        Tests the "_create_order_from_signal" method 
        as a basic sanity check.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        order = self.portfolio_handler._create_order_from_signal(signal_event)
        self.assertEqual(order.ticker, "MSFT")
        self.assertEqual(order.action, "BOT")
        self.assertEqual(order.quantity, 0)

    def test_place_orders_onto_queue_basic_check(self):
        """
        Tests the "_place_orders_onto_queue" method 
        as a basic sanity check.
        """
        order = OrderEvent("MSFT", "BOT", 100)
        order_list = [order]
        self.portfolio_handler._place_orders_onto_queue(order_list)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)

    def test_convert_fill_to_portfolio_update_basic_check(self):
        """
        Tests the "_convert_fill_to_portfolio_update" method
        as a basic sanity check.
        """
        fill_event_buy = FillEvent(
            datetime.datetime.utcnow(), "MSFT", "BOT",
            100, "ARCA", Decimal("50.25"), Decimal("1.00")
        )
        self.portfolio_handler._convert_fill_to_portfolio_update(fill_event_buy)

        # Check the Portfolio values within the PortfolioHandler
        port = self.portfolio_handler.portfolio
        self.assertEqual(port.cur_cash, Decimal("494974.00"))

    def test_on_signal_basic_check(self):
        """
        Tests the "on_signal" method as a basic sanity check.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        self.portfolio_handler.on_signal(signal_event)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)


if __name__ == "__main__":
    unittest.main()

The first task is to import the correct modules. We make use of decimal, as in prior articles, as well as the unittest module. We also need to import various Event objects that the PortfolioHandler uses to communicate. Finally we import the PortfolioHandler itself:

import datetime
from decimal import Decimal
import queue
import unittest

from qstrader.event.event import FillEvent, OrderEvent, SignalEvent
from qstrader.portfolio_handler.portfolio_handler import PortfolioHandler

We need to create three "mock" objects (see the previous article for a description of mock objects), one each for the PriceHandler, PositionSizer and RiskManager. The first, PriceHandlerMock, provides us with static bid/ask prices for three shares: MSFT, GOOG and AMZN. Essentially we want to simulate the get_best_bid_ask method for our repeatable unit tests:

class PriceHandlerMock(object):
    def __init__(self):
        pass

    def get_best_bid_ask(self, ticker):
        prices = {
            "MSFT": (Decimal("50.28"), Decimal("50.31")),
            "GOOG": (Decimal("705.46"), Decimal("705.46")),
            "AMZN": (Decimal("564.14"), Decimal("565.14")),
        }
        return prices[ticker]

The second mock object is PositionSizerMock. It simply sets the order quantity to be 100, which is an arbitrary choice, but is necessary to be fixed for our unit tests. It simulates the size_order method that will be found on the "real" PositionSizer class when it is complete:

class PositionSizerMock(object):
    def __init__(self):
        pass

    def size_order(self, portfolio, initial_order):
        """
        This PositionSizerMock object simply modifies
        the quantity to be 100 of any share transacted.
        """
        initial_order.quantity = 100
        return initial_order

The final mock object is RiskManagerMock. It doesn't do anything beyond creating an OrderEvent object and placing this in a list. Crucially, there is no real risk management! While this may seem contrived, it does allow us to perform a basic "sanity check" that the PortfolioHandler can simply transact the most basic of orders, fills and signals. As we create more sophisticated RiskManager objects, our unit test list will grow, in order to test the new functionality. This way we continually ensure that the codebase is working as expected:

class RiskManagerMock(object):
    def __init__(self):
        pass

    def refine_orders(self, portfolio, sized_order):
        """
        This RiskManagerMock object simply lets the
        sized order through, creates the corresponding
        OrderEvent object and adds it to a list.
        """
        order_event = OrderEvent(
            sized_order.ticker,
            sized_order.action,
            sized_order.quantity
        )
        return [order_event]

Now that we have the three mock objects defined, we can create the unit tests themselves. The class that carries this out is called TestSimpleSignalOrderFillCycleForPortfolioHandler. While verbose, it does tell us exactly what the test class is designed to do, namely test a simple signal-order-fill cycle within the portfolio handler.

In order to do this, we create an initial cash balance of 500,000 USD, an events queue and the three aforementioned mock objects. Finally, we create the PortfolioHandler itself and attach it to the test class:

class TestSimpleSignalOrderFillCycleForPortfolioHandler(unittest.TestCase):
    """
    Tests a simple Signal, Order and Fill cycle for the
    PortfolioHandler. This is, in effect, a sanity check.
    """
    def setUp(self):
        """
        Set up the PortfolioHandler object supplying it with
        $500,000.00 USD in initial cash.
        """
        initial_cash = Decimal("500000.00")
        events_queue = queue.Queue()
        price_handler = PriceHandlerMock()
        position_sizer = PositionSizerMock()
        risk_manager = RiskManagerMock()
        # Create the PortfolioHandler object from the rest
        self.portfolio_handler = PortfolioHandler(
            initial_cash, events_queue, price_handler, 
            position_sizer, risk_manager
        )

The first test simply generates a fake SignalEvent to buy Microsoft. We then test that the correct order has been generated. Note that a quantity has not been set at this stage (it is zero). We check for all properties to ensure that the order has been created correctly:

# test_portfolio_handler.py    

    def test_create_order_from_signal_basic_check(self):
        """
        Tests the "_create_order_from_signal" method 
        as a basic sanity check.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        order = self.portfolio_handler._create_order_from_signal(signal_event)
        self.assertEqual(order.ticker, "MSFT")
        self.assertEqual(order.action, "BOT")
        self.assertEqual(order.quantity, 0)

The next test is checking to see whether the orders are correctly placed on the queue (and retrieved). Note that we have to wrap the OrderEvent into a list, as the RiskManager produces a list of orders, due to the aforementioned need to possibly hedge or add additional orders beyond those suggested by the Strategy. Finally, we assert that the returned order (which is taken from the queue) contains the appropriate information:

# test_portfolio_handler.py    

    def test_place_orders_onto_queue_basic_check(self):
        """
        Tests the "_place_orders_onto_queue" method 
        as a basic sanity check.
        """
        order = OrderEvent("MSFT", "BOT", 100)
        order_list = [order]
        self.portfolio_handler._place_orders_onto_queue(order_list)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)

The following test creates a FillEvent, as if one had just been received from an ExecutionHandler object. The portfolio handler is then told to convert the fill to an actual portfolio update (i.e. register the transaction with the inner Portfolio object).

The test is to actually check that the current cash balance within the inner Portfolio is actually correct:

# test_portfolio_handler.py    

    def test_convert_fill_to_portfolio_update_basic_check(self):
        """
        Tests the "_convert_fill_to_portfolio_update" method
        as a basic sanity check.
        """
        fill_event_buy = FillEvent(
            datetime.datetime.utcnow(), "MSFT", "BOT",
            100, "ARCA", Decimal("50.25"), Decimal("1.00")
        )
        self.portfolio_handler._convert_fill_to_portfolio_update(fill_event_buy)

        # Check the Portfolio values within the PortfolioHandler
        port = self.portfolio_handler.portfolio
        self.assertEqual(port.cur_cash, Decimal("494974.00"))

The final test simply tests the on_signal method by creating a SignalEvent object, placing it on the queue and then retrieving it to check that the order values are as expected. This tests the "end to end" basic handling of the PositionSizer and RiskManager objects:

# test_portfolio_handler.py    

    def test_on_signal_basic_check(self):
        """
        Tests the "on_signal" method as a basic sanity check.
        """
        signal_event = SignalEvent("MSFT", "BOT")
        self.portfolio_handler.on_signal(signal_event)
        ret_order = self.portfolio_handler.events_queue.get()
        self.assertEqual(ret_order.ticker, "MSFT")
        self.assertEqual(ret_order.action, "BOT")
        self.assertEqual(ret_order.quantity, 100)

We can clearly see that there is a lot more testing to be done here. We've only scratched the surface with the sort of situations that can occur. However, it is always good to have a basic set of sanity checks in place. The unit testing framework is highly extensible and as we come across new situations/bugs, we can simply write new tests and correct for the problem.

For completeness you can find the full code for the testing of PortfolioHandler on Github at portfolio_handler_test.py.

Next Steps

We've now covered three of the main objects for the Order Management System, namely the Position, the Portfolio and the PortfolioHandler. These are the "core" mathematical calculation aspects of the code and as such we need to be sure that they work as expected.

While discussing these objects is not as exciting as building a Strategy object, or even a RiskManager, it is vital that they work, otherwise the rest of the backtesting and live trading infrastructure will be, at best, useless and at worst, highly unprofitable!

We've got a lot of remaining componets to cover in addition to those mentioned above, including the PriceHandler, the Backtest class, the various ExecutionHandlers that might tie into Interactive Brokers or OANDA, as well as the implementation of a non-trivial Strategy object.

In the next article I will discuss one or more of these classes.