Advanced Trading Infrastructure - Position Class

At the end of last year I announced that I would be working on a series of articles regarding the development of an Advanced Trading Infrastructure. Since the initial announcement I haven't mentioned the project to any great extent. However, in this article I want to discuss the progress I've made to date.

At the outset I decided that I wanted to re-use and improve upon as much of the codebases of the event-driven backtester and QSForex as possible in order to avoid duplicated effort. Despite a solid attempt to make use of large parts of these two codebases, I came to the conclusion that it was necessary to "start from scratch" with how the position handling/sizing, risk management and portfolio construction was handled.

In effect I've modified the design of QSForex and the event-driven backtesters to include proper integration of position sizing and risk management layers, which were not present in previous versions. These two components provide robust portfolio construction tools that aid in crafting "professional" portfolios, rather than those found in a simplistic vectorised backtester.

At this stage I have not actually written any specific risk management or position sizing logic. Since this logic is highly specific to an individual or firm, I have decided that I will provide example modules for straightforward situations such as Kelly Criterion positioning or volatility risk levels, but will leave complicated risk and position management overlays to be developed by users of the software. This allows you to use the "off the shelf" versions that I develop, but also to completely swap out these components with your own custom logic as you see fit. This way the software is not forcing you down a particular risk management methodology.

To date I have coded the basics of a portfolio management system. The entire backtesting and trading tool, which I am tentatively naming **QSTrader**, is far from being production-ready. In fact, I would go so far as to say it is in a "pre-alpha" state at this stage! Despite the fact that the system is early in its development, I have put significant effort into unit-testing the early components, as well as testing against external brokerage-calculated values. I am currently quite confident in the position handling mechanism. However, if edge cases do arrive, they can simply be added as unit tests to further improve robustness.

The project is now available (in an extremely early state!) at https://www.github.com/mhallsmoore/qstrader.git under a liberal open-source MIT license. At this stage it lacks documentation, so I am only adding the link for those who wish to browse the code as it stands.

In the previous article in the series on Advanced Trading Infrastructure I discussed the broad roadmap for development. In this article I want to discuss one of the most important aspects of the system, namely the `Position`

component, that will form the basis of the `Portfolio`

and subsequently `PortfolioHandler`

.

When designing such a system it is necessary to consider how we will break down separate behaviours into various submodules. Not only does this prevent strong coupling of the components, but it also allows much more straightforward testing, as each component can be unit tested separately.

The design I have chosen for the portfolio management infrastructure consists of the following components:

**Position**- This class encapsulates all data associated with an open position in an asset. That is, it tracks the realised and unrealised profit and loss (PnL) by averaging the multiple "legs" of the transaction, inclusive of transaction costs.**Portfolio**- The Portfolio class that encapsulates a list of Positions, as well as a cash balance, equity and PnL.**PositionSizer**- The PositionSizer class provides the PortfolioHandler (see below) with guidance on how to size positions once a strategy signal is received. For instance, the PositionSizer could incorporate a Kelly Criterion approach.**RiskManager**- The RiskManager is used by the PortfolioHandler to verify, modify or veto any suggested trades that pass through from the PositionSizer, based on the current composition of the portfolio and external risk considerations (such as correlation to indices or volatility).**PortfolioHandler**- The PortfolioHandler class is responsible for the management of the current Portfolio, interacting with the RiskManager and PositionSizer as well as submitting orders to be executed by an execution handler.

Note that this component organisation is somewhat different to how the backtesting system operates in QSForex. In that case the `Portfolio`

object is the equivalent of the `PortfolioHandler`

class above. I've split these into two here. This allows much more straightforward risk management and position sizing with the `RiskManager`

and `PositionSizer`

classes.

We will now turn our attention to the `Position`

class. In later articles we will look at the `Portfolio`

, `PortfolioHandler`

and the risk/position sizing components.

The `Position`

class is very similar to its namesake in the QSForex project, with the exception that it has initially been designed for use with equity, rather than forex, instruments. Hence there is no notion of a "base" or "quote" currency. However, we retain the ability to update the unrealised PnL on demand, via updating of the current bid and ask prices quoted on the market.

I'll output the code listing in full and then run through how it works.

*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.*

