From 0fd78cd54a5ef7c814dc16b21e8fa8ba032dcea6 Mon Sep 17 00:00:00 2001 From: Delaney Granizo-Mackenzie Date: Fri, 25 Jul 2014 15:04:23 -0400 Subject: [PATCH] BUG: Fixed random dips in returns as shown to user. Previously the last sale price was not correctly being set on positions when the transaction arrived before the trade event. The last sale price was defaulted to zero and never updated. This resulted in one holding stocks that were bough >>0 and now had value 0 from the perspective of returns. The returns would display correctly again when the next trade of that security happened. For most securities trading is frequent enough that there's no issue, but for some illiquid ones it took hours to fix itself. Updated test_perf_tracking:TestPerformanceTracker.test_minute_tracker This test was based on assuming that last_sale_price was zero, allowing the sharpe ratio to be calculated. The sharpe ratio can no longer be calculated for this specific tested scenario and the test has been changed accordingly. --- tests/test_perf_tracking.py | 16 +++++++++++++--- zipline/finance/performance/period.py | 1 + zipline/finance/performance/position.py | 6 ++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 87951e1b..39e0c420 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -760,6 +760,14 @@ class TestPositionPerformance(unittest.TestCase): pp = perf.PerformancePeriod(1000.0) pp.execute_transaction(txn) + + # This verifies that the last sale price is being correctly + # set in the positions. If this is not the case then returns can + # incorrectly show as sharply dipping if a transaction arrives + # before a trade. This is caused by returns being based on holding + # stocks with a last sale price of 0. + self.assertEqual(pp.positions[1].last_sale_price, 10.0) + for trade in trades: pp.update_last_sale(trade) @@ -1487,7 +1495,9 @@ class TestPerformanceTracker(unittest.TestCase): self.assertEquals(foo_event_2.dt, msg_2['minute_perf']['period_close']) - # Ensure that a Sharpe value for cumulative metrics is being - # created. - self.assertIsNotNone(msg_1['cumulative_risk_metrics']['sharpe']) + # In this test event1 transactions arrive on the first bar. + # This leads to no returns as the price is constant. + # Sharpe ratio cannot be computed and is None. + # In the second bar we can start establishing a sharpe ratio. + self.assertIsNone(msg_1['cumulative_risk_metrics']['sharpe']) self.assertIsNotNone(msg_2['cumulative_risk_metrics']['sharpe']) diff --git a/zipline/finance/performance/period.py b/zipline/finance/performance/period.py index aa8e752c..f8400acf 100644 --- a/zipline/finance/performance/period.py +++ b/zipline/finance/performance/period.py @@ -303,6 +303,7 @@ class PerformancePeriod(object): position.update(txn) self.ensure_position_index(txn.sid) self._position_amounts[txn.sid] = position.amount + self._position_last_sale_prices[txn.sid] = position.last_sale_price self.period_cash_flow -= txn.price * txn.amount diff --git a/zipline/finance/performance/position.py b/zipline/finance/performance/position.py index 008ced15..52bda2be 100644 --- a/zipline/finance/performance/position.py +++ b/zipline/finance/performance/position.py @@ -152,6 +152,12 @@ class Position(object): total_cost = prev_cost + txn_cost self.cost_basis = total_cost / total_shares + # Update the last sale price if txn is + # best data we have so far + if self.last_sale_date is None or txn.dt > self.last_sale_date: + self.last_sale_price = txn.price + self.last_sale_date = txn.dt + self.amount = total_shares def adjust_commission_cost_basis(self, commission):