MAINT: One way to set sim_params and data_frequency.

There were sevaral places you could supply sim_params
in TradingAlgorithm (__init__, run). This got confusing
as its not clear who updated what and which one was the
correct one to use at each time.

Then there were to ways to define data_frequency, one in
__init__() and one in the sim_params which also added code
complexity.

This refactor makes it explicit that sim_params are to be
passed to __init__() only. Moreover, data_frequency is
only stored in sim_params. For backwards compatibility,
it can still be supplied separately but will link to
the one in sim_params.

For example, you could create new sim params via:

sim_params = create_simulation_parameters(data_frequency='minute')
algo = MyAlgo(sim_params)
algo.run(data)

In addition, perf_tracker only gets initialized in one place:
_create_generator() which should also make the various ways
of running an algorithm more deterministic.

This also fixes a bug with SimulationParameters where
you could not change the period_start. Unfortunately, the
current implementation still requieres an implicit call to
update the internal variables.
This commit is contained in:
Thomas Wiecki
2014-06-30 17:28:02 +02:00
parent 4c9cf1321d
commit 10885e1b77
9 changed files with 95 additions and 101 deletions
+51 -54
View File
@@ -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,