diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index ce934929..bb127c1c 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -1949,8 +1949,7 @@ class TestPerformanceTracker(unittest.TestCase): tracker.process_order(event) elif event.type == zp.DATASOURCE_TYPE.TRANSACTION: tracker.process_transaction(event) - tracker.handle_minute_close(date) - msg = tracker.to_dict() + msg, _ = tracker.handle_minute_close(date) messages[date] = msg self.assertEquals(2, len(messages)) diff --git a/zipline/finance/performance/tracker.py b/zipline/finance/performance/tracker.py index fd4fdb20..1afa1888 100644 --- a/zipline/finance/performance/tracker.py +++ b/zipline/finance/performance/tracker.py @@ -279,8 +279,11 @@ class PerformanceTracker(object): Creates a dictionary representing the state of this tracker. Returns a dict object of the form described in header comments. """ - if not emission_type: + + # Default to the emission rate of this tracker if no type is provided + if emission_type is None: emission_type = self.emission_rate + _dict = { 'period_start': self.period_start, 'period_end': self.period_end, @@ -294,6 +297,8 @@ class PerformanceTracker(object): elif emission_type == 'minute': _dict['minute_perf'] = self.todays_performance.to_dict( self.saved_dt) + else: + raise BaseException("Invalid emission type: %s" % emission_type) return _dict @@ -409,6 +414,22 @@ class PerformanceTracker(object): period.handle_dividends_paid(net_cash_payment) def handle_minute_close(self, dt): + """ + Handles the close of the given minute. This includes handling + market-close functions if the given minute is the end of the market + day. + + Parameters + __________ + dt : Timestamp + The minute that is ending + + Returns + _______ + (dict, dict/None) + A tuple of the minute perf packet and daily perf packet. + If the market day has not ended, the daily perf packet is None. + """ self.update_performance() todays_date = normalize_date(dt) account = self.get_account(False) @@ -424,19 +445,14 @@ class PerformanceTracker(object): bench_since_open, account) - # if this is the close, update dividends for the next day. - if dt == self.market_close: - self.check_upcoming_dividends(todays_date) + minute_packet = self.to_dict(emission_type='minute') - def handle_intraday_market_close(self, new_mkt_open, new_mkt_close): - """ - Function called at market close only when emitting at minutely - frequency. - """ - # increment the day counter before we move markers forward. - self.day_count += 1.0 - self.market_open = new_mkt_open - self.market_close = new_mkt_close + # if this is the close, update dividends for the next day. + # Return the performance tuple + if dt == self.market_close: + return (minute_packet, self._handle_market_close(todays_date)) + else: + return (minute_packet, None) def handle_market_close_daily(self): """ @@ -454,12 +470,16 @@ class PerformanceTracker(object): self.all_benchmark_returns[completed_date], account) + return self._handle_market_close(completed_date) + + def _handle_market_close(self, completed_date): + # increment the day counter before we move markers forward. self.day_count += 1.0 # Take a snapshot of our current performance to return to the # browser. - daily_update = self.to_dict() + daily_update = self.to_dict(emission_type='daily') # On the last day of the test, don't create tomorrow's performance # period. We may not be able to find the next trading day if we're at diff --git a/zipline/gens/tradesimulation.py b/zipline/gens/tradesimulation.py index 54bb575c..b3d47b13 100644 --- a/zipline/gens/tradesimulation.py +++ b/zipline/gens/tradesimulation.py @@ -114,29 +114,20 @@ class AlgorithmSimulator(object): self.update_universe(event) else: - message = self._process_snapshot( + messages = self._process_snapshot( date, snapshot, self.algo.instant_fill, ) # Perf messages are only emitted if the snapshot contained # a benchmark event. - if message is not None: - yield message + if messages is not None: + for message in messages: + yield message - # When emitting minutely, we re-iterate the day as a - # packet with the entire days performance rolled up. + # When emitting minutely, we need to call + # before_trading_start before the next trading day begins if date == mkt_close: - if self.algo.perf_tracker.emission_rate == 'minute': - daily_rollup = self.algo.perf_tracker.to_dict( - emission_type='daily' - ) - daily_rollup['daily_perf']['recorded_vars'] = \ - self.algo.recorded_vars - yield daily_rollup - tp = self.algo.perf_tracker.todays_performance - tp.rollover() - if mkt_close <= self.algo.perf_tracker.last_close: before_last_close = \ mkt_close < self.algo.perf_tracker.last_close @@ -149,12 +140,6 @@ class AlgorithmSimulator(object): # If at the end of backtest history, # skip advancing market close. pass - if self.algo.perf_tracker.emission_rate == \ - 'minute': - self.algo.perf_tracker\ - .handle_intraday_market_close( - mkt_open, - mkt_close) if before_last_close: self._call_before_trading_start(mkt_open) @@ -328,7 +313,7 @@ class AlgorithmSimulator(object): perf_process_trade(trade) if benchmark_event_occurred: - return self.get_message(dt) + return self.generate_messages(dt) else: return None @@ -356,9 +341,9 @@ class AlgorithmSimulator(object): if self.algo.datetime != dt: self.algo.on_dt_changed(dt) - def get_message(self, dt): + def generate_messages(self, dt): """ - Get a perf message for the given datetime. + Generator that yields perf messages for the given datetime. """ # Ensure that updated_portfolio has been called at least once for this # dt before we emit a perf message. This is a no-op if @@ -371,13 +356,22 @@ class AlgorithmSimulator(object): perf_message = \ self.algo.perf_tracker.handle_market_close_daily() perf_message['daily_perf']['recorded_vars'] = rvars - return perf_message + yield perf_message elif self.algo.perf_tracker.emission_rate == 'minute': - self.algo.perf_tracker.handle_minute_close(dt) - perf_message = self.algo.perf_tracker.to_dict() - perf_message['minute_perf']['recorded_vars'] = rvars - return perf_message + # close the minute in the tracker, and collect the daily message if + # the minute is the close of the trading day + minute_message, daily_message = \ + self.algo.perf_tracker.handle_minute_close(dt) + + # collect and yield the minute's perf message + minute_message['minute_perf']['recorded_vars'] = rvars + yield minute_message + + # if there was a daily perf message, collect and yield it + if daily_message: + daily_message['daily_perf']['recorded_vars'] = rvars + yield daily_message def update_universe(self, event): """