From 44088dd4d5348c8d236d484e1da1787223dae36b Mon Sep 17 00:00:00 2001 From: dmichalowicz Date: Wed, 25 Jan 2017 13:56:11 -0500 Subject: [PATCH] BUG: Multipliers were not removed when closing a position --- tests/test_perf_tracking.py | 50 +++++++++++++++++++ .../finance/performance/position_tracker.py | 6 ++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 3d83593e..f76f9ee5 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -2085,3 +2085,53 @@ class TestPositionTracker(WithTradingEnvironment, # Test gross and net exposures self.assertEqual(100 + 150000 + 200, pos_stats.gross_exposure) self.assertEqual(100 + 150000 - 200, pos_stats.net_exposure) + + def test_close_position(self): + future_sid = 1032201401 + equity_sid = 1 + pt = perf.PositionTracker(self.env.asset_finder, None) + dt = pd.Timestamp('2017/01/04 3:00PM') + + pos1 = perf.Position( + sid=future_sid, + amount=np.float64(30.0), + last_sale_date=dt, + last_sale_price=100, + ) + pos2 = perf.Position( + sid=equity_sid, + amount=np.float64(10.0), + last_sale_date=dt, + last_sale_price=10, + ) + + # Update the positions dictionary with `future_sid` first. The order + # matters because it affects the multipliers dictionaries, which are + # OrderedDicts. If `future_sid` is not removed from the multipliers + # dictionaries, equities will hit the incorrect multiplier when + # computing `pt.stats()`. + pt.update_positions({future_sid: pos1, equity_sid: pos2}) + + asset_to_close = self.env.asset_finder.retrieve_asset(future_sid) + txn = create_txn(asset_to_close, dt, 100, -30) + pt.execute_transaction(txn) + + pos_stats = pt.stats() + + # Test long-only methods. + self.assertEqual(100, pos_stats.long_value) + self.assertEqual(100, pos_stats.long_exposure) + self.assertEqual(1, pos_stats.longs_count) + + # Test short-only methods. + self.assertEqual(0, pos_stats.short_value) + self.assertEqual(0, pos_stats.short_exposure) + self.assertEqual(0, pos_stats.shorts_count) + + # Test gross and net values. + self.assertEqual(100, pos_stats.gross_value) + self.assertEqual(100, pos_stats.net_value) + + # Test gross and net exposures. + self.assertEqual(100, pos_stats.gross_exposure) + self.assertEqual(100, pos_stats.net_exposure) diff --git a/zipline/finance/performance/position_tracker.py b/zipline/finance/performance/position_tracker.py index 7983e8f0..417c7392 100644 --- a/zipline/finance/performance/position_tracker.py +++ b/zipline/finance/performance/position_tracker.py @@ -192,6 +192,8 @@ class PositionTracker(object): # if this position now has 0 shares, remove it from our internal # bookkeeping. del self.positions[sid] + del self._position_value_multipliers[sid] + del self._position_exposure_multipliers[sid] try: # if this position exists in our user-facing dictionary, @@ -199,8 +201,8 @@ class PositionTracker(object): del self._positions_store[sid] except KeyError: pass - - self._update_asset(sid) + else: + self._update_asset(sid) def handle_commission(self, sid, cost): # Adjust the cost basis of the stock if we own it