Tuesday, October 6, 2009

TDD in Python: P&L Statement - Step 2

As a continuation of my TDD experiment, I've implemented a bit more functionality toward a P&L statement. As usual, I've posted all the code to github. This exercise implements the queueing I listed as a need in my first post on this subject. So, for a "story problem":

Eddie buys 1000 shares of AAPL at 10:30am for 185.25 and later, at 11:00am he buys another 1500 shares for 184.00. As he unrolls his holdings, Eddie sells 1500 shares for 186.00. What is Eddie's realized gain/loss? What's the cost basis for Eddie's current holdings?

For this post, I'd like to take a first step in solving my story problem by defining my queue a bit better. The real answer to the problem above is "it depends on whether you use fifo or lifo queueing." I've conceived a Holding object to serve as my queue for any positions I've entered for a particular instrument (as uniquely identified by its symbol). So, let's define the behavior we expect out of a fifo-ordered Holding queue in the form of a test:

def portfolio_holding_fifo_test():
""" test of 'fifo' and 'lifo' queuing by adding and removing
a position.
"""
p1 = position.Position(symbol="AAPL", qty=1000, price=185.25,
multiplier=1., fee=7.0, total_amt=185250.,
trans_date=1053605468.54)
p2 = position.Position(symbol="AAPL", qty=1500, price=184.00,
multiplier=1., fee=7.0, total_amt=276000.,
trans_date=1054202245.63)
p3 = position.Position(symbol="AAPL", qty=-1500, price=186.00,
multiplier=1., fee=7.0, total_amt=279000.,
trans_date=1055902486.22)

h = portfolio.Holding()

h.add_to(p1)
h.add_to(p2)

h.remove_from(p3, order='fifo')

assert h.qty==1000
assert len(h.positions)==1
# simple check to make sure the position that we expect is left over...
p = h.positions[0]
assert p.price==184.00

As you can see from lines 5-13 we use our earlier Position object to define the positions that we're holding or removing. I then introduce the notion of a holding object which I can add_to or remove_from. My assertions at the end of my test are some not-very-comprehensive checks to see if the queue behaves as I would expect ... the first position is drawn from when removing the final position, then any remaining shares are drawn from the second position.

Since I expect I'll need to generalize my queuing, I'll write a similar test with 'lifo' ordering:

def portfolio_holding_lifo_test():
""" test of 'lifo' queuing by adding and removing
a position.
"""
p1 = position.Position(symbol="AAPL", qty=1000, price=185.25,
multiplier=1., fee=7.0, total_amt=185250.,
trans_date=1053605468.54)
p2 = position.Position(symbol="AAPL", qty=1500, price=184.00,
multiplier=1., fee=7.0, total_amt=276000.,
trans_date=1054202245.63)
p3 = position.Position(symbol="AAPL", qty=-1500, price=186.00,
multiplier=1., fee=7.0, total_amt=279000.,
trans_date=1055902486.22)

h = portfolio.Holding()

h.add_to(p1)
h.add_to(p2)

h.remove_from(p3, order='lifo')

assert h.qty==1000
assert len(h.positions)==1
# simple check to make sure the position that we expect is left over...
p = h.positions[0]
assert p.price==185.25

So, I've written my tests first -- now for the code. I create my Holding class and put it in a file called portfolio.py. Setting up the class and creating my add_to method was a cinch:

# Enthought imports
from enthought.traits.api import (HasTraits, Float, Instance, List)

# Local imports
from position import Position

class Holding(HasTraits):
""" Queue for held positions in the same security (as identified
by symbol). The removal of entries are handled in a 'fifo'
or a 'lifo' order, depending on the order argument of the
remove_from method.
"""

# Total quantity for a particular holding
qty = Float

# List of positions making up the holding
positions = List(Instance(Position))

def add_to(self, position):
self.qty += position.qty
self.positions.append(position)

As you can see, I've again used Traits to make my life easier. In this case I've got a simple qty Float value which I intend to use for ease in checking the total quantity held in my Holding object (as well as a checksum). I also have defined a List of positions as another Trait of my class. From there, the add_to method is pretty much the obvious implementation.

The hard part is in the remove_from, which actually implements the various behavior of the queue ordering. I've punted (for now) on my original goal of a 'wifo' (worst-in-first-out) queue order, as I suspect I'll need to refactor my position class to handle different sorting. The important bits of the code are in the handling of a full position, a partial position, or multiple positions depending of the quantity to be removed. Rather than reproducing all the code here, I'll just provide the link. I use recursion in the case of multiple removals, otherwise it's pretty easy to follow.

This was a fairly simple step, yet I feel much closer to my portfolio/statement goals -- and I feel like I can "turn my back" to some extent on the code I've written so far. That is, it is reasonably tested code that meets the api I've defined by my tests.

Some TDD observations:
1. It is getting a little easier to think in terms of tests first as I've done this second exercise.
2. I did write some additional tests to try to get at the edges of the functionality I was looking for, and to provide more comprehensive regression coverage.
3. Small tests force you into a good habit of writing small chunks of code that work, heightening the impression of progress.

Next up: 1) aggregating the holdings into a real portfolio and 2) introducing a method of logging activity and reporting performance in the form of a statement.

As always, comments or corrections are welcome!