from decimal import Decimal TWOPLACES = Decimal("0.01") FIVEPLACES = Decimal("0.00001") class Position(object): def __init__( self, action, ticker, init_quantity, init_price, init_commission, bid, ask ): """ Set up the initial "account" of the Position to be zero for most items, with the exception of the initial purchase/sale. Then calculate the initial values and finally update the market value of the transaction. """ self.action = action self.ticker = ticker self.quantity = init_quantity self.init_price = init_price self.init_commission = init_commission self.realised_pnl = Decimal("0.00") self.unrealised_pnl = Decimal("0.00") self.buys = Decimal("0") self.sells = Decimal("0") self.avg_bot = Decimal("0.00") self.avg_sld = Decimal("0.00") self.total_bot = Decimal("0.00") self.total_sld = Decimal("0.00") self.total_commission = init_commission self._calculate_initial_value() self.update_market_value(bid, ask) def _calculate_initial_value(self): """ Depending upon whether the action was a buy or sell ("BOT" or "SLD") calculate the average bought cost, the total bought cost, the average price and the cost basis. Finally, calculate the net total with and without commission. """ if self.action == "BOT": self.buys = self.quantity self.avg_bot = self.init_price.quantize(FIVEPLACES) self.total_bot = (self.buys * self.avg_bot).quantize(TWOPLACES) self.avg_price = ( (self.init_price * self.quantity + self.init_commission)/self.quantity ).quantize(FIVEPLACES) self.cost_basis = ( self.quantity * self.avg_price ).quantize(TWOPLACES) else: # action == "SLD" self.sells = self.quantity self.avg_sld = self.init_price.quantize(FIVEPLACES) self.total_sld = (self.sells * self.avg_sld).quantize(TWOPLACES) self.avg_price = ( (self.init_price * self.quantity - self.init_commission)/self.quantity ).quantize(FIVEPLACES) self.cost_basis = ( -self.quantity * self.avg_price ).quantize(TWOPLACES) self.net = self.buys - self.sells self.net_total = (self.total_sld - self.total_bot).quantize(TWOPLACES) self.net_incl_comm = (self.net_total - self.init_commission).quantize(TWOPLACES) def update_market_value(self, bid, ask): """ The market value is tricky to calculate as we only have access to the top of the order book through Interactive Brokers, which means that the true redemption price is unknown until executed. However, it can be estimated via the mid-price of the bid-ask spread. Once the market value is calculated it allows calculation of the unrealised and realised profit and loss of any transactions. """ midpoint = (bid+ask)/Decimal("2.0") self.market_value = ( self.quantity * midpoint ).quantize(TWOPLACES) self.unrealised_pnl = ( self.market_value - self.cost_basis ).quantize(TWOPLACES) self.realised_pnl = ( self.market_value + self.net_incl_comm ) def transact_shares(self, action, quantity, price, commission): """ Calculates the adjustments to the Position that occur once new shares are bought and sold. Takes care to update the average bought/sold, total bought/sold, the cost basis and PnL calculations, as carried out through Interactive Brokers TWS. """ prev_quantity = self.quantity prev_commission = self.total_commission self.total_commission += commission # Adjust total bought and sold if action == "BOT": self.avg_bot = ( (self.avg_bot*self.buys + price*quantity)/(self.buys + quantity) ).quantize(FIVEPLACES) if self.action != "SLD": self.avg_price = ( ( self.avg_price*self.buys + price*quantity+commission )/(self.buys + quantity) ).quantize(FIVEPLACES) self.buys += quantity self.total_bot = (self.buys * self.avg_bot).quantize(TWOPLACES) # action == "SLD" else: self.avg_sld = ( (self.avg_sld*self.sells + price*quantity)/(self.sells + quantity) ).quantize(FIVEPLACES) if self.action != "BOT": self.avg_price = ( ( self.avg_price*self.sells + price*quantity-commission )/(self.sells + quantity) ).quantize(FIVEPLACES) self.sells += quantity self.total_sld = (self.sells * self.avg_sld).quantize(TWOPLACES) # Adjust net values, including commissions self.net = self.buys - self.sells self.quantity = self.net self.net_total = ( self.total_sld - self.total_bot ).quantize(TWOPLACES) self.net_incl_comm = ( self.net_total - self.total_commission ).quantize(TWOPLACES) # Adjust average price and cost basis self.cost_basis = ( self.quantity * self.avg_price ).quantize(TWOPLACES)

You'll notice that the entire project makes extensive use of the decimal module. This is an absolute necessity in financial applications, otherwise you end up causing significant rounding errors due to the mathematics of floating point handling.

