From 1ddfadf5b48876cdcc0ca170786ccdddc0b57fec Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Fri, 11 Jan 2013 23:30:43 -0500 Subject: [PATCH 1/2] Recycles the portfolio container to be passed to handle_data. The creation of a new portfolio ndict on each call of handle_data was creating a very high performance overhead. Instead, we use the same the portfolio object for each event, and replace the values contained within. --- zipline/finance/performance.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 8d0325de..3543f7ed 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -139,7 +139,6 @@ import math from zipline.utils.protocol_utils import ndict import zipline.protocol as zp import zipline.finance.risk as risk -from zipline.protocol import Portfolio log = logbook.Logger('Performance') @@ -443,6 +442,11 @@ class PerformancePeriod(object): self.calculate_performance() + # An object to recycle via assigning new values + # when returning portfolio information. + # So as not to avoid creating a new object for each event + self._portfolio_store = zp.Portfolio() + def calculate_performance(self): self.ending_value = self.calculate_positions_value() @@ -544,17 +548,21 @@ class PerformancePeriod(object): PerformancePeriod, and in this method we rename some fields for usability and remove extraneous fields. """ - return Portfolio({ - 'capital_used': self.period_capital_used, - 'starting_cash': self.starting_cash, - 'portfolio_value': self.ending_cash + self.ending_value, - 'pnl': self.pnl, - 'returns': self.returns, - 'cash': self.ending_cash, - 'start_date': self.period_open, - 'positions': self.get_positions(), - 'positions_value': self.ending_value - }) + # Recycles containing objects' Portfolio object + # which is used for returning values. + # as_portfolio is called in an inner loop, + # so repeated object creation becomes too expensive + portfolio = self._portfolio_store + portfolio.capital_used = self.period_capital_used, + portfolio.starting_cash = self.starting_cash + portfolio.portfolio_value = self.ending_cash + self.ending_value + portfolio.pnl = self.pnl + portfolio.returns = self.returns + portfolio.cash = self.ending_cash + portfolio.start_date = self.period_open + portfolio.positions = self.get_positions() + portfolio.positions_value = self.ending_value + return portfolio def get_positions(self): From 34d577d3d7e6f3b854ddb54b525fab684664925a Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Fri, 11 Jan 2013 23:54:52 -0500 Subject: [PATCH 2/2] Recycles objects for positions. Instead of creating a new ndict for each position on every event, we change the values in the object that held the previous position. The creation of new objects on each event was incurring too much overhead. Changes the position type returned by performance module. For improved speed, changes from ndict to a simple Python object, since the cost of setting ndict values is too expensive for the number of times that positions are returned. Also, changes the containing type of the positions to be dictionary with the __missing__ overloaded, instead of the ndict that had that behavior, to reduce the penalty of using ndicts. --- zipline/finance/performance.py | 20 ++++++++------------ zipline/protocol.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 3543f7ed..78130c05 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -136,7 +136,6 @@ import datetime import pytz import math -from zipline.utils.protocol_utils import ndict import zipline.protocol as zp import zipline.finance.risk as risk @@ -446,6 +445,7 @@ class PerformancePeriod(object): # when returning portfolio information. # So as not to avoid creating a new object for each event self._portfolio_store = zp.Portfolio() + self._positions_store = zp.Positions() def calculate_performance(self): self.ending_value = self.calculate_positions_value() @@ -566,11 +566,15 @@ class PerformancePeriod(object): def get_positions(self): - positions = ndict(internal=position_ndict()) + positions = self._positions_store for sid, pos in self.positions.iteritems(): - cur = pos.to_dict() - positions[sid] = ndict(cur) + if sid not in positions: + positions[sid] = zp.Position(sid) + position = positions[sid] + position.amount = pos.amount + position.cost_basis = pos.cost_basis + position.last_sale_price = pos.last_sale_price return positions @@ -588,11 +592,3 @@ class positiondict(dict): pos = Position(key) self[key] = pos return pos - - -class position_ndict(dict): - - def __missing__(self, key): - pos = Position(key) - self[key] = ndict(pos.to_dict()) - return pos diff --git a/zipline/protocol.py b/zipline/protocol.py index 9e71d3ae..25f1b90b 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -65,3 +65,23 @@ class Portfolio(object): def __repr__(self): return "Portfolio({0})".format(self.__dict__) + + +class Position(object): + + def __init__(self, sid): + self.sid = sid + self.amount = 0 + self.cost_basis = 0.0 # per share + self.last_sale_price = 0.0 + + def __repr__(self): + return "Position({0})".format(self.__dict__) + + +class Positions(dict): + + def __missing__(self, key): + pos = Position(key) + self[key] = pos + return pos