BUG: Aligns performance packet generation between minute and daily modes

This commit is contained in:
jfkirk
2015-07-21 13:25:39 -04:00
parent f22e9e5122
commit 8d5bfd3c91
3 changed files with 58 additions and 45 deletions
+1 -2
View File
@@ -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))
+34 -14
View File
@@ -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
+23 -29
View File
@@ -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):
"""