diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 098e0abd..86996971 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -17,7 +17,6 @@ import collections import unittest from nose_parameterized import parameterized -import random import datetime import pytz import itertools @@ -28,55 +27,377 @@ import zipline.finance.performance as perf from zipline.utils.protocol_utils import ndict from zipline.gens.composites import date_sorted_sources - from zipline.finance.trading import TradingEnvironment +from zipline.utils.factory import create_random_trading_environment -class TestPerformance(unittest.TestCase): +onesec = datetime.timedelta(seconds=1) +oneday = datetime.timedelta(days=1) +tradingday = datetime.timedelta(hours=6, minutes=30) + + +class TestDividendPerformance(unittest.TestCase): def setUp(self): - self.onesec = datetime.timedelta(seconds=1) - self.oneday = datetime.timedelta(days=1) - self.tradingday = datetime.timedelta(hours=6, minutes=30) - self.trading_environment, self.dt, self.end_dt = self.create_env() + self.trading_environment, self.dt, self.end_dt = \ + create_random_trading_environment() - def create_env(self, start_dt=None): - benchmark_returns, treasury_curves = \ - factory.load_market_data() + self.trading_environment.capital_base = 10e3 - if not start_dt: - for n in range(100): - - random_index = random.randint( - 0, - len(treasury_curves) - ) - - start_dt = treasury_curves.keys()[random_index] - end_dt = start_dt + datetime.timedelta(days=365) - - now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) - - if end_dt <= now: - break - else: - end_dt = start_dt + datetime.timedelta(days=365) - now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) - - assert end_dt <= now, """ -failed to find a date suitable daterange after 100 attempts. please double -check treasury and benchmark data in findb, and re-run the test.""" - assert start_dt < end_dt, "start_dt must be less than end_dt" - - trading_environment = TradingEnvironment( - benchmark_returns, - treasury_curves, - period_start=start_dt, - period_end=end_dt + def test_long_position_receives_dividend(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10], + [100, 100, 100, 100, 100], + oneday, + self.trading_environment ) - return trading_environment, start_dt, end_dt + dividend = factory.create_dividend( + 1, + 10.00, + events[0].dt, + events[1].dt, + events[2].dt + ) + + events.insert(1, dividend) + txn = factory.create_txn(1, 10.0, 100, self.dt+oneday) + events[2].TRANSACTION = txn + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(results[0]['daily_perf']['period_open'], events[0].dt) + self.assertEqual( + results[-1]['daily_perf']['period_open'], + events[-1].dt + ) + + self.assertEqual(len(results), 5) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0.0, 0.0, 0.1, 0.1, 0.1]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0.0, 0.0, 0.10, 0.0, 0.0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, -1000, 1000, 0, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 0, 0]) + + def test_post_ex_long_position_receives_no_dividend(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10], + [100, 100, 100, 100, 100], + oneday, + self.trading_environment + ) + + dividend = factory.create_dividend( + 1, + 10.00, + events[0].dt, + events[1].dt, + events[2].dt + ) + + events.insert(1, dividend) + txn = factory.create_txn(1, 10.0, 100, events[3].dt) + events[3].TRANSACTION = txn + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(len(results), 5) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0, 0, 0, 0, 0]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0, 0, 0, 0, 0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, 0, -1000, 0, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual(cumulative_cash_flows, [0, 0, -1000, -1000, -1000]) + + def test_selling_before_dividend_payment_still_gets_paid(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10], + [100, 100, 100, 100, 100], + oneday, + self.trading_environment + ) + + dividend = factory.create_dividend( + 1, + 10.00, + events[0].dt, + events[1].dt, + events[3].dt + ) + + buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt) + events[1].TRANSACTION = buy_txn + sell_txn = factory.create_txn(1, 10.0, -100, events[2].dt) + events[2].TRANSACTION = sell_txn + events.insert(1, dividend) + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(len(results), 5) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0, 0, 0, 0.1, 0.1]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0, 0, 0, 0.1, 0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, -1000, 1000, 1000, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 1000, 1000]) + + def test_buy_and_sell_before_ex(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10, 10], + [100, 100, 100, 100, 100, 100], + oneday, + self.trading_environment + ) + + dividend = factory.create_dividend( + 1, + 10.00, + events[3].dt, + events[4].dt, + events[5].dt + ) + + buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt) + events[1].TRANSACTION = buy_txn + sell_txn = factory.create_txn(1, 10.0, -100, events[2].dt) + events[2].TRANSACTION = sell_txn + events.insert(1, dividend) + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(len(results), 6) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0, 0, 0, 0, 0, 0]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0, 0, 0, 0, 0, 0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, -1000, 1000, 0, 0, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 0, 0, 0]) + + def test_ending_before_pay_date(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10], + [100, 100, 100, 100, 100], + oneday, + self.trading_environment + ) + + dividend = factory.create_dividend( + 1, + 10.00, + events[0].dt, + events[1].dt, + events[-1].dt + 10*oneday + ) + + buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt) + events[1].TRANSACTION = buy_txn + events.insert(1, dividend) + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(len(results), 5) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0, 0, 0, 0.0, 0.0]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0, 0, 0, 0, 0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, -1000, 0, 0, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual( + cumulative_cash_flows, + [0, -1000, -1000, -1000, -1000] + ) + + def test_short_position_receives_no_dividend(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10], + [100, 100, 100, 100, 100], + oneday, + self.trading_environment + ) + + dividend = factory.create_dividend( + 1, + 10.00, + events[0].dt, + events[1].dt, + events[2].dt + ) + + events.insert(1, dividend) + txn = factory.create_txn(1, 10.0, -100, self.dt+oneday) + events[2].TRANSACTION = txn + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(len(results), 5) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0.0, 0.0, 0.0, 0.0, 0.0]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0.0, 0.0, 0.0, 0.0, 0.0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, 1000, 0, 0, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual(cumulative_cash_flows, [0, 1000, 1000, 1000, 1000]) + + def test_no_position_receives_no_dividend(self): + #post some trades in the market + events = factory.create_trade_history( + 1, + [10, 10, 10, 10, 10], + [100, 100, 100, 100, 100], + oneday, + self.trading_environment + ) + + dividend = factory.create_dividend( + 1, + 10.00, + events[0].dt, + events[1].dt, + events[2].dt + ) + + events.insert(1, dividend) + perf_tracker = perf.PerformanceTracker(self.trading_environment) + transformed_events = list(perf_tracker.transform( + ((event.dt, [event]) for event in events)) + ) + + #flatten the list of events + results = [] + for te in transformed_events: + for event in te[1]: + for message in event.perf_messages: + results.append(message) + + perf_messages, risk = perf_tracker.handle_simulation_end() + results.append(perf_messages[0]) + + self.assertEqual(len(results), 5) + cumulative_returns = \ + [event['cumulative_perf']['returns'] for event in results] + self.assertEqual(cumulative_returns, [0.0, 0.0, 0.0, 0.0, 0.0]) + daily_returns = [event['daily_perf']['returns'] for event in results] + self.assertEqual(daily_returns, [0.0, 0.0, 0.0, 0.0, 0.0]) + cash_flows = [event['daily_perf']['capital_used'] for event in results] + self.assertEqual(cash_flows, [0, 0, 0, 0, 0]) + cumulative_cash_flows = \ + [event['cumulative_perf']['capital_used'] for event in results] + self.assertEqual(cumulative_cash_flows, [0, 0, 0, 0, 0]) + + +class TestPositionPerformance(unittest.TestCase): + + def setUp(self): + self.trading_environment, self.dt, self.end_dt = \ + create_random_trading_environment() def test_long_position(self): """ @@ -88,11 +409,11 @@ check treasury and benchmark data in findb, and re-run the test.""" 1, [10, 10, 10, 11], [100, 100, 100, 100], - self.onesec, + onesec, self.trading_environment ) - txn = factory.create_txn(1, 10.0, 100, self.dt + self.onesec) + txn = factory.create_txn(1, 10.0, 100, self.dt + onesec) pp = perf.PerformancePeriod(1000.0) pp.execute_transaction(txn) @@ -102,7 +423,7 @@ check treasury and benchmark data in findb, and re-run the test.""" pp.calculate_performance() self.assertEqual( - pp.period_capital_used, + pp.period_cash_flow, -1 * txn.price * txn.amount, "capital used should be equal to the opposite of the transaction \ cost of sole txn in test" @@ -157,13 +478,13 @@ single short-sale transaction""" 1, [10, 10, 10, 11, 10, 9], [100, 100, 100, 100, 100, 100], - self.onesec, + onesec, self.trading_environment ) trades_1 = trades[:-2] - txn = factory.create_txn(1, 10.0, -100, self.dt + self.onesec) + txn = factory.create_txn(1, 10.0, -100, self.dt + onesec) pp = perf.PerformancePeriod(1000.0) pp.execute_transaction(txn) @@ -173,7 +494,7 @@ single short-sale transaction""" pp.calculate_performance() self.assertEqual( - pp.period_capital_used, + pp.period_cash_flow, -1 * txn.price * txn.amount, "capital used should be equal to the opposite of the transaction\ cost of sole txn in test" @@ -230,7 +551,7 @@ single short-sale transaction""" pp.calculate_performance() self.assertEqual( - pp.period_capital_used, + pp.period_cash_flow, 0, "capital used should be zero, there were no transactions in \ performance period" @@ -292,7 +613,7 @@ single short-sale transaction""" ppTotal.calculate_performance() self.assertEqual( - ppTotal.period_capital_used, + ppTotal.period_cash_flow, -1 * txn.price * txn.amount, "capital used should be equal to the opposite of the transaction \ cost of sole txn in test" @@ -347,7 +668,7 @@ trade after cover""" 1, [10, 10, 10, 11, 9, 8, 7, 8, 9, 10], [100, 100, 100, 100, 100, 100, 100, 100, 100, 100], - self.onesec, + onesec, self.trading_environment ) @@ -355,10 +676,10 @@ trade after cover""" 1, 10.0, -100, - self.dt + self.onesec + self.dt + onesec ) - cover_txn = factory.create_txn(1, 7.0, 100, self.dt + self.onesec * 6) + cover_txn = factory.create_txn(1, 7.0, 100, self.dt + onesec * 6) pp = perf.PerformancePeriod(1000.0) pp.execute_transaction(short_txn) @@ -373,7 +694,7 @@ trade after cover""" cover_txn_cost = cover_txn.price * cover_txn.amount self.assertEqual( - pp.period_capital_used, + pp.period_cash_flow, -1 * short_txn_cost - cover_txn_cost, "capital used should be equal to the net transaction costs" ) @@ -426,7 +747,7 @@ shares in position" 1, [10, 11, 11, 12], [100, 100, 100, 100], - self.onesec, + onesec, self.trading_environment ) @@ -434,7 +755,7 @@ shares in position" 1, [10, 11, 11, 12], [100, 100, 100, 100], - self.onesec, + onesec, self.trading_environment ) @@ -470,13 +791,13 @@ shares in position" 1, 10.0, -100, - self.dt + self.onesec * 4) + self.dt + onesec * 4) down_tick = factory.create_trade( 1, 10.0, 100, - trades[-1].dt + self.onesec) + trades[-1].dt + onesec) pp.rollover() diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 18fa69f3..c2f17051 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -302,6 +302,7 @@ class TestBatchTransform(TestCase): "First three iterations should return None." + "\n" + "i.e. no returned values until window is full'" + "%s" % (algo.history_return_price_decorator,)) + # After three Nones, the next value should be a data frame self.assertTrue(isinstance( algo.history_return_price_class[wl], diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 5ed6cc16..d5e3a8cf 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -90,8 +90,9 @@ omitted). | ending_value | the total market value of the positions held at the | | | end of the period | +---------------+------------------------------------------------------+ - | capital_used | the net capital consumed (positive means spent) by | - | | buying and selling securities in the period | + | cash_flow | the cash flow in the period (negative means spent) | + | | from buying and selling securities in the period. | + | | Includes dividend payments in the period as well. | +---------------+------------------------------------------------------+ | starting_value| the total market value of the positions held at the | | | start of the period | @@ -212,13 +213,15 @@ class PerformanceTracker(object): new_snapshot = [] for event in snapshot: - event.perf_messages = self.process_event(event) - event.portfolio = self.get_portfolio() + messages = self.process_event(event) + if messages is not None: + event.perf_messages = messages + event.portfolio = self.get_portfolio() - del event['TRANSACTION'] - new_snapshot.append(event) + new_snapshot.append(event) - yield date, new_snapshot + if len(new_snapshot) > 0: + yield date, new_snapshot def get_portfolio(self): return self.cumulative_performance.as_portfolio() @@ -241,21 +244,31 @@ class PerformanceTracker(object): def process_event(self, event): - messages = [] - + messages = None self.event_count += 1 - while event.dt > self.market_close: - messages.append(self.handle_market_close()) + if event.type == zp.DATASOURCE_TYPE.TRADE: + messages = [] + while event.dt > self.market_close: + messages.append(self.handle_market_close()) - if event.TRANSACTION: - self.txn_count += 1 - self.cumulative_performance.execute_transaction(event.TRANSACTION) - self.todays_performance.execute_transaction(event.TRANSACTION) + if event.TRANSACTION: + self.txn_count += 1 + self.cumulative_performance.execute_transaction( + event.TRANSACTION + ) + self.todays_performance.execute_transaction(event.TRANSACTION) - #update last sale - self.cumulative_performance.update_last_sale(event) - self.todays_performance.update_last_sale(event) + #update last sale + self.cumulative_performance.update_last_sale(event) + self.todays_performance.update_last_sale(event) + del event['TRANSACTION'] + + elif event.type == zp.DATASOURCE_TYPE.DIVIDEND: + # TODO: confirm with @ehebert that positions objects + # are shared. (and that it is ok). + self.cumulative_performance.add_dividend(event) + self.todays_performance.add_dividend(event) #calculate performance as of last trade self.cumulative_performance.calculate_performance() @@ -267,6 +280,10 @@ class PerformanceTracker(object): # add the return results from today to the list of DailyReturn objects. todays_date = self.market_close.replace(hour=0, minute=0, second=0) + + self.cumulative_performance.update_dividends(todays_date) + self.todays_performance.update_dividends(todays_date) + todays_return_obj = risk.DailyReturn( todays_date, self.todays_performance.returns @@ -322,7 +339,6 @@ Last successful date: %s" % self.market_open) # not trigger an end of day, so we trigger the final # market close(s) here perf_messages = [] - while self.last_close > self.market_close: perf_messages.append(self.handle_market_close()) @@ -352,6 +368,33 @@ class Position(object): self.cost_basis = 0.0 # per share self.last_sale_price = 0.0 self.last_sale_date = 0.0 + self.dividends = [] + + def update_dividends(self, dt): + # TODO: should I have asserts for the dt to be at + # midnight? + payment = 0.0 + unpaid_dividends = [] + for dividend in self.dividends: + if dt == dividend.ex_date: + # if we own shares at midnight of the div_ex date + # we are entitled to the dividend. + dividend.amount_on_ex_date = self.amount + dividend.payment = self.amount * dividend.net_amount + + if dt == dividend.pay_date: + # if it is the payment date, include this + # dividend's actual payment (calculated on + # ex_date) + payment += dividend.payment + else: + unpaid_dividends.append(dividend) + + self.dividends = unpaid_dividends + return payment + + def add_dividend(self, dividend): + self.dividends.append(dividend) def update(self, txn): if(self.sid != txn.sid): @@ -408,7 +451,7 @@ class PerformancePeriod(object): self.period_close = period_close self.ending_value = 0.0 - self.period_capital_used = 0.0 + self.period_cash_flow = 0.0 self.pnl = 0.0 #sid => position object self.positions = positiondict() @@ -439,7 +482,7 @@ class PerformancePeriod(object): def rollover(self): self.starting_value = self.ending_value self.starting_cash = self.ending_cash - self.period_capital_used = 0.0 + self.period_cash_flow = 0.0 self.pnl = 0.0 self.processed_transactions = [] self.cumulative_capital_used = 0.0 @@ -457,11 +500,40 @@ class PerformancePeriod(object): self._position_last_sale_prices, [0]) return index + def add_dividend(self, div): + # The dividend is received on midnight of the dividend + # declared date. We calculate the dividends based on the amount of + # stock owned on midnight of the ex dividend date. However, the cash + # is not dispersed until the payment date, which is + # included in the event. + self.positions[div.sid].add_dividend(div) + + def update_dividends(self, todays_date): + """ + Check the payment date and ex date against today's date + to detrmine if we are owed a dividend payment or if the + payment has been disbursed. + """ + cash_payments = 0.0 + for sid, pos in self.positions.iteritems(): + cash_payments += pos.update_dividends(todays_date) + + if cash_payments > 0.0: + # credit our cash balance with the dividend payments + self.period_cash_flow += cash_payments + # debit our cumulative cash spent with the dividend + # payments + self.cumulative_capital_used -= cash_payments + + # recalculate performance, including the dividend + # paymtents + self.calculate_performance() + def calculate_performance(self): self.ending_value = self.calculate_positions_value() total_at_start = self.starting_cash + self.starting_value - self.ending_cash = self.starting_cash + self.period_capital_used + self.ending_cash = self.starting_cash + self.period_cash_flow total_at_end = self.ending_cash + self.ending_value self.pnl = total_at_end - total_at_start @@ -478,7 +550,7 @@ class PerformancePeriod(object): index = self.index_for_position(txn.sid) self._position_amounts[index] = position.amount - self.period_capital_used += -1 * txn.price * txn.amount + self.period_cash_flow += -1 * txn.price * txn.amount # Max Leverage # --------------- @@ -522,7 +594,9 @@ class PerformancePeriod(object): def __core_dict(self): rval = { 'ending_value': self.ending_value, - 'capital_used': self.period_capital_used, + # this field is renamed to capital_used for backward + # compatibility. + 'capital_used': self.period_cash_flow, 'starting_value': self.starting_value, 'starting_cash': self.starting_cash, 'ending_cash': self.ending_cash, @@ -567,7 +641,9 @@ class PerformancePeriod(object): # 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, + # maintaining the old name for the portfolio field for + # backward compatibility + portfolio.capital_used = self.period_cash_flow portfolio.starting_cash = self.starting_cash portfolio.portfolio_value = self.ending_cash + self.ending_value portfolio.pnl = self.pnl @@ -583,6 +659,7 @@ class PerformancePeriod(object): positions = self._positions_store for sid, pos in self.positions.iteritems(): + if sid not in positions: positions[sid] = zp.Position(sid) position = positions[sid] @@ -595,8 +672,8 @@ class PerformancePeriod(object): def get_positions_list(self): positions = [] for sid, pos in self.positions.iteritems(): - cur = pos.to_dict() - positions.append(cur) + if pos.amount != 0: + positions.append(pos.to_dict()) return positions diff --git a/zipline/gens/utils.py b/zipline/gens/utils.py index dd72cb41..d2f1b0fb 100644 --- a/zipline/gens/utils.py +++ b/zipline/gens/utils.py @@ -71,6 +71,7 @@ def create_trade(sid, price, amount, datetime, source_id="test_factory"): trade.low = price * .95 trade.high = price * 1.05 trade.volume = amount + trade.TRANSACTION = None return trade diff --git a/zipline/utils/factory.py b/zipline/utils/factory.py index fa5822de..83e5da33 100644 --- a/zipline/utils/factory.py +++ b/zipline/utils/factory.py @@ -30,7 +30,7 @@ from datetime import datetime, timedelta import zipline.finance.risk as risk from zipline.utils.date_utils import tuple_to_date -from zipline.protocol import Event +from zipline.protocol import Event, DATASOURCE_TYPE from zipline.sources import (SpecificEquityTrades, DataFrameSource, DataPanelSource) @@ -119,6 +119,38 @@ def create_trading_environment(year=2006, start=None, end=None, return trading_environment +def create_random_trading_environment(): + benchmark_returns, treasury_curves = load_market_data() + + for n in range(100): + + random_index = random.randint( + 0, + len(treasury_curves) + ) + + start_dt = treasury_curves.keys()[random_index] + end_dt = start_dt + timedelta(days=365) + + now = datetime.utcnow().replace(tzinfo=pytz.utc) + + if end_dt <= now: + break + + assert end_dt <= now, """ +failed to find a suitable daterange after 100 attempts. please double +check treasury and benchmark data in findb, and re-run the test.""" + + trading_environment = TradingEnvironment( + benchmark_returns, + treasury_curves, + period_start=start_dt, + period_end=end_dt + ) + + return trading_environment, start_dt, end_dt + + def get_next_trading_dt(current, interval, trading_calendar): next = current while True: @@ -143,6 +175,20 @@ def create_trade_history(sid, prices, amounts, interval, trading_calendar, return trades +def create_dividend(sid, payment, declared_date, ex_date, pay_date): + div = Event({ + 'sid': sid, + 'gross_amount': payment, + 'net_amount': payment, + 'dt': declared_date.replace(hour=0, minute=0, second=0), + 'ex_date': ex_date.replace(hour=0, minute=0, second=0), + 'pay_date': pay_date.replace(hour=0, minute=0, second=0), + 'type': DATASOURCE_TYPE.DIVIDEND + }) + + return div + + def create_txn(sid, price, amount, datetime): txn = Event({ 'sid': sid,