From 98f3fc932628cef6e96f99c90a643d445a218305 Mon Sep 17 00:00:00 2001 From: Andrew Liang Date: Thu, 28 Jul 2016 14:52:29 -0400 Subject: [PATCH] MAINT: Refactor application of capital changes Previously, on the dt of a capital change, we use the un-updated prices to find the ending performance of the previous subperiod and then got the new prices to determine the portfolio value used to calculate the delta, without actually updating the performance before applying the capital change. This logic is confusing and unintuitive. Instead, save the ending performance as we do previously, but have temp values for the starting current subperiod value. Update those temp values after processing the capital change --- tests/test_perf_tracking.py | 7 +++---- zipline/algorithm.py | 21 ++++----------------- zipline/finance/performance/period.py | 26 +++++++++++++++++++------- zipline/finance/performance/tracker.py | 13 +++++++++++-- zipline/gens/tradesimulation.py | 10 +++++----- 5 files changed, 42 insertions(+), 35 deletions(-) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index d8d13820..076ec252 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -2264,12 +2264,11 @@ shares in position" pt.execute_transaction(txn) pp.handle_execution(txn) - # sync prices and calculate performance before we introduce a capital - # change + # sync prices before we introduce a capital change pt.sync_last_sale_prices(trades[2].dt, False, data_portal) - pp.calculate_performance() - pp.subdivide_period(1000.0) + pp.initialize_subperiod_divider() + pp.set_current_subperiod_starting_values(1000.0) pt.sync_last_sale_prices(trades[-1].dt, False, data_portal) pp.calculate_performance() diff --git a/zipline/algorithm.py b/zipline/algorithm.py index c8f68078..bc9b5c4c 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -821,25 +821,12 @@ class TradingAlgorithm(object): self.data_portal ) - # Calculate performance before we sync prices price for the current dt - self.perf_tracker.cumulative_performance.calculate_performance() - self.perf_tracker.todays_performance.calculate_performance() + self.perf_tracker.prepare_capital_change(is_interday) if capital_change['type'] == 'target': - # Get an updated portfolio value as of this dt, but do it in a way - # so that the performance is not recalculated. This is done so - # that `process_capital_change` can find the performance values - # for the end of the subperiod, which is the previous dt - self.perf_tracker.position_tracker.sync_last_sale_prices( - dt, - self._in_before_trading_start, - self.data_portal - ) - portfolio_value = \ - self.perf_tracker.position_tracker.stats().net_value + \ - self.perf_tracker.cumulative_performance.ending_cash - - capital_change_amount = capital_change['value'] - portfolio_value + capital_change_amount = capital_change['value'] - \ + self.updated_portfolio().portfolio_value + self.portfolio_needs_update = True log.info('Processing capital change to target %s at %s. Capital ' 'change delta is %s' % (capital_change['value'], dt, diff --git a/zipline/finance/performance/period.py b/zipline/finance/performance/period.py index 2aeab406..cc17f374 100644 --- a/zipline/finance/performance/period.py +++ b/zipline/finance/performance/period.py @@ -241,14 +241,12 @@ class PerformancePeriod(object): else: del self._payout_last_sale_prices[asset] - def subdivide_period(self, capital_change): - # Apply the capital change to the ending cash - self.ending_cash += capital_change + def initialize_subperiod_divider(self): + self.calculate_performance() - # Increment the total capital change occurred within the period - self._total_intraperiod_capital_change += capital_change - - # Divide the period into subperiods + # Initialize a subperiod divider to stash the current performance + # values. Current period starting values are set to equal ending values + # of the previous subperiod self.subperiod_divider = SubPeriodDivider( prev_returns=self.returns, prev_pnl=self.pnl, @@ -257,6 +255,20 @@ class PerformancePeriod(object): curr_starting_cash=self.ending_cash ) + def set_current_subperiod_starting_values(self, capital_change): + # Apply the capital change to the ending cash + self.ending_cash += capital_change + + # Increment the total capital change occurred within the period + self._total_intraperiod_capital_change += capital_change + + # Update the current subperiod starting cash to reflect the capital + # change + starting_value = self.subperiod_divider.curr_subperiod.starting_value + self.subperiod_divider.curr_subperiod = CurrSubPeriodStats( + starting_value=starting_value, + starting_cash=self.ending_cash) + def handle_dividends_paid(self, net_cash_payment): if net_cash_payment: self.handle_cash_payment(net_cash_payment) diff --git a/zipline/finance/performance/tracker.py b/zipline/finance/performance/tracker.py index 017bfea5..617a44bb 100644 --- a/zipline/finance/performance/tracker.py +++ b/zipline/finance/performance/tracker.py @@ -238,8 +238,16 @@ class PerformanceTracker(object): return _dict + def prepare_capital_change(self, is_interday): + self.cumulative_performance.initialize_subperiod_divider() + + if not is_interday: + # Change comes in the middle of day + self.todays_performance.initialize_subperiod_divider() + def process_capital_change(self, capital_change_amount, is_interday): - self.cumulative_performance.subdivide_period(capital_change_amount) + self.cumulative_performance.set_current_subperiod_starting_values( + capital_change_amount) if is_interday: # Change comes between days @@ -247,7 +255,8 @@ class PerformanceTracker(object): capital_change_amount) else: # Change comes in the middle of day - self.todays_performance.subdivide_period(capital_change_amount) + self.todays_performance.set_current_subperiod_starting_values( + capital_change_amount) def process_transaction(self, transaction): self.txn_count += 1 diff --git a/zipline/gens/tradesimulation.py b/zipline/gens/tradesimulation.py index e44e1196..4aa1c2fa 100644 --- a/zipline/gens/tradesimulation.py +++ b/zipline/gens/tradesimulation.py @@ -100,11 +100,11 @@ class AlgorithmSimulator(object): def every_bar(dt_to_use, current_data=self.current_data, handle_data=algo.event_manager.handle_data): # called every tick (minute or day). + algo.on_dt_changed(dt_to_use) calculate_minute_capital_changes(dt_to_use) self.simulation_dt = dt_to_use - algo.on_dt_changed(dt_to_use) blotter = algo.blotter perf_tracker = algo.perf_tracker @@ -149,10 +149,6 @@ class AlgorithmSimulator(object): perf_tracker = algo.perf_tracker - # process any capital changes that came overnight - algo.calculate_capital_changes( - midnight_dt, emission_rate=emission_rate, is_interday=True) - # Get the positions before updating the date so that prices are # fetched for trading close instead of midnight positions = algo.perf_tracker.position_tracker.positions @@ -162,6 +158,10 @@ class AlgorithmSimulator(object): self.simulation_dt = midnight_dt algo.on_dt_changed(midnight_dt) + # process any capital changes that came overnight + algo.calculate_capital_changes( + midnight_dt, emission_rate=emission_rate, is_interday=True) + # we want to wait until the clock rolls over to the next day # before cleaning up expired assets. self._cleanup_expired_assets(midnight_dt, position_assets)