diff --git a/zipline/finance/trading.py b/zipline/finance/trading.py index a089f392..d3fbabe5 100644 --- a/zipline/finance/trading.py +++ b/zipline/finance/trading.py @@ -357,13 +357,13 @@ for order: order=str(order) ) qutil.LOGGER.warn(warning) - - - orders = [ x for x in orders if x.amount - x.filled > 0 and x.dt.day >= event.dt.day] + + orders = [ x for x in orders if abs(x.amount - x.filled) > 0 and x.dt.day >= event.dt.day] + self.open_orders[event.sid] = orders - if simulated_amount > 0: + if simulated_amount != 0: return self.create_transaction( event.sid, simulated_amount, diff --git a/zipline/lines.py b/zipline/lines.py index 2123cfdf..624a9456 100644 --- a/zipline/lines.py +++ b/zipline/lines.py @@ -191,16 +191,16 @@ class SimulatedTrading(object): - sid - an integer, which will be used as the security ID. - order_count - the number of orders the test algo will place, defaults to 100 + - order_amount - the number of shares per order, defaults to 100 - trade_count - the number of trades to simulate, defaults to 100 - simulator_class - optional parameter that provides an alternative subclass of ComponentHost to hold the whole zipline. Defaults to :py:class:`zipline.simulator.Simulator` - algorithm - optional parameter providing an algorithm. defaults to :py:class:`zipline.test.algorithms.TestAlgorithm` - - random - optional parameter to request random trades. if present - :py:class:`zipline.sources.RandomEquityTrades` is the source. If - not :py:class:`ziplien.sources.SpecificEquityTrades` is the - source + - trade_source - optional parameter to specify trades, if present. + If not present :py:class:`ziplien.sources.SpecificEquityTrades` + is the source, with daily frequency in trades. """ assert isinstance(config, dict) @@ -219,6 +219,11 @@ class SimulatedTrading(object): order_count = config['order_count'] else: order_count = 100 + + if config.has_key('order_amount'): + order_amount = config['order_amount'] + else: + order_amount = 100 if config.has_key('trade_count'): trade_count = config['trade_count'] @@ -235,12 +240,8 @@ class SimulatedTrading(object): #------------------- sids = [sid] #------------------- - if config.has_key('random'): - trade_source = factory.create_random_trade_source( - sids, - trade_count, - trading_environment - ) + if config.has_key('trade_source'): + trade_source = config['trade_source'] else: trade_source = factory.create_daily_trade_source( sids, @@ -253,7 +254,6 @@ class SimulatedTrading(object): if config.has_key('algorithm'): test_algo = config['algorithm'] else: - order_amount = 100 test_algo = TestAlgorithm( sid, order_amount, diff --git a/zipline/test/algorithms.py b/zipline/test/algorithms.py index 49ec15c0..522bf76f 100644 --- a/zipline/test/algorithms.py +++ b/zipline/test/algorithms.py @@ -76,8 +76,8 @@ class TestAlgorithm(): self.incr += 1 def get_sid_filter(self): - return [self.sid] - + return [self.sid] + class NoopAlgorithm(object): """ Dolce fa niente. diff --git a/zipline/test/factory.py b/zipline/test/factory.py index 92000650..381fbb7e 100644 --- a/zipline/test/factory.py +++ b/zipline/test/factory.py @@ -167,6 +167,7 @@ def create_random_trade_source(sid, trade_count, trading_environment): return source def create_daily_trade_source(sids, trade_count, trading_environment): + """ creates trade_count trades for each sid in sids list. first trade will be on trading_environment.period_start, and daily @@ -176,12 +177,38 @@ def create_daily_trade_source(sids, trade_count, trading_environment): Important side-effect: trading_environment.period_end will be modified to match the day of the final trade. """ + return create_trade_source( + sids, + trade_count, + timedelta(days=1), + trading_environment + ) + + +def create_minutely_trade_source(sids, trade_count, trading_environment): + + """ + creates trade_count trades for each sid in sids list. + first trade will be on trading_environment.period_start, and every minute + thereafter for each sid. Thus, two sids should result in two trades per + minute. + + Important side-effect: trading_environment.period_end will be modified + to match the day of the final trade. + """ + return create_trade_source( + sids, + trade_count, + timedelta(minutes=1), + trading_environment + ) + +def create_trade_source(sids, trade_count, trade_time_increment, trading_environment): trade_history = [] for sid in sids: price = [10.1] * trade_count volume = [100] * trade_count start_date = trading_environment.first_open - trade_time_increment = timedelta(days=1) generated_trades = create_trade_history( sid, diff --git a/zipline/test/test_finance.py b/zipline/test/test_finance.py index 42d5367c..9b3e4187 100644 --- a/zipline/test/test_finance.py +++ b/zipline/test/test_finance.py @@ -24,6 +24,7 @@ from zipline.lines import SimulatedTrading from zipline.protocol_utils import namedict DEFAULT_TIMEOUT = 15 # seconds +EXTENDED_TIMEOUT = 90 allocator = AddressAllocator(1000) @@ -121,6 +122,38 @@ class FinanceTestCase(TestCase): .format(n=zipline.sim.feed.pending_messages())) + + @timed(EXTENDED_TIMEOUT) + def test_aggressive_buying(self): + + # Simulation + # ---------- + trade_count = 10 * 1000 + self.zipline_test_config['order_count'] = 5 * 1000 + self.zipline_test_config['trade_count'] = trade_count + self.zipline_test_config['order_amount'] = 100 + self.zipline_test_config['environment'] = factory.create_trading_environment() + + sid_list = [self.zipline_test_config['sid']] + + self.zipline_test_config['trade_source'] = factory.create_minutely_trade_source( + sid_list, + trade_count, + self.zipline_test_config['environment'] + ) + + zipline = SimulatedTrading.create_test_zipline(**self.zipline_test_config) + zipline.simulate(blocking=True) + + self.assertTrue(zipline.sim.ready()) + self.assertFalse(zipline.sim.exception) + + # TODO: Make more assertions about the final state of the components. + self.assertEqual(zipline.sim.feed.pending_messages(), 0, \ + "The feed should be drained of all messages, found {n} remaining." \ + .format(n=zipline.sim.feed.pending_messages())) + + @timed(DEFAULT_TIMEOUT) def test_performance(self): #provide enough trades to ensure all orders are filled. @@ -218,9 +251,11 @@ class FinanceTestCase(TestCase): ) - # TODO: write a test that proves orders expire without being filled. + # TODO: write tests for short sales + # TODO: write a test to do massive buying or shorting. + @timed(DEFAULT_TIMEOUT) - def test_transaction_sim(self): + def test_partially_filled_orders(self): # create a scenario where order size and trade size are equal # so that orders must be spread out over several trades. @@ -240,9 +275,25 @@ class FinanceTestCase(TestCase): self.transaction_sim(**params) + # same scenario, but with short sales + params2 ={ + 'trade_count':360, + 'trade_amount':100, + 'trade_interval': timedelta(minutes=1), + 'order_count':2, + 'order_amount':-100, + 'order_interval': timedelta(minutes=1), + 'expected_txn_count':8, + 'expected_txn_volume':2 * -100 + } + + self.transaction_sim(**params2) + + @timed(DEFAULT_TIMEOUT) + def test_collapsing_orders(self): # create a scenario where order.amount <<< trade.volume # to test that several orders can be covered properly by one trade. - params2 ={ + params1 ={ 'trade_count':6, 'trade_amount':100, 'trade_interval': timedelta(hours=1), @@ -254,11 +305,26 @@ class FinanceTestCase(TestCase): 'expected_txn_count':1, 'expected_txn_volume':24 * 1 } - self.transaction_sim(**params2) + self.transaction_sim(**params1) + # second verse, same as the first. except short! + params2 ={ + 'trade_count':6, + 'trade_amount':100, + 'trade_interval': timedelta(hours=1), + 'order_count':24, + 'order_amount':-1, + 'order_interval': timedelta(minutes=1), + 'expected_txn_count':1, + 'expected_txn_volume':24 * -1 + } + self.transaction_sim(**params2) + + @timed(DEFAULT_TIMEOUT) + def test_partial_expiration_orders(self): # create a scenario where orders expire without being filled # entirely - params3 = { + params1 = { 'trade_count':100, 'trade_amount':100, 'trade_delay': timedelta(minutes=5), @@ -271,7 +337,25 @@ class FinanceTestCase(TestCase): 'expected_txn_count' : 1, 'expected_txn_volume' : 25 } - self.transaction_sim(**params3) + self.transaction_sim(**params1) + + # same scenario, but short sales. + params2 = { + 'trade_count':100, + 'trade_amount':100, + 'trade_delay': timedelta(minutes=5), + 'trade_interval': timedelta(days=1), + 'order_count':3, + 'order_amount':1000, + 'order_interval': timedelta(minutes=30), + # because we placed an orders totaling less than 25% of one trade + # the simulator should produce just one transaction. + 'expected_txn_count' : 1, + 'expected_txn_volume' : 25 + } + self.transaction_sim(**params2) + + def transaction_sim(self, **params): trade_count = params['trade_count'] diff --git a/zipline/test/test_perf_tracking.py b/zipline/test/test_perf_tracking.py index bd6efe7c..be7c9a25 100644 --- a/zipline/test/test_perf_tracking.py +++ b/zipline/test/test_perf_tracking.py @@ -22,8 +22,15 @@ class PerformanceTestCase(unittest.TestCase): 0, len(self.treasury_curves) ) - self.dt = self.treasury_curves.keys()[random_index] - self.end_dt = self.dt + datetime.timedelta(days=365) + for n in range(100): + self.dt = self.treasury_curves.keys()[random_index] + self.end_dt = self.dt + datetime.timedelta(days=365) + + now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + + if self.end_dt <= now: + break + self.trading_environment = TradingEnvironment( self.benchmark_returns, self.treasury_curves, @@ -505,8 +512,6 @@ shares in position" price = 10.1 price_list = [price] * trade_count volume = [100] * trade_count - #start_date = datetime.datetime.strptime("01/01/2011","%m/%d/%Y") - #start_date = start_date.replace(tzinfo=pytz.utc) trade_time_increment = datetime.timedelta(days=1) trade_history = factory.create_trade_history( sid,