diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index fab97cfe..45445e7b 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -1030,3 +1030,43 @@ class TestPerformanceTracker(unittest.TestCase): event['TRANSACTION'] = txn return event + + def test_minute_tracker(self): + """ Tests minute performance tracking.""" + exc_tz = pytz.timezone('US/Eastern') + start_dt = trading.exchange_dt_in_utc( + datetime.datetime(2013, 3, 1, 9, 30, tzinfo=exc_tz)) + end_dt = trading.exchange_dt_in_utc( + datetime.datetime(2013, 3, 1, 16, 0, tzinfo=exc_tz)) + + sim_params = SimulationParameters( + period_start=start_dt, + period_end=end_dt, + emission_rate='minute' + ) + tracker = perf.PerformanceTracker(sim_params) + + foo_event_1 = factory.create_trade('foo', 10.0, 20, start_dt) + bar_event_1 = factory.create_trade('bar', 100.0, 200, start_dt) + txn = Transaction(sid=foo_event_1.sid, + amount=-25, + dt=foo_event_1.dt, + price=10.0, + commission=0.50) + foo_event_1.TRANSACTION = txn + + foo_event_2 = factory.create_trade( + 'foo', 11.0, 20, start_dt + datetime.timedelta(minutes=1)) + bar_event_2 = factory.create_trade( + 'bar', 11.0, 20, start_dt + datetime.timedelta(minutes=1)) + + foo_event_3 = factory.create_trade( + 'foo', 12.0, 30, start_dt + datetime.timedelta(minutes=2)) + + tracker.process_event(foo_event_1) + tracker.process_event(bar_event_1) + messages = tracker.process_event(foo_event_2) + tracker.process_event(bar_event_2) + messages += tracker.process_event(foo_event_3) + + self.assertEquals(2, len(messages)) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 9fad92fc..5bbac867 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -161,6 +161,7 @@ class PerformanceTracker(object): self.capital_base = self.sim_params.capital_base self.cumulative_risk_metrics = \ risk.RiskMetricsIterative(self.period_start) + self.emission_rate = sim_params.emission_rate # this performance period will span the entire simulation. self.cumulative_performance = PerformancePeriod( @@ -187,6 +188,7 @@ class PerformanceTracker(object): serialize_positions=True ) + self.saved_dt = self.period_start self.returns = [] # one indexed so that we reach 100% self.day_count = 0.0 @@ -225,15 +227,27 @@ class PerformanceTracker(object): Creates a dictionary representing the state of this tracker. Returns a dict object of the form described in header comments. """ - return { + _dict = { 'period_start': self.period_start, 'period_end': self.period_end, 'progress': self.progress, 'capital_base': self.capital_base, 'cumulative_perf': self.cumulative_performance.to_dict(), - 'daily_perf': self.todays_performance.to_dict(), - 'cumulative_risk_metrics': self.cumulative_risk_metrics.to_dict() } + if self.emission_rate == 'daily': + _dict.update({'cumulative_risk_metrics': + self.cumulative_risk_metrics.to_dict(), + 'daily_perf': + self.todays_performance.to_dict()}) + if self.emission_rate == 'minute': + # Currently reusing 'todays_performance' for intraday trading + # result, should be analogous, but has the potential for needing + # its own configuration down the line. + # Naming as intraday to make clear that these results are + # being updated per minute + _dict['intraday_perf'] = self.todays_performance.to_dict() + + return _dict def process_event(self, event): @@ -242,8 +256,17 @@ class PerformanceTracker(object): if event.type == zp.DATASOURCE_TYPE.TRADE: messages = [] - while event.dt > self.market_close and event.dt < self.last_close: - messages.append(self.handle_market_close()) + + # This switch could also be handled by an inheritance + # with a DailyPerformanceTracker and a MinutePerformanceTracker + if self.emission_rate == 'daily': + while (event.dt > self.market_close and + event.dt < self.last_close): + messages.append(self.handle_market_close()) + elif self.emission_rate == 'minute': + if event.dt > self.saved_dt: + messages.append(self.to_dict()) + self.saved_dt = event.dt if event.TRANSACTION: self.txn_count += 1 diff --git a/zipline/finance/trading.py b/zipline/finance/trading.py index dc907ad3..97c97c00 100644 --- a/zipline/finance/trading.py +++ b/zipline/finance/trading.py @@ -74,6 +74,11 @@ log = logbook.Logger('Transaction Simulator') environment = None +def exchange_dt_in_utc(dt): + delorean = Delorean(dt, dt.tzinfo) + return delorean.shift(pytz.utc.zone).datetime + + class TransactionSimulator(object): def __init__(self): @@ -253,7 +258,8 @@ Last successful date: %s" % self.market_open) class SimulationParameters(object): def __init__(self, period_start, period_end, - capital_base=10e3): + capital_base=10e3, + emission_rate='daily'): global environment if not environment: @@ -264,6 +270,8 @@ class SimulationParameters(object): self.period_end = period_end self.capital_base = capital_base + self.emission_rate = emission_rate + assert self.period_start <= self.period_end, \ "Period start falls after period end."