diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 4490576d..ed53466b 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -93,8 +93,7 @@ class TestRecordAlgorithm(TestCase): def test_record_incr(self): algo = RecordAlgorithm( - sim_params=self.sim_params, - data_frequency='daily') + sim_params=self.sim_params) output = algo.run(self.source) np.testing.assert_array_equal(output['incr'].values, @@ -161,8 +160,9 @@ class TestMiscellaneousAPI(TestCase): algo.minute += 1 algo = TradingAlgorithm(initialize=initialize, - handle_data=handle_data) - algo.run(self.source, sim_params=self.sim_params) + handle_data=handle_data, + sim_params=self.sim_params) + algo.run(self.source) class TestTransformAlgorithm(TestCase): @@ -194,14 +194,6 @@ class TestTransformAlgorithm(TestCase): self.assertEqual(len(algo.sources), 1) assert isinstance(algo.sources[0], SpecificEquityTrades) - def test_multi_source_as_input_no_start_end(self): - algo = TestRegisterTransformAlgorithm( - sids=[133] - ) - - with self.assertRaises(AssertionError): - algo.run([self.source, self.df_source]) - def test_invalid_order_parameters(self): algo = InvalidOrderAlgorithm( sids=[133], @@ -261,26 +253,26 @@ class TestTransformAlgorithm(TestCase): assert algo.registered_transforms['mavg']['class'] is MovingAverage def test_data_frequency_setting(self): + self.sim_params.data_frequency = 'daily' algo = TestRegisterTransformAlgorithm( sim_params=self.sim_params, - data_frequency='daily' ) - self.assertEqual(algo.data_frequency, 'daily') + self.assertEqual(algo.sim_params.data_frequency, 'daily') self.assertEqual(algo.annualizer, 250) + self.sim_params.data_frequency = 'minute' algo = TestRegisterTransformAlgorithm( sim_params=self.sim_params, - data_frequency='minute' ) - self.assertEqual(algo.data_frequency, 'minute') + self.assertEqual(algo.sim_params.data_frequency, 'minute') self.assertEqual(algo.annualizer, 250 * 6 * 60) + self.sim_params.data_frequency = 'minute' algo = TestRegisterTransformAlgorithm( sim_params=self.sim_params, - data_frequency='minute', annualizer=10 ) - self.assertEqual(algo.data_frequency, 'minute') + self.assertEqual(algo.sim_params.data_frequency, 'minute') self.assertEqual(algo.annualizer, 10) def test_order_methods(self): @@ -294,7 +286,6 @@ class TestTransformAlgorithm(TestCase): for AlgoClass in AlgoClasses: algo = AlgoClass( sim_params=self.sim_params, - data_frequency='daily' ) algo.run(self.df) @@ -310,7 +301,6 @@ class TestTransformAlgorithm(TestCase): for name in method_names_to_test: algo = TestOrderStyleForwardingAlgorithm( sim_params=self.sim_params, - data_frequency='daily', instant_fill=False, method_name=name ) @@ -318,19 +308,18 @@ class TestTransformAlgorithm(TestCase): def test_order_instant(self): algo = TestOrderInstantAlgorithm(sim_params=self.sim_params, - data_frequency='daily', instant_fill=True) algo.run(self.df) def test_minute_data(self): source = RandomWalkSource(freq='minute', - start=pd.Timestamp('2000-1-1', + start=pd.Timestamp('2000-1-3', tz='UTC'), - end=pd.Timestamp('2000-1-1', + end=pd.Timestamp('2000-1-4', tz='UTC')) + self.sim_params.data_frequency = 'minute' algo = TestOrderInstantAlgorithm(sim_params=self.sim_params, - data_frequency='minute', instant_fill=True) algo.run(source) @@ -355,8 +344,7 @@ class TestPositions(TestCase): factory.create_test_df_source(self.sim_params) def test_empty_portfolio(self): - algo = EmptyPositionsAlgorithm(sim_params=self.sim_params, - data_frequency='daily') + algo = EmptyPositionsAlgorithm(sim_params=self.sim_params) daily_stats = algo.run(self.df) expected_position_count = [ @@ -634,7 +622,9 @@ def handle_data(context, data): end = pd.Timestamp('1991-01-15', tz='UTC') source = RandomWalkSource(start=start, end=end) - algo = TradingAlgorithm(script=history_algo, data_frequency='minute') + sim_params = factory.create_simulation_parameters( + data_frequency='minute') + algo = TradingAlgorithm(script=history_algo, sim_params=sim_params) output = algo.run(source) self.assertIsNot(output, None) diff --git a/tests/test_algorithm_gen.py b/tests/test_algorithm_gen.py index dc215a94..d077b551 100644 --- a/tests/test_algorithm_gen.py +++ b/tests/test_algorithm_gen.py @@ -27,6 +27,7 @@ from zipline.finance import trading from zipline.algorithm import TradingAlgorithm from zipline.finance import slippage from zipline.utils import factory +from zipline.utils.factory import create_simulation_parameters from zipline.utils.test_utils import ( setup_logger, teardown_logger @@ -222,7 +223,8 @@ class AlgorithmGeneratorTestCase(TestCase): algo.datetime should be equal to the last benchmark time. See https://github.com/quantopian/zipline/issues/241 """ - sim_params = factory.create_simulation_parameters(num_days=1) - algo = TestAlgo(self, sim_params=sim_params, data_frequency='minute') + sim_params = create_simulation_parameters(num_days=1, + data_frequency='minute') + algo = TestAlgo(self, sim_params=sim_params) algo.run(source=[]) self.assertEqual(algo.datetime, sim_params.last_close) diff --git a/tests/test_events_through_risk.py b/tests/test_events_through_risk.py index f854500e..e951e26b 100644 --- a/tests/test_events_through_risk.py +++ b/tests/test_events_through_risk.py @@ -63,12 +63,12 @@ class TestEventsThroughRisk(unittest.TestCase): sim_params = SimulationParameters( period_start=start_date, period_end=end_date, + data_frequency='daily', emission_rate='daily' ) algo = BuyAndHoldAlgorithm( - sim_params=sim_params, - data_frequency='daily') + sim_params=sim_params) first_date = datetime.datetime(2006, 1, 3, tzinfo=pytz.utc) second_date = datetime.datetime(2006, 1, 4, tzinfo=pytz.utc) @@ -128,7 +128,7 @@ class TestEventsThroughRisk(unittest.TestCase): ] algo.benchmark_return_source = benchmark_data - algo.sources = list([trade_bar_data]) + algo.set_sources(list([trade_bar_data])) gen = algo._create_generator(sim_params) # TODO: Hand derive these results. @@ -188,8 +188,7 @@ class TestEventsThroughRisk(unittest.TestCase): data_frequency='minute') algo = BuyAndHoldAlgorithm( - sim_params=sim_params, - data_frequency='minute') + sim_params=sim_params) first_date = datetime.datetime(2006, 1, 3, tzinfo=pytz.utc) first_open, first_close = \ @@ -288,7 +287,7 @@ class TestEventsThroughRisk(unittest.TestCase): ] algo.benchmark_return_source = benchmark_data - algo.sources = list([trade_bar_data]) + algo.set_sources(list([trade_bar_data])) gen = algo._create_generator(sim_params) crm = algo.perf_tracker.cumulative_risk_metrics diff --git a/tests/test_tradesimulation.py b/tests/test_tradesimulation.py index aa4f382e..868d2ae0 100644 --- a/tests/test_tradesimulation.py +++ b/tests/test_tradesimulation.py @@ -21,9 +21,9 @@ from zipline.utils import factory class TestTradeSimulation(TestCase): def test_minutely_emissions_generate_performance_stats_for_last_day(self): - params = factory.create_simulation_parameters(num_days=1) - params.data_frequency = 'minute' - params.emission_rate = 'minute' - algo = NoopAlgorithm() - algo.run(source=[], sim_params=params) + params = factory.create_simulation_parameters(num_days=1, + data_frequency='minute', + emission_rate='minute') + algo = NoopAlgorithm(sim_params=params) + algo.run(source=[]) self.assertEqual(algo.perf_tracker.day_count, 1.0) diff --git a/zipline/__init__.py b/zipline/__init__.py index 990c92bf..33467375 100644 --- a/zipline/__init__.py +++ b/zipline/__init__.py @@ -32,7 +32,7 @@ from . algorithm import TradingAlgorithm from . import api try: - ip = get_ipython() # flake8: noqa + ip = get_ipython() # flake8: noqa ip.register_magic_function(utils.parse_cell_magic, "line_cell", "zipline") except: pass diff --git a/zipline/algorithm.py b/zipline/algorithm.py index 6450bafc..000a1a98 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from copy import copy +import warnings import pytz import pandas as pd @@ -147,11 +148,6 @@ class TradingAlgorithm(object): self.slippage = VolumeShareSlippage() self.commission = PerShare() - if 'data_frequency' in kwargs: - self.set_data_frequency(kwargs.pop('data_frequency')) - else: - self.data_frequency = None - self.instant_fill = kwargs.pop('instant_fill', False) # Override annualizer if set @@ -162,13 +158,13 @@ class TradingAlgorithm(object): self.capital_base = kwargs.pop('capital_base', DEFAULT_CAPITAL_BASE) self.sim_params = kwargs.pop('sim_params', None) - if self.sim_params: - if self.data_frequency is None: - self.data_frequency = self.sim_params.data_frequency - else: - self.sim_params.data_frequency = self.data_frequency + if self.sim_params is None: + self.sim_params = create_simulation_parameters( + capital_base=self.capital_base + ) - self.perf_tracker = PerformanceTracker(self.sim_params) + # perf_tacker gets instantiated in ._create_generator() + self.perf_tracker = None self.blotter = kwargs.pop('blotter', None) if not self.blotter: @@ -209,6 +205,11 @@ class TradingAlgorithm(object): if self._initialize is None: self._initialize = lambda x: None + # Alternative way of setting data_frequency for backwards + # compatibility. + if 'data_frequency' in kwargs: + self.data_frequency = kwargs.pop('data_frequency') + # an algorithm subclass needs to set initialized to True when # it is fully initialized. self.initialized = False @@ -261,7 +262,7 @@ class TradingAlgorithm(object): blotter=repr(self.blotter), recorded_vars=repr(self.recorded_vars)) - def _create_data_generator(self, source_filter, sim_params): + def _create_data_generator(self, source_filter, sim_params=None): """ Create a merged data generator using the sources and transforms attached to this algorithm. @@ -271,9 +272,12 @@ class TradingAlgorithm(object): processed by the zipline, and False for those that should be skipped. """ + if sim_params is None: + sim_params = self.sim_params + if self.benchmark_return_source is None: env = trading.environment - if (self.data_frequency == 'minute' + if (sim_params.data_frequency == 'minute' or sim_params.emission_rate == 'minute'): update_time = lambda date: env.get_open_and_close(date)[1] else: @@ -315,15 +319,11 @@ class TradingAlgorithm(object): processed by the zipline, and False for those that should be skipped. """ - sim_params.data_frequency = self.data_frequency - - # perf_tracker will be instantiated in __init__ if a sim_params - # is passed to the constructor. If not, we instantiate here. + # Instantiate perf_tracker if self.perf_tracker is None: self.perf_tracker = PerformanceTracker(sim_params) - self.data_gen = self._create_data_generator(source_filter, - sim_params) + self.data_gen = self._create_data_generator(source_filter, sim_params) self.trading_client = AlgorithmSimulator(self, sim_params) @@ -343,14 +343,15 @@ class TradingAlgorithm(object): # TODO: make a new subclass, e.g. BatchAlgorithm, and move # the run method to the subclass, and refactor to put the # generator creation logic into get_generator. - def run(self, source, sim_params=None, benchmark_return_source=None): + def run(self, source, overwrite_sim_params=True, + benchmark_return_source=None): """Run the algorithm. :Arguments: source : can be either: - pandas.DataFrame - zipline source - - list of zipline sources + - list of sources If pandas.DataFrame is provided, it must have the following structure: @@ -364,44 +365,34 @@ class TradingAlgorithm(object): Daily performance metrics such as returns, alpha etc. """ - if isinstance(source, (list, tuple)): - assert self.sim_params is not None or sim_params is not None, \ - """When providing a list of sources, \ - sim_params have to be specified as a parameter - or in the constructor.""" + if isinstance(source, list): + if overwrite_sim_params: + warnings.warn("""List of sources passed, will not attempt to extract sids, and start and end + dates. Make sure to set the correct fields in sim_params passed to + __init__().""", UserWarning) + overwrite_sim_params = False elif isinstance(source, pd.DataFrame): # if DataFrame provided, wrap in DataFrameSource source = DataFrameSource(source) elif isinstance(source, pd.Panel): source = DataPanelSource(source) - if not isinstance(source, (list, tuple)): - self.sources = [source] + if isinstance(source, list): + self.set_sources(source) else: - self.sources = source + self.set_sources([source]) - # Check for override of sim_params. - # If it isn't passed to this function, - # use the default params set with the algorithm. - # Else, we create simulation parameters using the start and end of the - # source provided. - if sim_params is None: - if self.sim_params is None: - start = source.start - end = source.end - sim_params = create_simulation_parameters( - start=start, - end=end, - capital_base=self.capital_base, - ) - else: - sim_params = self.sim_params - - # update sim params to ensure it's set - self.sim_params = sim_params - if self.sim_params.sids is None: + # Override sim_params if params are provided by the source. + if overwrite_sim_params: + if hasattr(source, 'start'): + self.sim_params.period_start = source.start + if hasattr(source, 'end'): + self.sim_params.period_end = source.end all_sids = [sid for s in self.sources for sid in s.sids] self.sim_params.sids = set(all_sids) + # Changing period_start and period_close might require updating + # of first_open and last_close. + self.sim_params._update_internal() # Create history containers if len(self.history_specs) != 0: @@ -427,7 +418,7 @@ class TradingAlgorithm(object): self.perf_tracker = None # create transforms and zipline - self.gen = self._create_generator(sim_params) + self.gen = self._create_generator(self.sim_params) with ZiplineAPI(self): # loop through simulated_trading, each iteration returns a @@ -677,10 +668,16 @@ class TradingAlgorithm(object): assert isinstance(transforms, list) self.transforms = transforms - def set_data_frequency(self, data_frequency): - assert data_frequency in ('daily', 'minute') - self.data_frequency = data_frequency - self.annualizer = ANNUALIZER[self.data_frequency] + # Remain backwards compatibility + @property + def data_frequency(self): + return self.sim_params.data_frequency + + @data_frequency.setter + def data_frequency(self, value): + assert value in ('daily', 'minute') + self.sim_params.data_frequency = value + self.annualizer = ANNUALIZER[self.sim_params.data_frequency] @api_method def order_percent(self, sid, percent, diff --git a/zipline/finance/trading.py b/zipline/finance/trading.py index 8330bfca..61c92371 100644 --- a/zipline/finance/trading.py +++ b/zipline/finance/trading.py @@ -354,9 +354,6 @@ class SimulationParameters(object): data_frequency='daily', sids=None): - # This is the global environment for trading simulation. - environment = TradingEnvironment.instance() - self.period_start = period_start self.period_end = period_end self.capital_base = capital_base @@ -365,6 +362,12 @@ class SimulationParameters(object): self.data_frequency = data_frequency self.sids = sids + self._update_internal() + + def _update_internal(self): + # This is the global environment for trading simulation. + environment = TradingEnvironment.instance() + assert self.period_start <= self.period_end, \ "Period start falls after period end." diff --git a/zipline/utils/cli.py b/zipline/utils/cli.py index e5681fd0..7af986e8 100644 --- a/zipline/utils/cli.py +++ b/zipline/utils/cli.py @@ -119,7 +119,7 @@ def parse_cell_magic(line, cell): local_namespace = args.pop('local_namespace', False) # By default, execute inside IPython namespace if not local_namespace: - args['namespace'] = get_ipython().user_ns # flake8: noqa + args['namespace'] = get_ipython().user_ns # flake8: noqa # If we are running inside NB, do not output to file but create a # variable instead @@ -128,7 +128,7 @@ def parse_cell_magic(line, cell): perf = run_pipeline(print_algo=False, algo_text=cell, **args) if output_var_name is not None: - get_ipython().user_ns[output_var_name] = perf # flake8: noqa + get_ipython().user_ns[output_var_name] = perf # flake8: noqa def run_pipeline(print_algo=True, **kwargs): diff --git a/zipline/utils/factory.py b/zipline/utils/factory.py index 7ff9edf4..e2301eb8 100644 --- a/zipline/utils/factory.py +++ b/zipline/utils/factory.py @@ -43,7 +43,8 @@ __all__ = ['load_from_yahoo', 'load_bars_from_yahoo'] def create_simulation_parameters(year=2006, start=None, end=None, capital_base=float("1.0e5"), num_days=None, load=None, - sids=None): + sids=None, data_frequency='daily', + emission_rate='daily'): """Construct a complete environment with reasonable defaults""" if start is None: start = datetime(year, 1, 1, tzinfo=pytz.utc) @@ -60,6 +61,8 @@ def create_simulation_parameters(year=2006, start=None, end=None, period_end=end, capital_base=capital_base, sids=sids, + data_frequency=data_frequency, + emission_rate=emission_rate, ) return sim_params