From 7679e5a581d6639440b1b6920b46a55214b97bef Mon Sep 17 00:00:00 2001 From: Eddie Hebert Date: Mon, 25 Mar 2013 13:36:33 -0400 Subject: [PATCH] ENH: Wires minutely emission of data from performance tracker. Wires up performance tracker so that when `emission_rate` is set to `minute`, the performance packets are sent out every minute, instead of once per day. Please note, the performance packets that are generated are not ready for prime time consumption, this patch is merely a step towards hooking up the ability to inspect minute data. Known issues: - The packets do not currently include risk information. Since we need to consider how this affects the denominators of the risk calculations. --- tests/test_perf_tracking.py | 40 ++++++++++++++++++++++++++++++++++ zipline/finance/performance.py | 33 +++++++++++++++++++++++----- zipline/finance/trading.py | 10 ++++++++- 3 files changed, 77 insertions(+), 6 deletions(-) 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."