# # 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. """ A source to be used in testing. """ import pytz from six.moves import filter from datetime import datetime, timedelta import itertools from six.moves import range from zipline.protocol import ( Event, DATASOURCE_TYPE ) from zipline.gens.utils import hash_args def create_trade(sid, price, amount, datetime, source_id="test_factory"): trade = Event() trade.source_id = source_id trade.type = DATASOURCE_TYPE.TRADE trade.sid = sid trade.dt = datetime trade.price = price trade.close_price = price trade.open_price = price trade.low = price * .95 trade.high = price * 1.05 trade.volume = amount return trade def date_gen(start, end, trading_calendar, delta=timedelta(minutes=1), repeats=None): """ Utility to generate a stream of dates. """ daily_delta = not (delta.total_seconds() % timedelta(days=1).total_seconds()) cur = start if daily_delta: # if we are producing daily timestamps, we # use midnight cur = cur.replace(hour=0, minute=0, second=0, microsecond=0) def advance_current(cur): """ Advances the current dt skipping non market days and minutes. """ cur = cur + delta currently_executing = \ (daily_delta and (cur in trading_calendar.all_sessions)) or \ (trading_calendar.is_open_on_minute(cur)) if currently_executing: return cur else: if daily_delta: return trading_calendar.minute_to_session_label(cur) else: return trading_calendar.open_and_close_for_session( trading_calendar.minute_to_session_label(cur) )[0] # yield count trade events, all on trading days, and # during trading hours. while cur < end: if repeats: for j in range(repeats): yield cur else: yield cur cur = advance_current(cur) class SpecificEquityTrades(object): """ Yields all events in event_list that match the given sid_filter. If no event_list is specified, generates an internal stream of events to filter. Returns all events if filter is None. Configuration options: count : integer representing number of trades sids : list of values representing simulated internal sids start : start date delta : timedelta between internal events filter : filter to remove the sids """ def __init__(self, env, trading_calendar, *args, **kwargs): # We shouldn't get any positional arguments. assert len(args) == 0 self.env = env self.trading_calendar = trading_calendar # Default to None for event_list and filter. self.event_list = kwargs.get('event_list') self.filter = kwargs.get('filter') if self.event_list is not None: # If event_list is provided, extract parameters from there # This isn't really clean and ultimately I think this # class should serve a single purpose (either take an # event_list or autocreate events). self.count = kwargs.get('count', len(self.event_list)) self.start = kwargs.get('start', self.event_list[0].dt) self.end = kwargs.get('end', self.event_list[-1].dt) self.delta = delta = kwargs.get('delta') if delta is None: self.delta = self.event_list[1].dt - self.event_list[0].dt self.concurrent = kwargs.get('concurrent', False) self.identifiers = kwargs.get( 'sids', set(event.sid for event in self.event_list) ) assets_by_identifier = {} for identifier in self.identifiers: assets_by_identifier[identifier] = env.asset_finder.\ lookup_generic(identifier, datetime.now())[0] self.sids = [asset.sid for asset in assets_by_identifier.values()] for event in self.event_list: event.sid = assets_by_identifier[event.sid].sid else: # Unpack config dictionary with default values. self.count = kwargs.get('count', 500) self.start = kwargs.get( 'start', datetime(2008, 6, 6, 15, tzinfo=pytz.utc)) self.end = kwargs.get( 'end', datetime(2008, 6, 6, 15, tzinfo=pytz.utc)) self.delta = kwargs.get( 'delta', timedelta(minutes=1)) self.concurrent = kwargs.get('concurrent', False) self.identifiers = kwargs.get('sids', [1, 2]) assets_by_identifier = {} for identifier in self.identifiers: assets_by_identifier[identifier] = env.asset_finder.\ lookup_generic(identifier, datetime.now())[0] self.sids = [asset.sid for asset in assets_by_identifier.values()] # Hash_value for downstream sorting. self.arg_string = hash_args(*args, **kwargs) self.generator = self.create_fresh_generator() def __iter__(self): return self def next(self): return self.generator.next() def __next__(self): return next(self.generator) def rewind(self): self.generator = self.create_fresh_generator() def get_hash(self): return self.__class__.__name__ + "-" + self.arg_string def update_source_id(self, gen): for event in gen: event.source_id = self.get_hash() yield event def create_fresh_generator(self): if self.event_list: event_gen = (event for event in self.event_list) unfiltered = self.update_source_id(event_gen) # Set up iterators for each expected field. else: if self.concurrent: # in this context the count is the number of # trades per sid, not the total. date_generator = date_gen( start=self.start, end=self.end, delta=self.delta, repeats=len(self.sids), trading_calendar=self.trading_calendar, ) else: date_generator = date_gen( start=self.start, end=self.end, delta=self.delta, trading_calendar=self.trading_calendar, ) source_id = self.get_hash() unfiltered = ( create_trade( sid=sid, price=float(i % 10) + 1.0, amount=(i * 50) % 900 + 100, datetime=date, source_id=source_id, ) for (i, date), sid in itertools.product( enumerate(date_generator), self.sids ) ) # If we specified a sid filter, filter out elements that don't # match the filter. if self.filter: filtered = filter( lambda event: event.sid in self.filter, unfiltered) # Otherwise just use all events. else: filtered = unfiltered # Return the filtered event stream. return filtered