I've created two helper variables, `TWOPLACES`

and `FIVEPLACES`

, which are used subsequently to define the level of precision necessary for rounding in calculations.

TWOPLACES = Decimal("0.01") FIVEPLACES = Decimal("0.00001")

The `Position`

class requires a transaction action: "Buy" or "Sell". I've used the Interactive Brokers code **BOT** and **SLD** for these throughout the code. In addition, the `Position`

requires a ticker symbol, a quantity to transact, the price of purchase or sale and the commission.

class Position(object): def __init__( self, action, ticker, init_quantity, init_price, init_commission, bid, ask ): """ Set up the initial "account" of the Position to be zero for most items, with the exception of the initial purchase/sale. Then calculate the initial values and finally update the market value of the transaction. """ self.action = action self.ticker = ticker self.quantity = init_quantity self.init_price = init_price self.init_commission = init_commission

The `Position`

also keeps track of a few other metrics, which fully mirror those handled by Interactive Brokers. It tracks the PnL, the quantity of buys and sells, the average purchase price and the average sale price, the total purchase price and the total sale price, as well as the total commission spent to date.

self.realised_pnl = Decimal("0.00") self.unrealised_pnl = Decimal("0.00") self.buys = Decimal("0") self.sells = Decimal("0") self.avg_bot = Decimal("0.00") self.avg_sld = Decimal("0.00") self.total_bot = Decimal("0.00") self.total_sld = Decimal("0.00") self.total_commission = init_commission

The `Position`

class has been structured this way because it is very difficult to pin down the idea of a "trade". For instance, let's imagine we carry out the following transactions:

**Day 1**- Purchase 100 shares of GOOG. Total 100.**Day 2**- Purchase 200 shares of GOOG. Total 300.**Day 3**- Sell 400 shares of GOOG. Total -100.**Day 4**- Purchase 200 shares of GOOG. Total 100.**Day 5**- Sell 100 shares of GOOG. Total 0.

This constitutes a "round trip". How are we to determine the profit on this? Do we work out the profit on every leg of the transaction? Do we only calculate profit once the quantity nets out back to zero?

These problems are solved by using a realised and unrealised PnL. We will always know how much we have made *to date* and how much we *might make* by keeping track of these two values. At the `Portfolio`

level we can simply total these values up and work out our total PnL at any time.

Finally, in the initialisation method `__init__`

, we calculate the initial values and update the market value by the latest bid/ask:

self._calculate_initial_value() self.update_market_value(bid, ask)

The `_calculate_initial_value`

method is as follows:

def _calculate_initial_value(self): """ Depending upon whether the action was a buy or sell ("BOT" or "SLD") calculate the average bought cost, the total bought cost, the average price and the cost basis. Finally, calculate the net total with and without commission. """ if self.action == "BOT": self.buys = self.quantity self.avg_bot = self.init_price.quantize(FIVEPLACES) self.total_bot = (self.buys * self.avg_bot).quantize(TWOPLACES) self.avg_price = ( (self.init_price * self.quantity + self.init_commission)/self.quantity ).quantize(FIVEPLACES) self.cost_basis = ( self.quantity * self.avg_price ).quantize(TWOPLACES) else: # action == "SLD" self.sells = self.quantity self.avg_sld = self.init_price.quantize(FIVEPLACES) self.total_sld = (self.sells * self.avg_sld).quantize(TWOPLACES) self.avg_price = ( (self.init_price * self.quantity - self.init_commission)/self.quantity ).quantize(FIVEPLACES) self.cost_basis = ( -self.quantity * self.avg_price ).quantize(TWOPLACES) self.net = self.buys - self.sells self.net_total = (self.total_sld - self.total_bot).quantize(TWOPLACES) self.net_incl_comm = (self.net_total - self.init_commission).quantize(TWOPLACES)

This method carries out the initial calculations upon opening a new position. For "BOT" actions (purchases), the number of buys is incremented, the average and total bought values are calculated, as is the average *price* of the position. The *cost basis* is calculated as the current quantity multiplied by the average price paid. Hence it is the "total price paid so far". In addition, the net quantity, net total and net including commission are also calculated.

The next method is `update_market_value`

. This is a tricky method to implement as it relies on a particular choice for how "market value" is calculated. There is no correct choice for this, as there is no such thing as "market value". There are some useful choices though:

