diff --git a/tests/test_minute_risk.py b/tests/test_minute_risk.py new file mode 100644 index 00000000..9fa02a43 --- /dev/null +++ b/tests/test_minute_risk.py @@ -0,0 +1,57 @@ +# +# Copyright 2013 Quantopian, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import datetime +import pytz + +from zipline.finance.trading import SimulationParameters +from zipline.finance import risk + + +class TestMinuteRisk(unittest.TestCase): + + def setUp(self): + + start_date = datetime.datetime( + year=2006, + month=1, + day=3, + hour=0, + minute=0, + tzinfo=pytz.utc) + end_date = datetime.datetime( + year=2006, month=1, day=3, tzinfo=pytz.utc) + + self.sim_params = SimulationParameters( + period_start=start_date, + period_end=end_date + ) + self.sim_params.emission_rate = 'minute' + + def test_minute_risk(self): + + risk_metrics = risk.RiskMetricsIterative(self.sim_params) + + first_dt = self.sim_params.first_open + second_dt = self.sim_params.first_open + datetime.timedelta(minutes=1) + + risk_metrics.update(first_dt, 1.0, 2.0) + + self.assertEquals(1, len(risk_metrics.alpha)) + + risk_metrics.update(second_dt, 3.0, 4.0) + + self.assertEquals(2, len(risk_metrics.alpha)) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 3dfbe5ac..d85678e1 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -1047,19 +1047,31 @@ class TestPerformanceTracker(unittest.TestCase): dt=foo_event_1.dt, price=10.0, commission=0.50) + benchmark_event_1 = Event({ + 'dt': start_dt, + 'returns': 1.0, + 'type': DATASOURCE_TYPE.BENCHMARK + }) 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)) + benchmark_event_2 = Event({ + 'dt': start_dt + datetime.timedelta(minutes=1), + 'returns': 2.0, + 'type': DATASOURCE_TYPE.BENCHMARK + }) events = [ foo_event_1, order_event_1, + benchmark_event_1, txn_event_1, bar_event_1, foo_event_2, - bar_event_2 + benchmark_event_2, + bar_event_2, ] messages = {date: snapshot[-1].perf_messages[0] for date, snapshot in diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 1fdfe4ad..b37221d6 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -166,9 +166,13 @@ class PerformanceTracker(object): risk.RiskMetricsIterative(self.sim_params) self.emission_rate = sim_params.emission_rate - # Temporarily hold these here as we work on streaming benchmarks. - self.all_benchmark_returns = pd.Series( - index=trading.environment.trading_days) + if self.emission_rate == 'daily': + self.all_benchmark_returns = pd.Series( + index=trading.environment.trading_days) + elif self.emission_rate == 'minute': + self.all_benchmark_returns = pd.Series(index=pd.date_range( + self.sim_params.first_open, self.sim_params.last_close, + freq='Min')) # this performance period will span the entire simulation. self.cumulative_performance = PerformancePeriod( @@ -244,6 +248,9 @@ class PerformanceTracker(object): event.portfolio = None new_snapshot.append(event) + + self.handle_minute_close(date) + if new_snapshot: new_snapshot[-1].perf_messages = [self.to_dict()] new_snapshot[-1].portfolio = self.get_portfolio() @@ -342,6 +349,15 @@ class PerformanceTracker(object): return messages + def handle_minute_close(self, dt): + #update risk metrics for cumulative performance + algorithm_returns = pd.Series({dt: self.todays_performance.returns}) + benchmark_returns = pd.Series({dt: self.all_benchmark_returns[dt]}) + + self.cumulative_risk_metrics.update(dt, + algorithm_returns, + benchmark_returns) + def handle_market_close(self): # add the return results from today to the list of DailyReturn objects. todays_date = self.market_close.replace(hour=0, minute=0, second=0, diff --git a/zipline/finance/risk.py b/zipline/finance/risk.py index 023d7b97..b61385c2 100644 --- a/zipline/finance/risk.py +++ b/zipline/finance/risk.py @@ -537,8 +537,20 @@ class RiskMetricsIterative(RiskMetricsBase): self.trading_days = all_trading_days[mask] - self.algorithm_returns_cont = pd.Series(index=self.trading_days) - self.benchmark_returns_cont = pd.Series(index=self.trading_days) + self.sim_params = sim_params + + if sim_params.emission_rate == 'daily': + self.algorithm_returns_cont = pd.Series(index=self.trading_days) + self.benchmark_returns_cont = pd.Series(index=self.trading_days) + + elif sim_params.emission_rate == 'minute': + + self.algorithm_returns_cont = pd.Series(index=pd.date_range( + sim_params.first_open, sim_params.last_close, + freq="Min")) + self.benchmark_returns_cont = pd.Series(index=pd.date_range( + sim_params.first_open, sim_params.last_close, + freq="Min")) self.algorithm_returns = None self.benchmark_returns = None @@ -629,9 +641,6 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" 'treasury_period_return': self.treasury_period_return, 'algorithm_period_return': self.algorithm_period_returns[-1], 'benchmark_period_return': self.benchmark_period_returns[-1], - 'sharpe': self.sharpe[-1], - 'sortino': self.sortino[-1], - 'information': self.information[-1], 'beta': self.beta[-1], 'alpha': self.alpha[-1], 'excess_return': self.excess_returns[-1], @@ -639,6 +648,17 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" 'period_label': period_label } + if self.sim_params.emission_rate == 'daily': + # Some risk metrics only make sense in a context of daily + # risk calculations. + rval['sharpe'] = self.sharpe[-1] + rval['sortino'] = self.sortino[-1] + rval['information'] = self.information[-1] + elif self.sim_params.emission_rate == 'minute': + rval['sharpe'] = 0.0 + rval['sortino'] = 0.0 + rval['information'] = 0.0 + # check if a field in rval is nan, and replace it with # None. def check_entry(key, value):