**Midpoint**- A common choice is to take the mid-point of the bid-ask spread. This takes the top bid and ask prices of the order book (for a particular exchange) and averages them.**Last Traded Price**- This is the last price that a stock traded at. Calculating this is tricky because the trade may have occurred across multiple prices (different lots at different prices). Hence it could be a weighted average.**Bid or Ask**- Depending upon the side of the transaction (i.e. a purchase or sale), the top bid or ask price could be used as an indication.

None of these prices are likely to be the one achieved in practice, however. The order book dynamics, slippage and market impact are all going to cause the true sale price to differ from the currently quoted bid/ask.

In the following method I have opted for the midpoint calculation in order to provide some sense of "market value". It is important to stress, however, that this value can find its way into the risk management and position sizing calculations, so it is necessary to make sure that you are happy with how it is calculated and modify it accordingly if you wish to use it in your own trading engines.

Once the market value is calculated it allows subsequent calculation of the unrealised and realised PnL of the position:

def update_market_value(self, bid, ask): """ The market value is tricky to calculate as we only have access to the top of the order book through Interactive Brokers, which means that the true redemption price is unknown until executed. However, it can be estimated via the mid-price of the bid-ask spread. Once the market value is calculated it allows calculation of the unrealised and realised profit and loss of any transactions. """ midpoint = (bid+ask)/Decimal("2.0") self.market_value = ( self.quantity * midpoint ).quantize(TWOPLACES) self.unrealised_pnl = ( self.market_value - self.cost_basis ).quantize(TWOPLACES) self.realised_pnl = ( self.market_value + self.net_incl_comm )

The final method is `transact_shares`

. This is the method called by the `Portfolio`

class to actually carry out a transaction. I won't repeat the method in full here, as it can be found above and at Github, but I will concentrate on some important sections.

In the code snippet below you can see that if the action is a purchase ("BOT") then the average buy price is recalculated. If the original action was also a purchase, the average price is necessarily modified. The total buys are increased by the new quantity and the total purchase price is modified. The logic is similar for the sell/"SLD" side:

if action == "BOT": self.avg_bot = ( (self.avg_bot*self.buys + price*quantity)/(self.buys + quantity) ).quantize(FIVEPLACES) if self.action != "SLD": self.avg_price = ( ( self.avg_price*self.buys + price*quantity+commission )/(self.buys + quantity) ).quantize(FIVEPLACES) self.buys += quantity self.total_bot = (self.buys * self.avg_bot).quantize(TWOPLACES)

Finally, the net values are all adjusted. The calculations are relatively straightforward and can be followed from the snippet. Notice that they are all rounded to two decimal places:

# Adjust net values, including commissions self.net = self.buys - self.sells self.quantity = self.net self.net_total = ( self.total_sld - self.total_bot ).quantize(TWOPLACES) self.net_incl_comm = ( self.net_total - self.total_commission ).quantize(TWOPLACES) # Adjust average price and cost basis self.cost_basis = ( self.quantity * self.avg_price ).quantize(TWOPLACES)

That concludes the `Position`

class. It provides a robust mechanism for handling position calculation and storage.

For completeness you can find the full code for the `Position`

class on Github at position.py.

In order to test the calculations within the `Position`

class I've written the following unit tests, checking them against similar transactions carried out within Interactive Brokers. I do fully anticipate finding new edge cases and bugs that will need error correction, but these current unit tests will provide strong confidence in the results going forward.

The full listing of `position_test.py`

is as follows:

from decimal import Decimal import unittest from qstrader.position.position import Position class TestRoundTripXOMPosition(unittest.TestCase): """ Test a round-trip trade in Exxon-Mobil where the initial trade is a buy/long of 100 shares of XOM, at a price of $74.78, with $1.00 commission. """ def setUp(self): """ Set up the Position object that will store the PnL. """ self.position = Position( "BOT", "XOM", Decimal('100'), Decimal("74.78"), Decimal("1.00"), Decimal('74.78'), Decimal('74.80') ) def test_calculate_round_trip(self): """ After the subsequent purchase, carry out two more buys/longs and then close the position out with two additional sells/shorts. The following prices have been tested against those calculated via Interactive Brokers' Trader Workstation (TWS). """ self.position.transact_shares( "BOT", Decimal('100'), Decimal('74.63'), Decimal('1.00') ) self.position.transact_shares( "BOT", Decimal('250'), Decimal('74.620'), Decimal('1.25') ) self.position.transact_shares( "SLD", Decimal('200'), Decimal('74.58'), Decimal('1.00') ) self.position.transact_shares( "SLD", Decimal('250'), Decimal('75.26'), Decimal('1.25') ) self.position.update_market_value(Decimal("77.75"), Decimal("77.77")) self.assertEqual(self.position.action, "BOT") self.assertEqual(self.position.ticker, "XOM") self.assertEqual(self.position.quantity, Decimal("0")) self.assertEqual(self.position.buys, Decimal("450")) self.assertEqual(self.position.sells, Decimal("450")) self.assertEqual(self.position.net, Decimal("0")) self.assertEqual(self.position.avg_bot, Decimal("74.65778")) self.assertEqual(self.position.avg_sld, Decimal("74.95778")) self.assertEqual(self.position.total_bot, Decimal("33596.00")) self.assertEqual(self.position.total_sld, Decimal("33731.00")) self.assertEqual(self.position.net_total, Decimal("135.00")) self.assertEqual(self.position.total_commission, Decimal("5.50")) self.assertEqual(self.position.net_incl_comm, Decimal("129.50")) self.assertEqual(self.position.avg_price, Decimal("74.665")) self.assertEqual(self.position.cost_basis, Decimal("0.00")) self.assertEqual(self.position.market_value, Decimal("0.00")) self.assertEqual(self.position.unrealised_pnl, Decimal("0.00")) self.assertEqual(self.position.realised_pnl, Decimal("129.50")) class TestRoundTripPGPosition(unittest.TestCase): """ Test a round-trip trade in Proctor & Gamble where the initial trade is a sell/short of 100 shares of PG, at a price of $77.69, with $1.00 commission. """ def setUp(self): self.position = Position( "SLD", "PG", Decimal('100'), Decimal("77.69"), Decimal("1.00"), Decimal('77.68'), Decimal('77.70') ) def test_calculate_round_trip(self): """ After the subsequent sale, carry out two more sells/shorts and then close the position out with two additional buys/longs. The following prices have been tested against those calculated via Interactive Brokers' Trader Workstation (TWS). """ self.position.transact_shares( "SLD", Decimal('100'), Decimal('77.68'), Decimal('1.00') ) self.position.transact_shares( "SLD", Decimal('50'), Decimal('77.70'), Decimal('1.00') ) self.position.transact_shares( "BOT", Decimal('100'), Decimal('77.77'), Decimal('1.00') ) self.position.transact_shares( "BOT", Decimal('150'), Decimal('77.73'), Decimal('1.00') ) self.position.update_market_value(Decimal("77.72"), Decimal("77.72")) self.assertEqual(self.position.action, "SLD") self.assertEqual(self.position.ticker, "PG") self.assertEqual(self.position.quantity, Decimal("0")) self.assertEqual(self.position.buys, Decimal("250")) self.assertEqual(self.position.sells, Decimal("250")) self.assertEqual(self.position.net, Decimal("0")) self.assertEqual(self.position.avg_bot, Decimal("77.746")) self.assertEqual(self.position.avg_sld, Decimal("77.688")) self.assertEqual(self.position.total_bot, Decimal("19436.50")) self.assertEqual(self.position.total_sld, Decimal("19422.00")) self.assertEqual(self.position.net_total, Decimal("-14.50")) self.assertEqual(self.position.total_commission, Decimal("5.00")) self.assertEqual(self.position.net_incl_comm, Decimal("-19.50")) self.assertEqual(self.position.avg_price, Decimal("77.67600")) self.assertEqual(self.position.cost_basis, Decimal("0.00")) self.assertEqual(self.position.market_value, Decimal("0.00")) self.assertEqual(self.position.unrealised_pnl, Decimal("0.00")) self.assertEqual(self.position.realised_pnl, Decimal("-19.50")) if __name__ == "__main__": unittest.main()

The imports for this module are straightforward. We once again import the `Decimal`

class, but also add the unittest module as well as the `Position`

class itself, since it is being tested:

from decimal import Decimal import unittest from qstrader.position.position import Position

For those who have not yet seen a Python unit test, the basic idea is to create a class called `TestXXXX`

that inherits from `unittest.TestCase`

as in the subsequent snippet. The class exposes a `setUp`

method that allows any data or state to be utilised for the remainder of *that particular test*. Here's an example unit test setup of a "round trip" trade for Exxon-Mobil/XOM:

class TestRoundTripXOMPosition(unittest.TestCase): """ Test a round-trip trade in Exxon-Mobil where the initial trade is a buy/long of 100 shares of XOM, at a price of $74.78, with $1.00 commission. """ def setUp(self): """ Set up the Position object that will store the PnL. """ self.position = Position( "BOT", "XOM", Decimal('100'), Decimal("74.78"), Decimal("1.00"), Decimal('74.78'), Decimal('74.80') )

Notice that `self.position`

is set to be a new `Position`

class, where 100 shares of XOM are purchased for 74.78 USD.

Subsequent methods, in the format of `test_XXXX`

, allow unit testing of various aspects of the system. In this particular method, after the initial purchase, two more long purchases are made and finally two sales are made to net the position out to zero:

def test_calculate_round_trip(self): """ After the subsequent purchase, carry out two more buys/longs and then close the position out with two additional sells/shorts. The following prices have been tested against those calculated via Interactive Brokers' Trader Workstation (TWS). """ self.position.transact_shares( "BOT", Decimal('100'), Decimal('74.63'), Decimal('1.00') ) self.position.transact_shares( "BOT", Decimal('250'), Decimal('74.620'), Decimal('1.25') ) self.position.transact_shares( "SLD", Decimal('200'), Decimal('74.58'), Decimal('1.00') ) self.position.transact_shares( "SLD", Decimal('250'), Decimal('75.26'), Decimal('1.25') ) self.position.update_market_value(Decimal("77.75"), Decimal("77.77"))

`transact_shares`

is called four times and finally the market value is updated by `update_market_value`

. At this point `self.position`

stores all of the various calculations and is ready to be tested, using the `assertEqual`

method derived from the `unittest.TestCase`

class. Notice how all of the various properties of the `Position`

class are tested against externally calculated (in this case by Interactive Brokers TWS) values:

self.assertEqual(self.position.action, "BOT") self.assertEqual(self.position.ticker, "XOM") self.assertEqual(self.position.quantity, Decimal("0")) self.assertEqual(self.position.buys, Decimal("450")) self.assertEqual(self.position.sells, Decimal("450")) self.assertEqual(self.position.net, Decimal("0")) self.assertEqual(self.position.avg_bot, Decimal("74.65778")) self.assertEqual(self.position.avg_sld, Decimal("74.95778")) self.assertEqual(self.position.total_bot, Decimal("33596.00")) self.assertEqual(self.position.total_sld, Decimal("33731.00")) self.assertEqual(self.position.net_total, Decimal("135.00")) self.assertEqual(self.position.total_commission, Decimal("5.50")) self.assertEqual(self.position.net_incl_comm, Decimal("129.50")) self.assertEqual(self.position.avg_price, Decimal("74.665")) self.assertEqual(self.position.cost_basis, Decimal("0.00")) self.assertEqual(self.position.market_value, Decimal("0.00")) self.assertEqual(self.position.unrealised_pnl, Decimal("0.00")) self.assertEqual(self.position.realised_pnl, Decimal("129.50"))

When I execute the correct Python binary from within my virtual environment (under the command line in Ubuntu), I receive the following output:

(qstrader)mhallsmoore@desktop:~/sites/qstrader/approot/position$ python position_test.py .. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK

Note that there are *two* tests within the full file, so take a look at the second test in the full listing in order to familiarise yourself with the calculations.

Clearly there are a lot more tests that could be carried out on the `Position`

class. At this stage we can be reassured that it handles the basic functionality of holding an equity position. In time, the class will likely be expanded to cope with forex, futures and options instruments, allowing a sophisticated investment strategy to be carried out within a portfolio.

The full listing itself can be found at position_test.py on Github.

In the following articles we are going to look at the `Portfolio`

and `PortfolioHandler`

classes. Both of these are currently coded up and unit tested in a basic manner. If you wish to "jump ahead" and see the code, you can take a look at the full QSTrader repository here: https://github.com/mhallsmoore/qstrader.

You'll get instant access to a free 10-part email course packed with hints and tips to help you get started in quantitative trading!

Every week I'll send you a wrap of all activity on QuantStart so you'll never miss a post again.

Real, actionable quant trading tips with no nonsense.