diff --git a/tests/finance/test_blotter.py b/tests/finance/test_blotter.py index 2a031c81..aaaa2bf7 100644 --- a/tests/finance/test_blotter.py +++ b/tests/finance/test_blotter.py @@ -108,7 +108,7 @@ class BlotterTestCase(WithCreateBarData, (StopLimitOrder(10, 20), 10, 20)]) def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp): - blotter = Blotter('daily', self.asset_finder) + blotter = Blotter('daily') blotter.order(self.asset_24, 100, style_obj) result = blotter.open_orders[self.asset_24][0] @@ -117,7 +117,7 @@ class BlotterTestCase(WithCreateBarData, self.assertEqual(result.stop, expected_stp) def test_cancel(self): - blotter = Blotter('daily', self.asset_finder) + blotter = Blotter('daily') oid_1 = blotter.order(self.asset_24, 100, MarketOrder()) oid_2 = blotter.order(self.asset_24, 200, MarketOrder()) @@ -151,10 +151,9 @@ class BlotterTestCase(WithCreateBarData, self.assertEqual(list(blotter.open_orders), [self.asset_25]) def test_blotter_eod_cancellation(self): - blotter = Blotter('minute', self.asset_finder, - cancel_policy=EODCancel()) + blotter = Blotter('minute', cancel_policy=EODCancel()) - # Make two orders for the same sid, so we can test that we are not + # Make two orders for the same asset, so we can test that we are not # mutating the orders list as we are cancelling orders blotter.order(self.asset_24, 100, MarketOrder()) blotter.order(self.asset_24, -100, MarketOrder()) @@ -175,8 +174,7 @@ class BlotterTestCase(WithCreateBarData, self.assertEqual(order.status, ORDER_STATUS.CANCELLED) def test_blotter_never_cancel(self): - blotter = Blotter('minute', self.asset_finder, - cancel_policy=NeverCancel()) + blotter = Blotter('minute', cancel_policy=NeverCancel()) blotter.order(self.asset_24, 100, MarketOrder()) @@ -190,8 +188,7 @@ class BlotterTestCase(WithCreateBarData, self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) def test_order_rejection(self): - blotter = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter = Blotter(self.sim_params.data_frequency) # Reject a nonexistent order -> no order appears in new_order, # no exceptions raised out @@ -220,8 +217,7 @@ class BlotterTestCase(WithCreateBarData, # Do it again, but reject it at a later time (after tradesimulation # pulls it from new_orders) - blotter = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter = Blotter(self.sim_params.data_frequency) new_open_id = blotter.order(self.asset_24, 10, MarketOrder()) new_open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(new_open_id, new_open_order.id) @@ -237,8 +233,7 @@ class BlotterTestCase(WithCreateBarData, # You can't reject a filled order. # Reset for paranoia - blotter = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter = Blotter(self.sim_params.data_frequency) blotter.slippage_models[Equity] = FixedSlippage() filled_id = blotter.order(self.asset_24, 100, MarketOrder()) filled_order = None @@ -266,8 +261,7 @@ class BlotterTestCase(WithCreateBarData, status indication. When a fill happens, the order should switch status to OPEN/FILLED as necessary """ - blotter = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter = Blotter(self.sim_params.data_frequency) # Nothing happens on held of a non-existent order blotter.hold(56) self.assertEqual(blotter.new_orders, []) @@ -303,8 +297,7 @@ class BlotterTestCase(WithCreateBarData, expected_status = ORDER_STATUS.OPEN if expected_open else \ ORDER_STATUS.FILLED - blotter = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter = Blotter(self.sim_params.data_frequency) open_id = blotter.order(self.asset_24, order_size, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_id, open_order.id) @@ -326,8 +319,7 @@ class BlotterTestCase(WithCreateBarData, self.assertEqual(filled_order.open_amount, expected_open) def test_prune_orders(self): - blotter = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter = Blotter(self.sim_params.data_frequency) blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] @@ -343,7 +335,7 @@ class BlotterTestCase(WithCreateBarData, other_order = Order( dt=blotter.current_dt, - sid=self.asset_25, + asset=self.asset_25, amount=1 ) @@ -354,10 +346,8 @@ class BlotterTestCase(WithCreateBarData, Ensure the effect of order_batch is the same as multiple calls to order. """ - blotter1 = Blotter(self.sim_params.data_frequency, - self.asset_finder) - blotter2 = Blotter(self.sim_params.data_frequency, - self.asset_finder) + blotter1 = Blotter(self.sim_params.data_frequency) + blotter2 = Blotter(self.sim_params.data_frequency) for i in range(1, 4): order_arg_lists = [ (self.asset_24, i * 100, MarketOrder()), @@ -386,7 +376,6 @@ class BlotterTestCase(WithCreateBarData, def test_slippage_and_commission_dispatching(self): blotter = Blotter( self.sim_params.data_frequency, - self.asset_finder, equity_slippage=FixedSlippage(spread=0.0), future_slippage=FixedSlippage(spread=2.0), equity_commission=PerTrade(cost=1.0), @@ -406,7 +395,7 @@ class BlotterTestCase(WithCreateBarData, equity_txn = txns[0] self.assertEqual( equity_txn.price, - bar_data.current(equity_txn.sid, 'price'), + bar_data.current(equity_txn.asset, 'price'), ) self.assertEqual(commissions[0]['cost'], 1.0) @@ -416,6 +405,6 @@ class BlotterTestCase(WithCreateBarData, future_txn = txns[1] self.assertEqual( future_txn.price, - bar_data.current(future_txn.sid, 'price') + 1.0, + bar_data.current(future_txn.asset, 'price') + 1.0, ) self.assertEqual(commissions[1]['cost'], 2.0) diff --git a/tests/finance/test_commissions.py b/tests/finance/test_commissions.py index dacfe562..7bedb51a 100644 --- a/tests/finance/test_commissions.py +++ b/tests/finance/test_commissions.py @@ -21,16 +21,16 @@ class CommissionUnitTests(WithAssetFinder, ZiplineTestCase): asset1 = self.asset_finder.retrieve_asset(1) # one order - order = Order(dt=None, sid=asset1, amount=500) + order = Order(dt=None, asset=asset1, amount=500) # three fills - txn1 = Transaction(sid=asset1, amount=230, dt=None, + txn1 = Transaction(asset=asset1, amount=230, dt=None, price=100, order_id=order.id) - txn2 = Transaction(sid=asset1, amount=170, dt=None, + txn2 = Transaction(asset=asset1, amount=170, dt=None, price=101, order_id=order.id) - txn3 = Transaction(sid=asset1, amount=100, dt=None, + txn3 = Transaction(asset=asset1, amount=100, dt=None, price=102, order_id=order.id) return order, [txn1, txn2, txn3] diff --git a/tests/finance/test_slippage.py b/tests/finance/test_slippage.py index 5698080f..85b07a73 100644 --- a/tests/finance/test_slippage.py +++ b/tests/finance/test_slippage.py @@ -126,7 +126,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': 100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'limit': 3.5}) ] @@ -148,7 +148,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': 100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'limit': 3.5}) ] @@ -170,7 +170,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': 100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'limit': 3.6}) ] @@ -194,7 +194,7 @@ class SlippageTestCase(WithCreateBarData, # we ordered 100 shares, but default volume slippage only allows # for 2.5% of the volume. 2.5% * 2000 = 50 shares 'amount': int(50), - 'sid': int(133), + 'asset': self.ASSET133, 'order_id': open_orders[0].id } @@ -209,7 +209,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': -100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'limit': 3.5}) ] @@ -231,7 +231,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': -100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'limit': 3.5}) ] @@ -253,7 +253,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': -100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'limit': 3.4}) ] @@ -275,7 +275,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime( 2006, 1, 5, 14, 32, tzinfo=pytz.utc), 'amount': int(-50), - 'sid': int(133) + 'asset': self.ASSET133, } self.assertIsNotNone(txn) @@ -293,7 +293,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': 100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'stop': 4.0, 'limit': 3.0}) ] @@ -328,7 +328,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': 100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'stop': 4.0, 'limit': 3.5}) ] @@ -363,7 +363,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': 100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'stop': 4.0, 'limit': 3.6}) ] @@ -398,7 +398,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime( 2006, 1, 5, 14, 34, tzinfo=pytz.utc), 'amount': int(50), - 'sid': int(133) + 'asset': self.ASSET133 } for key, value in expected_txn.items(): @@ -411,7 +411,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': -100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'stop': 3.0, 'limit': 4.0}) ] @@ -446,7 +446,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': -100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'stop': 3.0, 'limit': 3.5}) ] @@ -481,7 +481,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'amount': -100, 'filled': 0, - 'sid': self.ASSET133, + 'asset': self.ASSET133, 'stop': 3.0, 'limit': 3.4}) ] @@ -516,7 +516,7 @@ class SlippageTestCase(WithCreateBarData, 'dt': datetime.datetime( 2006, 1, 5, 14, 32, tzinfo=pytz.utc), 'amount': int(-50), - 'sid': int(133) + 'asset': self.ASSET133, } for key, value in expected_txn.items(): @@ -574,7 +574,7 @@ class VolumeShareSlippageTestCase(WithCreateBarData, dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), amount=100, filled=0, - sid=self.ASSET133 + asset=self.ASSET133 ) ] @@ -596,7 +596,7 @@ class VolumeShareSlippageTestCase(WithCreateBarData, 'dt': datetime.datetime( 2006, 1, 5, 14, 31, tzinfo=pytz.utc), 'amount': int(5), - 'sid': int(133), + 'asset': self.ASSET133, 'commission': None, 'type': DATASOURCE_TYPE.TRANSACTION, 'order_id': open_orders[0].id @@ -613,7 +613,7 @@ class VolumeShareSlippageTestCase(WithCreateBarData, dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), amount=100, filled=0, - sid=self.ASSET133 + asset=self.ASSET133 ) ] @@ -684,7 +684,6 @@ class OrdersStopTestCase(WithSimParams, 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'amount': 100, 'filled': 0, - 'sid': 133, 'stop': 3.5 }, 'event': { @@ -693,7 +692,6 @@ class OrdersStopTestCase(WithSimParams, 'price': 4.0, 'high': 3.15, 'low': 2.85, - 'sid': 133, 'close': 4.0, 'open': 3.5 }, @@ -702,7 +700,6 @@ class OrdersStopTestCase(WithSimParams, 'price': 4.00025, 'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'), 'amount': 50, - 'sid': 133, } } }, @@ -711,7 +708,6 @@ class OrdersStopTestCase(WithSimParams, 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'amount': 100, 'filled': 0, - 'sid': 133, 'stop': 3.6 }, 'event': { @@ -720,7 +716,6 @@ class OrdersStopTestCase(WithSimParams, 'price': 3.5, 'high': 3.15, 'low': 2.85, - 'sid': 133, 'close': 3.5, 'open': 4.0 }, @@ -733,7 +728,6 @@ class OrdersStopTestCase(WithSimParams, 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'amount': -100, 'filled': 0, - 'sid': 133, 'stop': 3.4 }, 'event': { @@ -742,7 +736,6 @@ class OrdersStopTestCase(WithSimParams, 'price': 3.5, 'high': 3.15, 'low': 2.85, - 'sid': 133, 'close': 3.5, 'open': 3.0 }, @@ -755,7 +748,6 @@ class OrdersStopTestCase(WithSimParams, 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'amount': -100, 'filled': 0, - 'sid': 133, 'stop': 3.5 }, 'event': { @@ -764,7 +756,6 @@ class OrdersStopTestCase(WithSimParams, 'price': 3.0, 'high': 3.15, 'low': 2.85, - 'sid': 133, 'close': 3.0, 'open': 3.0 }, @@ -773,7 +764,6 @@ class OrdersStopTestCase(WithSimParams, 'price': 2.9998125, 'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'), 'amount': -50, - 'sid': 133, } } }, @@ -785,9 +775,13 @@ class OrdersStopTestCase(WithSimParams, ]) def test_orders_stop(self, name, order_data, event_data, expected): data = order_data - data['sid'] = self.ASSET133 + data['asset'] = self.ASSET133 order = Order(**data) + if expected['transaction']: + expected['transaction']['asset'] = self.ASSET133 + event_data['asset'] = self.ASSET133 + assets = ( (133, pd.DataFrame( { diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 8a907fd2..e44b14b9 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -1790,8 +1790,7 @@ def handle_data(context, data): def test_batch_order_target_percent_matches_multi_order(self): weights = pd.Series([.3, .7]) - multi_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY, - self.asset_finder) + multi_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY) multi_test_algo = TradingAlgorithm( script=dedent("""\ from collections import OrderedDict @@ -1820,8 +1819,7 @@ def handle_data(context, data): multi_stats = multi_test_algo.run(self.data_portal) self.assertFalse(multi_blotter.order_batch_called) - batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY, - self.asset_finder) + batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY) batch_test_algo = TradingAlgorithm( script=dedent("""\ from collections import OrderedDict @@ -1864,8 +1862,7 @@ def handle_data(context, data): def test_batch_order_target_percent_filters_null_orders(self): weights = pd.Series([1, 0]) - batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY, - self.asset_finder) + batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY) batch_test_algo = TradingAlgorithm( script=dedent("""\ from collections import OrderedDict @@ -3802,7 +3799,7 @@ class TestOrderCancelation(WithDataPortal, np.copysign(389, direction), daily_positions[0]["amount"], ) - self.assertEqual(1, results.positions[0][0]["sid"].sid) + self.assertEqual(1, results.positions[0][0]["sid"]) # should be an order on day1, but no more orders afterwards np.testing.assert_array_equal([1, 0, 0], @@ -4067,7 +4064,6 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): resources = self.make_data(auto_close_delta, 'daily', capital_base) assets = resources.assets - sids = [asset.sid for asset in assets] final_prices = resources.final_prices # Prices at which we expect our orders to be filled. @@ -4174,14 +4170,14 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): self.test_days[1] )[1] - for sid, txn in zip(sids, initial_fills): + for asset, txn in zip(assets, initial_fills): self.assertDictContainsSubset( { 'amount': order_size, 'commission': None, 'dt': last_minute_of_session, - 'price': initial_fill_prices[sid], - 'sid': sid, + 'price': initial_fill_prices[asset], + 'sid': asset, }, txn, ) @@ -4203,7 +4199,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): 'commission': 0.0, 'dt': assets[0].auto_close_date, 'price': fp0, - 'sid': sids[0], + 'sid': assets[0], 'order_id': None, # Auto-close txns emit Nones for order_id. }, ) @@ -4218,7 +4214,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): 'commission': 0.0, 'dt': assets[1].auto_close_date, 'price': fp1, - 'sid': sids[1], + 'sid': assets[1], 'order_id': None, # Auto-close txns emit Nones for order_id. }, ) @@ -4313,7 +4309,6 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): env = resources.env assets = resources.assets - sids = [a.sid for a in assets] final_prices = resources.final_prices backtest_minutes = resources.trade_data_by_sid[0].index.tolist() @@ -4390,14 +4385,14 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): # the backtest, which is still on the first day in minute mode. initial_fills = transactions.iloc[0] self.assertEqual(len(initial_fills), len(assets)) - for sid, txn in zip(sids, initial_fills): + for asset, txn in zip(assets, initial_fills): self.assertDictContainsSubset( { 'amount': order_size, 'commission': None, 'dt': backtest_minutes[1], - 'price': initial_fill_prices[sid], - 'sid': sid, + 'price': initial_fill_prices[asset], + 'sid': asset, }, txn, ) @@ -4419,7 +4414,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): 'commission': 0.0, 'dt': assets[0].auto_close_date, 'price': fp0, - 'sid': sids[0], + 'sid': assets[0], 'order_id': None, # Auto-close txns emit Nones for order_id. }, ) @@ -4434,7 +4429,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase): 'commission': 0.0, 'dt': assets[1].auto_close_date, 'price': fp1, - 'sid': sids[1], + 'sid': assets[1], 'order_id': None, # Auto-close txns emit Nones for order_id. }, ) diff --git a/tests/test_data_portal.py b/tests/test_data_portal.py index 26391f60..8f08d730 100644 --- a/tests/test_data_portal.py +++ b/tests/test_data_portal.py @@ -401,6 +401,10 @@ class DataPortalTestBase(WithDataPortal, "Asset 10000 had a trade on fourth minute, so should " "return that as the last trade on the fifth.") + def test_get_empty_splits(self): + splits = self.data_portal.get_splits([], self.trading_days[2]) + self.assertEqual([], splits) + class TestDataPortal(DataPortalTestBase): DATA_PORTAL_LAST_AVAILABLE_SESSION = None diff --git a/tests/test_finance.py b/tests/test_finance.py index 5b72449f..40667326 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -187,8 +187,8 @@ class FinanceTestCase(WithLogger, # if present, expect transaction amounts to match orders exactly. complete_fill = params.get('complete_fill') - sid = 1 - metadata = make_simple_equity_info([sid], self.start, self.end) + asset1 = self.asset_finder.retrieve_asset(1) + metadata = make_simple_equity_info([asset1.sid], self.start, self.end) with TempDirectory() as tempdir, \ tmp_trading_env(equities=metadata) as env: @@ -206,7 +206,7 @@ class FinanceTestCase(WithLogger, price_data = np.array([10.1] * len(minutes)) assets = { - sid: pd.DataFrame({ + asset1.sid: pd.DataFrame({ "open": price_data, "high": price_data, "low": price_data, @@ -275,8 +275,7 @@ class FinanceTestCase(WithLogger, else: slippage_func = None - blotter = Blotter(sim_params.data_frequency, self.env.asset_finder, - slippage_func) + blotter = Blotter(sim_params.data_frequency, slippage_func) start_date = sim_params.first_open @@ -305,7 +304,7 @@ class FinanceTestCase(WithLogger, # place an order direction = alternator ** len(order_list) order_id = blotter.order( - blotter.asset_finder.retrieve_asset(sid), + asset1, order_amount * direction, MarketOrder()) order_list.append(blotter.orders[order_id]) @@ -333,7 +332,7 @@ class FinanceTestCase(WithLogger, for i in range(order_count): order = order_list[i] - self.assertEqual(order.sid, sid) + self.assertEqual(order.asset, asset1) self.assertEqual(order.amount, order_amount * alternator ** i) if complete_fill: @@ -351,48 +350,54 @@ class FinanceTestCase(WithLogger, self.assertEqual(len(transactions), expected_txn_count) - cumulative_pos = tracker.position_tracker.positions[sid] + cumulative_pos = tracker.position_tracker.positions[asset1] if total_volume == 0: self.assertIsNone(cumulative_pos) else: self.assertEqual(total_volume, cumulative_pos.amount) - # the open orders should not contain sid. + # the open orders should not contain the asset. oo = blotter.open_orders - self.assertNotIn(sid, oo, "Entry is removed when no open orders") + self.assertNotIn( + asset1, + oo, + "Entry is removed when no open orders" + ) def test_blotter_processes_splits(self): - blotter = Blotter('daily', self.env.asset_finder, - equity_slippage=FixedSlippage()) + blotter = Blotter('daily', equity_slippage=FixedSlippage()) # set up two open limit orders with very low limit prices, # one for sid 1 and one for sid 2 - blotter.order( - blotter.asset_finder.retrieve_asset(1), 100, LimitOrder(10)) - blotter.order( - blotter.asset_finder.retrieve_asset(2), 100, LimitOrder(10)) + asset1 = self.asset_finder.retrieve_asset(1) + asset2 = self.asset_finder.retrieve_asset(2) + asset133 = self.asset_finder.retrieve_asset(133) - # send in a split for sid 2 - blotter.process_splits([(2, 0.3333)]) + blotter.order(asset1, 100, LimitOrder(10)) + blotter.order(asset2, 100, LimitOrder(10)) - for sid in [1, 2]: - order_lists = blotter.open_orders[sid] + # send in splits for assets 133 and 2. We have no open orders for + # asset 133 so it should be ignored. + blotter.process_splits([(asset133, 0.5), (asset2, 0.3333)]) + + for asset in [asset1, asset2]: + order_lists = blotter.open_orders[asset] self.assertIsNotNone(order_lists) self.assertEqual(1, len(order_lists)) - aapl_order = blotter.open_orders[1][0].to_dict() - fls_order = blotter.open_orders[2][0].to_dict() + asset1_order = blotter.open_orders[1][0] + asset2_order = blotter.open_orders[2][0] - # make sure the aapl order didn't change - self.assertEqual(100, aapl_order['amount']) - self.assertEqual(10, aapl_order['limit']) - self.assertEqual(1, aapl_order['sid']) + # make sure the asset1 order didn't change + self.assertEqual(100, asset1_order.amount) + self.assertEqual(10, asset1_order.limit) + self.assertEqual(1, asset1_order.asset) - # make sure the fls order did change + # make sure the asset2 order did change # to 300 shares at 3.33 - self.assertEqual(300, fls_order['amount']) - self.assertEqual(3.33, fls_order['limit']) - self.assertEqual(2, fls_order['sid']) + self.assertEqual(300, asset2_order.amount) + self.assertEqual(3.33, asset2_order.limit) + self.assertEqual(2, asset2_order.asset) class TradingEnvironmentTestCase(WithLogger, diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index f76f9ee5..53f52367 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -276,6 +276,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase): def init_class_fixtures(cls): super(TestSplitPerformance, cls).init_class_fixtures() cls.asset1 = cls.env.asset_finder.retrieve_asset(1) + cls.asset2 = cls.env.asset_finder.retrieve_asset(2) def test_multiple_splits(self): # if multiple positions all have splits at the same time, verify that @@ -284,17 +285,14 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase): self.trading_calendar, self.env) - asset1 = self.asset_finder.retrieve_asset(1) - asset2 = self.asset_finder.retrieve_asset(2) - perf_tracker.position_tracker.positions[1] = \ - Position(asset1, amount=10, cost_basis=10, last_sale_price=11) + Position(self.asset1, amount=10, cost_basis=10, last_sale_price=11) perf_tracker.position_tracker.positions[2] = \ - Position(asset2, amount=10, cost_basis=10, last_sale_price=11) + Position(self.asset2, amount=10, cost_basis=10, last_sale_price=11) leftover_cash = perf_tracker.position_tracker.handle_splits( - [(1, 0.333), (2, 0.333)] + [(self.asset1, 0.333), (self.asset2, 0.333)] ) # we used to have 10 shares that each cost us $10, total $100 @@ -329,7 +327,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase): # set up a split with ratio 3 occurring at the start of the second # day. splits = { - events[1].dt: [(1, 3)] + events[1].dt: [(self.asset1, 3)] } results = calculate_results(self.sim_params, @@ -347,7 +345,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase): # check the last position to make sure it's been updated position = latest_positions[0] - self.assertEqual(1, position['sid']) + self.assertEqual(self.asset1, position['sid']) self.assertEqual(33, position['amount']) self.assertEqual(60, position['cost_basis']) self.assertEqual(60, position['last_sale_price']) @@ -1106,10 +1104,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars, txn1 = create_txn(self.asset1, trades_1[0].dt, 10.0, 100) txn2 = create_txn(self.asset2, trades_1[0].dt, 10.0, -100) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency) pp.position_tracker = pt pt.execute_transaction(txn1) pp.handle_execution(txn1) @@ -1199,10 +1195,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars, self.sim_params, {1: trades}) txn = create_txn(self.asset1, trades[1].dt, 10.0, 1000) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency) pp.position_tracker = pt pt.execute_transaction(txn) @@ -1291,9 +1285,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars, self.sim_params, {1: trades}) txn = create_txn(self.asset1, trades[1].dt, 10.0, 100) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency, period_open=self.sim_params.start_session, period_close=self.sim_params.end_session) @@ -1326,8 +1319,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars, "should be just one position") self.assertEqual( - pp.positions[1].sid, - txn.sid, + pp.positions[1].asset, + txn.asset, "position should be in security with id 1") self.assertEqual( @@ -1410,11 +1403,8 @@ single short-sale transaction""" {1: trades}) txn = create_txn(self.asset1, trades[1].dt, 10.0, -100) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod( - 1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency) pp.position_tracker = pt pt.execute_transaction(txn) @@ -1437,8 +1427,8 @@ single short-sale transaction""" "should be just one position") self.assertEqual( - pp.positions[1].sid, - txn.sid, + pp.positions[1].asset, + txn.asset, "position should be in security from the transaction" ) @@ -1494,8 +1484,8 @@ single short-sale transaction""" ) self.assertEqual( - pp.positions[1].sid, - txn.sid, + pp.positions[1].asset, + txn.asset, "position should be in security from the transaction" ) @@ -1530,10 +1520,10 @@ single short-sale transaction""" ) # now run a performance period encompassing the entire trade sample. - ptTotal = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - ppTotal = perf.PerformancePeriod(1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + ptTotal = perf.PositionTracker(self.sim_params.data_frequency) + ppTotal = perf.PerformancePeriod( + 1000.0, self.sim_params.data_frequency + ) ppTotal.position_tracker = pt ptTotal.execute_transaction(txn) @@ -1556,8 +1546,8 @@ cost of sole txn in test" "should be just one position" ) self.assertEqual( - ppTotal.positions[1].sid, - txn.sid, + ppTotal.positions[1].asset, + txn.asset, "position should be in security from the transaction" ) @@ -1638,10 +1628,8 @@ trade after cover""" short_txn = create_txn(self.asset1, trades[1].dt, 10.0, -100) cover_txn = create_txn(self.asset1, trades[6].dt, 7.0, 100) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency) pp.position_tracker = pt pt.execute_transaction(short_txn) @@ -1725,11 +1713,9 @@ shares in position" self.sim_params, {1: trades}) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) + pt = perf.PositionTracker(self.sim_params.data_frequency) pp = perf.PerformancePeriod( 1000.0, - self.env.asset_finder, self.sim_params.data_frequency, period_open=self.sim_params.start_session, period_close=self.sim_params.sessions[-1] @@ -1792,10 +1778,8 @@ shares in position" self.assertEqual(pp.pnl, -800, "this period goes from +400 to -400") - pt3 = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp3 = perf.PerformancePeriod(1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + pt3 = perf.PositionTracker(self.sim_params.data_frequency) + pp3 = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency) pp3.position_tracker = pt3 average_cost = 0 @@ -1834,7 +1818,7 @@ shares in position" self.create_environment_stuff(num_days=8) history_args = ( - 1, + self.asset1, [10, 9, 11, 8, 9, 12, 13, 14], [200, -100, -100, 100, -300, 100, 500, 400], oneday, @@ -1845,10 +1829,8 @@ shares in position" transactions = factory.create_txn_history(*history_args) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, - self.sim_params.data_frequency) + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency) pp.position_tracker = pt for idx, (txn, cb) in enumerate(zip(transactions, cost_bases)): @@ -1885,9 +1867,8 @@ shares in position" self.sim_params, {1: trades}) txn = create_txn(self.asset1, trades[0].dt, 10.0, 100) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency, period_open=self.sim_params.start_session, period_close=self.sim_params.end_session) @@ -1929,9 +1910,8 @@ shares in position" self.sim_params, {1: trades}) txn = create_txn(self.asset1, trades[0].dt, 10.0, 100) - pt = perf.PositionTracker(self.env.asset_finder, - self.sim_params.data_frequency) - pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, + pt = perf.PositionTracker(self.sim_params.data_frequency) + pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency, period_open=self.sim_params.start_session, period_close=self.sim_params.end_session) @@ -1973,6 +1953,16 @@ class TestPositionTracker(WithTradingEnvironment, ZiplineTestCase): ASSET_FINDER_EQUITY_SIDS = 1, 2 + @classmethod + def init_class_fixtures(cls): + super(TestPositionTracker, cls).init_class_fixtures() + + cls.EQUITY1 = cls.asset_finder.retrieve_asset(1) + cls.EQUITY2 = cls.asset_finder.retrieve_asset(2) + cls.FUTURE3 = cls.asset_finder.retrieve_asset(3) + cls.FUTURE4 = cls.asset_finder.retrieve_asset(4) + cls.FUTURE5 = cls.asset_finder.retrieve_asset(1032201401) + @classmethod def make_futures_info(cls): return pd.DataFrame.from_dict( @@ -1993,8 +1983,7 @@ class TestPositionTracker(WithTradingEnvironment, """ sim_params = factory.create_simulation_parameters(num_days=4) - pt = perf.PositionTracker(self.env.asset_finder, - sim_params.data_frequency) + pt = perf.PositionTracker(sim_params.data_frequency) pos_stats = pt.stats() stats = [ @@ -2015,17 +2004,27 @@ class TestPositionTracker(WithTradingEnvironment, self.assertNotIsInstance(val, (bool, np.bool_)) def test_position_values_and_exposures(self): - pt = perf.PositionTracker(self.env.asset_finder, None) + pt = perf.PositionTracker(None) dt = pd.Timestamp("1984/03/06 3:00PM") - pos1 = perf.Position(1, amount=np.float64(10.0), - last_sale_date=dt, last_sale_price=10) - pos2 = perf.Position(2, amount=np.float64(-20.0), - last_sale_date=dt, last_sale_price=10) - pos3 = perf.Position(3, amount=np.float64(30.0), - last_sale_date=dt, last_sale_price=10) - pos4 = perf.Position(4, amount=np.float64(-40.0), - last_sale_date=dt, last_sale_price=10) - pt.update_positions({1: pos1, 2: pos2, 3: pos3, 4: pos4}) + pt.update_position( + self.EQUITY1, amount=np.float64(10.0), + last_sale_date=dt, last_sale_price=10 + ) + + pt.update_position( + self.EQUITY2, amount=np.float64(-20.0), + last_sale_date=dt, last_sale_price=10 + ) + + pt.update_position( + self.FUTURE3, amount=np.float64(30.0), + last_sale_date=dt, last_sale_price=10 + ) + + pt.update_position( + self.FUTURE4, amount=np.float64(-40.0), + last_sale_date=dt, last_sale_price=10 + ) # Test long-only methods pos_stats = pt.stats() @@ -2046,25 +2045,71 @@ class TestPositionTracker(WithTradingEnvironment, self.assertEqual(100 + 200 + 300000 + 400000, pos_stats.gross_exposure) self.assertEqual(100 - 200 + 300000 - 400000, pos_stats.net_exposure) - def test_update_positions(self): - pt = perf.PositionTracker(self.env.asset_finder, None) - dt = pd.Timestamp("2014/01/01 3:00PM") - pos1 = perf.Position(1, amount=np.float64(10.0), - last_sale_date=dt, last_sale_price=10) - pos2 = perf.Position(2, amount=np.float64(-20.0), - last_sale_date=dt, last_sale_price=10) - pos3 = perf.Position(1032201401, amount=np.float64(30.0), - last_sale_date=dt, last_sale_price=100) + def test_cost_basis(self): + dt = pd.Timestamp("2015-12-10 15:00", tz='UTC') - # Call update_positions twice. When the second call is made, - # self.positions will already contain data. The order of this data - # needs to be preserved so that it is consistent with the order of the - # data stored in the multipliers OrderedDict()'s. If self.positions - # were to be stored as a dict, then its order could change in arbitrary - # ways when the second update_positions call is made. Hence we also - # store it as an OrderedDict. - pt.update_positions({1: pos1, 1032201401: pos3}) - pt.update_positions({2: pos2}) + equity_pos = perf.Position( + self.EQUITY1, + amount=10, + last_sale_date=dt, + cost_basis=10, + last_sale_price=11, + ) + + future_pos = perf.Position( + self.FUTURE3, + amount=10, + last_sale_date=dt, + cost_basis=10, + last_sale_price=11, + ) + + self.assertEqual(10, equity_pos.cost_basis) + + # send a $5 commission to the equity position. Spread out over 10 + # shares, that bumps the cost basis by $0.50. + equity_pos.adjust_commission_cost_basis(self.EQUITY1, 5) + self.assertEqual(10.5, equity_pos.cost_basis) + + self.assertEqual(10, future_pos.cost_basis) + + # send a $5k commission to the futures position. since self.FUTURE3 + # has a contract size (multipler) of 1000, this should result in a + # $10.5 updated cost basis. (5000 / 1000 = $5, spread out over 10 + # contracts, is $0.50 extra per contract). + future_pos.adjust_commission_cost_basis(self.FUTURE3, 5000) + self.assertEqual(10.5, future_pos.cost_basis) + + def test_update_positions(self): + pt = perf.PositionTracker(None) + dt = pd.Timestamp("2014/01/01 3:00PM") + # pos1 = perf.Position(self.EQUITY1, amount=np.float64(10.0), + # last_sale_date=dt, last_sale_price=10) + # pos2 = perf.Position(self.EQUITY2, amount=np.float64(-20.0), + # last_sale_date=dt, last_sale_price=10) + # pos3 = perf.Position(self.FUTURE5, amount=np.float64(30.0), + # last_sale_date=dt, last_sale_price=100) + + pt.update_position( + self.EQUITY1, + amount=np.float64(10.0), + last_sale_price=10, + last_sale_date=dt + ) + + pt.update_position( + self.EQUITY2, + amount=np.float64(-20.0), + last_sale_price=10, + last_sale_date=dt + ) + + pt.update_position( + self.FUTURE5, + amount=np.float64(30.0), + last_sale_price=100, + last_sale_date=dt + ) pos_stats = pt.stats() # Test long-only methods @@ -2087,33 +2132,20 @@ class TestPositionTracker(WithTradingEnvironment, self.assertEqual(100 + 150000 - 200, pos_stats.net_exposure) def test_close_position(self): - future_sid = 1032201401 - equity_sid = 1 - pt = perf.PositionTracker(self.env.asset_finder, None) + pt = perf.PositionTracker(None) dt = pd.Timestamp('2017/01/04 3:00PM') - pos1 = perf.Position( - sid=future_sid, - amount=np.float64(30.0), - last_sale_date=dt, - last_sale_price=100, - ) - pos2 = perf.Position( - sid=equity_sid, - amount=np.float64(10.0), - last_sale_date=dt, - last_sale_price=10, + pt.update_position( + asset=self.FUTURE5, amount=np.float64(30.0), + last_sale_date=dt, last_sale_price=100 ) - # Update the positions dictionary with `future_sid` first. The order - # matters because it affects the multipliers dictionaries, which are - # OrderedDicts. If `future_sid` is not removed from the multipliers - # dictionaries, equities will hit the incorrect multiplier when - # computing `pt.stats()`. - pt.update_positions({future_sid: pos1, equity_sid: pos2}) + pt.update_position( + asset=self.EQUITY1, amount=np.float64(10.0), + last_sale_date=dt, last_sale_price=10 + ) - asset_to_close = self.env.asset_finder.retrieve_asset(future_sid) - txn = create_txn(asset_to_close, dt, 100, -30) + txn = create_txn(self.FUTURE5, dt, 100, -30) pt.execute_transaction(txn) pos_stats = pt.stats() diff --git a/zipline/algorithm.py b/zipline/algorithm.py index 476dc706..8ec1883b 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -322,7 +322,6 @@ class TradingAlgorithm(object): if not self.blotter: self.blotter = Blotter( data_frequency=self.data_frequency, - asset_finder=self.asset_finder, # Default to NeverCancel in zipline cancel_policy=self.cancel_policy, ) diff --git a/zipline/data/data_portal.py b/zipline/data/data_portal.py index 39250ef1..bd4cb61b 100644 --- a/zipline/data/data_portal.py +++ b/zipline/data/data_portal.py @@ -1121,25 +1121,25 @@ class DataPortal(object): self._asset_end_dates[sid] = asset.end_date - def get_splits(self, sids, dt): + def get_splits(self, assets, dt): """ Returns any splits for the given sids and the given dt. Parameters ---------- - sids : container - Sids for which we want splits. + assets : container + Assets for which we want splits. dt : pd.Timestamp The date for which we are checking for splits. Note: this is expected to be midnight UTC. Returns ------- - splits : list[(int, float)] - List of splits, where each split is a (sid, ratio) tuple. + splits : list[(asset, float)] + List of splits, where each split is a (asset, ratio) tuple. """ - if self._adjustment_reader is None or not sids: - return {} + if self._adjustment_reader is None or not assets: + return [] # convert dt to # of seconds since epoch, because that's what we use # in the adjustments db @@ -1149,7 +1149,9 @@ class DataPortal(object): "SELECT sid, ratio FROM SPLITS WHERE effective_date = ?", (seconds,)).fetchall() - splits = [split for split in splits if split[0] in sids] + splits = [split for split in splits if split[0] in assets] + splits = [(self.asset_finder.retrieve_asset(split[0]), split[1]) + for split in splits] return splits diff --git a/zipline/errors.py b/zipline/errors.py index dfd0a294..04080510 100644 --- a/zipline/errors.py +++ b/zipline/errors.py @@ -665,18 +665,6 @@ class UnsupportedDatetimeFormat(ZiplineError): "coercible to a pandas.Timestamp object.") -class PositionTrackerMissingAssetFinder(ZiplineError): - """ - Raised by a PositionTracker if it is asked to update an Asset but does not - have an AssetFinder - """ - msg = ( - "PositionTracker attempted to update its Asset information but does " - "not have an AssetFinder. This may be caused by a failure to properly " - "de-serialize a TradingAlgorithm." - ) - - class AssetDBVersionError(ZiplineError): """ Raised by an AssetDBWriter or AssetFinder if the version number in the diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py index 9a61e06a..11d58a11 100644 --- a/zipline/finance/blotter.py +++ b/zipline/finance/blotter.py @@ -18,7 +18,7 @@ from copy import copy from six import iteritems -from zipline.assets import Equity, Future +from zipline.assets import Equity, Future, Asset from zipline.finance.order import Order from zipline.finance.slippage import VolumeShareSlippage from zipline.finance.commission import ( @@ -27,26 +27,22 @@ from zipline.finance.commission import ( PerTrade, ) from zipline.finance.cancel_policy import NeverCancel +from zipline.utils.input_validation import expect_types log = Logger('Blotter') warning_logger = Logger('AlgoWarning') class Blotter(object): - def __init__(self, data_frequency, asset_finder, equity_slippage=None, + def __init__(self, data_frequency, equity_slippage=None, future_slippage=None, equity_commission=None, future_commission=None, cancel_policy=None): - # these orders are aggregated by sid + # these orders are aggregated by asset self.open_orders = defaultdict(list) # keep a dict of orders by their own id self.orders = {} - # all our legacy order management code works with integer sids. - # this lets us convert those to assets when needed. ideally, we'd just - # revamp all the legacy code to work with assets. - self.asset_finder = asset_finder - # holding orders that have come in since the last event. self.new_orders = [] self.current_dt = None @@ -88,7 +84,8 @@ class Blotter(object): def set_date(self, dt): self.current_dt = dt - def order(self, sid, amount, style, order_id=None): + @expect_types(asset=Asset) + def order(self, asset, amount, style, order_id=None): """Place an order. Parameters @@ -114,10 +111,10 @@ class Blotter(object): ----- amount > 0 :: Buy/Cover amount < 0 :: Sell/Short - Market order: order(sid, amount) - Limit order: order(sid, amount, style=LimitOrder(limit_price)) - Stop order: order(sid, amount, style=StopOrder(stop_price)) - StopLimit order: order(sid, amount, style=StopLimitOrder(limit_price, + Market order: order(asset, amount) + Limit order: order(asset, amount, style=LimitOrder(limit_price)) + Stop order: order(asset, amount, style=StopOrder(stop_price)) + StopLimit order: order(asset, amount, style=StopLimitOrder(limit_price, stop_price)) """ # something could be done with amount to further divide @@ -136,14 +133,14 @@ class Blotter(object): is_buy = (amount > 0) order = Order( dt=self.current_dt, - sid=sid, + asset=asset, amount=amount, stop=style.get_stop_price(is_buy), limit=style.get_limit_price(is_buy), id=order_id ) - self.open_orders[order.sid].append(order) + self.open_orders[order.asset].append(order) self.orders[order.id] = order self.new_orders.append(order) @@ -177,7 +174,7 @@ class Blotter(object): cur_order = self.orders[order_id] if cur_order.open: - order_list = self.open_orders[cur_order.sid] + order_list = self.open_orders[cur_order.asset] if cur_order in order_list: order_list.remove(cur_order) @@ -217,7 +214,7 @@ class Blotter(object): 'filled by the end of day and ' 'were canceled.'.format( order_amt=order.amount, - order_sym=order.sid.symbol, + order_sym=order.asset.symbol, order_filled=order.filled, order_failed=order.amount - order.filled, ) @@ -231,7 +228,7 @@ class Blotter(object): 'filled by the end of day and ' 'were canceled.'.format( order_amt=order.amount, - order_sym=order.sid.symbol, + order_sym=order.asset.symbol, order_filled=-1 * order.filled, order_failed=-1 * (order.amount - order.filled), ) @@ -242,7 +239,7 @@ class Blotter(object): '{order_sym} failed to fill by the end of day ' 'and was canceled.'.format( order_amt=order.amount, - order_sym=order.sid.symbol, + order_sym=order.asset.symbol, ) ) @@ -268,7 +265,7 @@ class Blotter(object): cur_order = self.orders[order_id] - order_list = self.open_orders[cur_order.sid] + order_list = self.open_orders[cur_order.asset] if cur_order in order_list: order_list.remove(cur_order) @@ -306,20 +303,19 @@ class Blotter(object): Parameters ---------- splits: list - A list of splits. Each split is a tuple of (sid, ratio). + A list of splits. Each split is a tuple of (asset, ratio). Returns ------- None """ - for split in splits: - sid = split[0] - if sid not in self.open_orders: - return + for asset, ratio in splits: + if asset not in self.open_orders: + continue - orders_to_modify = self.open_orders[sid] + orders_to_modify = self.open_orders[asset] for order in orders_to_modify: - order.handle_split(split[1]) + order.handle_split(ratio) def get_transactions(self, bar_data): """ @@ -344,7 +340,7 @@ class Blotter(object): commissions_list: List commissions_list: list of commissions resulting from filling the - open orders. A commission is an object with "sid" and "cost" + open orders. A commission is an object with "asset" and "cost" parameters. closed_orders: List @@ -356,11 +352,7 @@ class Blotter(object): commissions = [] if self.open_orders: - assets = self.asset_finder.retrieve_all(self.open_orders) - asset_dict = {asset.sid: asset for asset in assets} - - for sid, asset_orders in iteritems(self.open_orders): - asset = asset_dict[sid] + for asset, asset_orders in iteritems(self.open_orders): slippage = self.slippage_models[type(asset)] for order, txn in \ @@ -370,7 +362,7 @@ class Blotter(object): if additional_commission > 0: commissions.append({ - "sid": order.sid, + "asset": order.asset, "order": order, "cost": additional_commission }) @@ -401,15 +393,15 @@ class Blotter(object): """ # remove all closed orders from our open_orders dict for order in closed_orders: - sid = order.sid - sid_orders = self.open_orders[sid] + asset = order.asset + asset_orders = self.open_orders[asset] try: - sid_orders.remove(order) + asset_orders.remove(order) except ValueError: continue - # now clear out the sids from our open_orders dict that have + # now clear out the assets from our open_orders dict that have # zero open orders - for sid in list(self.open_orders.keys()): - if len(self.open_orders[sid]) == 0: - del self.open_orders[sid] + for asset in list(self.open_orders.keys()): + if len(self.open_orders[asset]) == 0: + del self.open_orders[asset] diff --git a/zipline/finance/order.py b/zipline/finance/order.py index d326f206..47ee24c3 100644 --- a/zipline/finance/order.py +++ b/zipline/finance/order.py @@ -20,6 +20,7 @@ from six import text_type import zipline.protocol as zp from zipline.assets import Asset from zipline.utils.enum import enum +from zipline.utils.input_validation import expect_types ORDER_STATUS = enum( 'OPEN', @@ -34,35 +35,35 @@ BUY = 1 << 1 STOP = 1 << 2 LIMIT = 1 << 3 -ORDER_FIELDS_TO_IGNORE = {'type', 'direction', '_status'} +ORDER_FIELDS_TO_IGNORE = {'type', 'direction', '_status', 'asset'} class Order(object): # using __slots__ to save on memory usage. Simulations can create many # Order objects and we keep them all in memory, so it's worthwhile trying # to cut down on the memory footprint of this object. - __slots__ = ["id", "dt", "reason", "created", "sid", "amount", "filled", + __slots__ = ["id", "dt", "reason", "created", "asset", "amount", "filled", "commission", "_status", "stop", "limit", "stop_reached", "limit_reached", "direction", "type", "broker_order_id"] - def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0, + @expect_types(asset=Asset) + def __init__(self, dt, asset, amount, stop=None, limit=None, filled=0, commission=0, id=None): """ @dt - datetime.datetime that the order was placed - @sid - asset for the order. called sid for historical reasons. + @asset - asset for the order. @amount - the number of shares to buy/sell a positive sign indicates a buy a negative sign indicates a sell @filled - how many shares of the order have been filled so far """ - assert isinstance(sid, Asset) # get a string representation of the uuid. self.id = self.make_id() if id is None else id self.dt = dt self.reason = None self.created = dt - self.sid = sid + self.asset = asset self.amount = amount self.filled = filled self.commission = commission @@ -86,9 +87,18 @@ class Order(object): if self.broker_order_id is None: del dct['broker_order_id'] + # Adding 'sid' for backwards compatibility with downstream consumers. + dct['sid'] = self.asset dct['status'] = self.status + return dct + @property + def sid(self): + # For backwards compatibility because we pass this object to + # custom slippage models. + return self.asset + def to_api_obj(self): pydict = self.to_dict() obj = zp.Order(initial_values=pydict) @@ -215,14 +225,6 @@ class Order(object): def open(self): return self.status in [ORDER_STATUS.OPEN, ORDER_STATUS.HELD] - @property - def asset(self): - """ - Convenience accessor to hide away a historical API that we'd like to - change at some point. - """ - return self.sid - @property def triggered(self): """ diff --git a/zipline/finance/performance/period.py b/zipline/finance/performance/period.py index cc17f374..89ddbfd8 100644 --- a/zipline/finance/performance/period.py +++ b/zipline/finance/performance/period.py @@ -139,7 +139,6 @@ class PerformancePeriod(object): def __init__( self, starting_cash, - asset_finder, data_frequency, period_open=None, period_close=None, @@ -148,7 +147,6 @@ class PerformancePeriod(object): serialize_positions=True, name=None): - self.asset_finder = asset_finder self.data_frequency = data_frequency # Start and end of the entire period @@ -185,10 +183,6 @@ class PerformancePeriod(object): self._account_store = zp.Account() self.serialize_positions = serialize_positions - # This dict contains the known cash flow multipliers for sids and is - # keyed on sid - self._execution_cash_flow_multipliers = {} - _position_tracker = None def initialize(self, starting_cash, starting_value, starting_exposure): @@ -362,7 +356,7 @@ class PerformancePeriod(object): def handle_execution(self, txn): self.cash_flow += self._calculate_execution_cash_flow(txn) - asset = self.asset_finder.retrieve_asset(txn.sid) + asset = txn.asset if isinstance(asset, Future): try: old_price = self._payout_last_sale_prices[asset] @@ -385,25 +379,15 @@ class PerformancePeriod(object): except KeyError: self.processed_transactions[txn.dt] = [txn] - def _calculate_execution_cash_flow(self, txn): + @staticmethod + def _calculate_execution_cash_flow(txn): """ Calculates the cash flow from executing the given transaction """ - # Check if the multiplier is cached. If it is not, look up the asset - # and cache the multiplier. - try: - multiplier = self._execution_cash_flow_multipliers[txn.sid] - except KeyError: - asset = self.asset_finder.retrieve_asset(txn.sid) - # Futures experience no cash flow on transactions - if isinstance(asset, Future): - multiplier = 0 - else: - multiplier = 1 - self._execution_cash_flow_multipliers[txn.sid] = multiplier + if txn.asset is Future: + return 0.0 - # Calculate and return the cash flow given the multiplier - return -1 * txn.price * txn.amount * multiplier + return -1 * txn.price * txn.amount # backwards compat. TODO: remove? @property diff --git a/zipline/finance/performance/position.py b/zipline/finance/performance/position.py index 7c818d59..a73ee913 100644 --- a/zipline/finance/performance/position.py +++ b/zipline/finance/performance/position.py @@ -20,7 +20,7 @@ Position Tracking +-----------------+----------------------------------------------------+ | key | value | +=================+====================================================+ - | sid | the sid for the asset held in this position | + | asset | the asset held in this position | +-----------------+----------------------------------------------------+ | amount | whole number of shares in the position | +-----------------+----------------------------------------------------+ @@ -37,15 +37,19 @@ from collections import OrderedDict import numpy as np import logbook +from zipline.assets import Future, Asset +from zipline.utils.input_validation import expect_types + log = logbook.Logger('Performance') class Position(object): - def __init__(self, sid, amount=0, cost_basis=0.0, + @expect_types(asset=Asset) + def __init__(self, asset, amount=0, cost_basis=0.0, last_sale_price=0.0, last_sale_date=None): - self.sid = sid + self.asset = asset self.amount = amount self.cost_basis = cost_basis # per share self.last_sale_price = last_sale_price @@ -72,15 +76,16 @@ class Position(object): ) } - def handle_split(self, sid, ratio): + @expect_types(asset=Asset) + def handle_split(self, asset, ratio): """ Update the position by the split ratio, and return the resulting fractional share that will be converted into cash. Returns the unused cash. """ - if self.sid != sid: - raise Exception("updating split with the wrong sid!") + if self.asset != asset: + raise Exception("updating split with the wrong asset!") # adjust the # of shares by the ratio # (if we had 100 shares, and the ratio is 3, @@ -113,9 +118,9 @@ class Position(object): return return_cash def update(self, txn): - if self.sid != txn.sid: + if self.asset != txn.asset: raise Exception('updating position with txn for a ' - 'different sid') + 'different asset') total_shares = self.amount + txn.amount @@ -145,7 +150,8 @@ class Position(object): self.amount = total_shares - def adjust_commission_cost_basis(self, sid, cost): + @expect_types(asset=Asset) + def adjust_commission_cost_basis(self, asset, cost): """ A note about cost-basis in zipline: all positions are considered to share a cost basis, even if they were executed in different @@ -156,8 +162,8 @@ class Position(object): all shares in a position. """ - if sid != self.sid: - raise Exception('Updating a commission for a different sid?') + if asset != self.asset: + raise Exception('Updating a commission for a different asset?') if cost == 0.0: return @@ -167,14 +173,18 @@ class Position(object): return prev_cost = self.cost_basis * self.amount - new_cost = prev_cost + cost + if isinstance(asset, Future): + cost_to_use = cost / asset.multiplier + else: + cost_to_use = cost + new_cost = prev_cost + cost_to_use self.cost_basis = new_cost / self.amount def __repr__(self): - template = "sid: {sid}, amount: {amount}, cost_basis: {cost_basis}, \ + template = "asset: {asset}, amount: {amount}, cost_basis: {cost_basis}, \ last_sale_price: {last_sale_price}" return template.format( - sid=self.sid, + asset=self.asset, amount=self.amount, cost_basis=self.cost_basis, last_sale_price=self.last_sale_price @@ -186,7 +196,7 @@ last_sale_price: {last_sale_price}" Returns a dict object of the form: """ return { - 'sid': self.sid, + 'sid': self.asset, 'amount': self.amount, 'cost_basis': self.cost_basis, 'last_sale_price': self.last_sale_price diff --git a/zipline/finance/performance/position_tracker.py b/zipline/finance/performance/position_tracker.py index 417c7392..67e547dc 100644 --- a/zipline/finance/performance/position_tracker.py +++ b/zipline/finance/performance/position_tracker.py @@ -19,21 +19,17 @@ import logbook import numpy as np from collections import namedtuple from math import isnan -from zipline.finance.performance.position import Position -from zipline.finance.transaction import Transaction -try: - # optional cython based OrderedDict - from cyordereddict import OrderedDict -except ImportError: - from collections import OrderedDict from six import iteritems, itervalues +from zipline.finance.performance.position import Position +from zipline.finance.transaction import Transaction +from zipline.utils.input_validation import expect_types import zipline.protocol as zp from zipline.assets import ( - Equity, Future + Future, + Asset ) -from zipline.errors import PositionTrackerMissingAssetFinder from . position import positiondict log = logbook.Logger('Performance') @@ -52,18 +48,17 @@ PositionStats = namedtuple('PositionStats', 'net_value']) -def calc_position_values(amounts, - last_sale_prices, - value_multipliers): - iter_amount_price_multiplier = zip( - amounts, - last_sale_prices, - itervalues(value_multipliers), - ) - return [ - price * amount * multiplier for - price, amount, multiplier in iter_amount_price_multiplier - ] +def calc_position_values(positions): + values = [] + + for position in positions: + if isinstance(position.asset, Future): + # Futures don't have an inherent position value. + values.append(0.0) + else: + values.append(position.last_sale_price * position.amount) + + return values def calc_net(values): @@ -71,18 +66,18 @@ def calc_net(values): return sum(values, np.float64()) -def calc_position_exposures(amounts, - last_sale_prices, - exposure_multipliers): - iter_amount_price_multiplier = zip( - amounts, - last_sale_prices, - itervalues(exposure_multipliers), - ) - return [ - price * amount * multiplier for - price, amount, multiplier in iter_amount_price_multiplier - ] +def calc_position_exposures(positions): + exposures = [] + + for position in positions: + exposure = position.amount * position.last_sale_price + + if isinstance(position.asset, Future): + exposure *= position.asset.multiplier + + exposures.append(exposure) + + return exposures def calc_long_value(position_values): @@ -119,55 +114,26 @@ def calc_gross_value(long_value, short_value): class PositionTracker(object): - def __init__(self, asset_finder, data_frequency): - self.asset_finder = asset_finder - - # sid => position object + def __init__(self, data_frequency): + # asset => position object self.positions = positiondict() - # Arrays for quick calculations of positions value - self._position_value_multipliers = OrderedDict() - self._position_exposure_multipliers = OrderedDict() self._unpaid_dividends = {} self._unpaid_stock_dividends = {} self._positions_store = zp.Positions() self.data_frequency = data_frequency - def _update_asset(self, sid): - try: - self._position_value_multipliers[sid] - self._position_exposure_multipliers[sid] - except KeyError: - # Check if there is an AssetFinder - if self.asset_finder is None: - raise PositionTrackerMissingAssetFinder() - - # Collect the value multipliers from applicable sids - asset = self.asset_finder.retrieve_asset(sid) - if isinstance(asset, Equity): - self._position_value_multipliers[sid] = 1 - self._position_exposure_multipliers[sid] = 1 - if isinstance(asset, Future): - self._position_value_multipliers[sid] = 0 - self._position_exposure_multipliers[sid] = asset.multiplier - - def update_positions(self, positions): - # update positions in batch - self.positions.update(positions) - for sid, pos in iteritems(positions): - self._update_asset(sid) - - def update_position(self, sid, amount=None, last_sale_price=None, + @expect_types(asset=Asset) + def update_position(self, asset, amount=None, last_sale_price=None, last_sale_date=None, cost_basis=None): - if sid not in self.positions: - position = Position(sid) - self.positions[sid] = position + if asset not in self.positions: + position = Position(asset) + self.positions[asset] = position else: - position = self.positions[sid] + position = self.positions[asset] if amount is not None: position.amount = amount - self._update_asset(sid=sid) if last_sale_price is not None: position.last_sale_price = last_sale_price if last_sale_date is not None: @@ -178,36 +144,31 @@ class PositionTracker(object): def execute_transaction(self, txn): # Update Position # ---------------- - sid = txn.sid + asset = txn.asset - if sid not in self.positions: - position = Position(sid) - self.positions[sid] = position + if asset not in self.positions: + position = Position(asset) + self.positions[asset] = position else: - position = self.positions[sid] + position = self.positions[asset] position.update(txn) if position.amount == 0: - # if this position now has 0 shares, remove it from our internal - # bookkeeping. - del self.positions[sid] - del self._position_value_multipliers[sid] - del self._position_exposure_multipliers[sid] + del self.positions[asset] try: # if this position exists in our user-facing dictionary, # remove it as well. - del self._positions_store[sid] + del self._positions_store[asset] except KeyError: pass - else: - self._update_asset(sid) - def handle_commission(self, sid, cost): + @expect_types(asset=Asset) + def handle_commission(self, asset, cost): # Adjust the cost basis of the stock if we own it - if sid in self.positions: - self.positions[sid].adjust_commission_cost_basis(sid, cost) + if asset in self.positions: + self.positions[asset].adjust_commission_cost_basis(asset, cost) def handle_splits(self, splits): """ @@ -216,7 +177,7 @@ class PositionTracker(object): Parameters ---------- splits: list - A list of splits. Each split is a tuple of (sid, ratio). + A list of splits. Each split is a tuple of (asset, ratio). Returns ------- @@ -225,14 +186,12 @@ class PositionTracker(object): """ total_leftover_cash = 0 - for split in splits: - sid = split[0] - if sid in self.positions: + for asset, ratio in splits: + if asset in self.positions: # Make the position object handle the split. It returns the # leftover cash from a fractional share, if there is any. - position = self.positions[sid] - leftover_cash = position.handle_split(sid, split[1]) - self._update_asset(split[0]) + position = self.positions[asset] + leftover_cash = position.handle_split(asset, ratio) total_leftover_cash += leftover_cash return total_leftover_cash @@ -309,7 +268,6 @@ class PositionTracker(object): Position(payment_asset) position.amount += share_count - self._update_asset(payment_asset) return net_cash_payment @@ -326,7 +284,7 @@ class PositionTracker(object): price = self.positions.get(asset).last_sale_price txn = Transaction( - sid=asset, + asset=asset, amount=(-1 * amount), dt=dt, price=price, @@ -339,20 +297,20 @@ class PositionTracker(object): positions = self._positions_store - for sid, pos in iteritems(self.positions): + for asset, pos in iteritems(self.positions): if pos.amount == 0: # Clear out the position if it has become empty since the last # time get_positions was called. Catching the KeyError is - # faster than checking `if sid in positions`, and this can be + # faster than checking `if asset in positions`, and this can be # potentially called in a tight inner loop. try: - del positions[sid] + del positions[asset] except KeyError: pass continue - position = zp.Position(sid) + position = zp.Position(asset) position.amount = pos.amount position.cost_basis = pos.cost_basis position.last_sale_price = pos.last_sale_price @@ -360,13 +318,13 @@ class PositionTracker(object): # Adds the new position if we didn't have one before, or overwrite # one we have currently - positions[sid] = position + positions[asset] = position return positions def get_positions_list(self): positions = [] - for sid, pos in iteritems(self.positions): + for asset, pos in iteritems(self.positions): if pos.amount != 0: positions.append(pos.to_dict()) return positions @@ -401,16 +359,9 @@ class PositionTracker(object): amounts.append(pos.amount) last_sale_prices.append(pos.last_sale_price) - position_values = calc_position_values( - amounts, - last_sale_prices, - self._position_value_multipliers - ) - + position_values = calc_position_values(itervalues(self.positions)) position_exposures = calc_position_exposures( - amounts, - last_sale_prices, - self._position_exposure_multipliers + itervalues(self.positions) ) long_value = calc_long_value(position_values) diff --git a/zipline/finance/performance/tracker.py b/zipline/finance/performance/tracker.py index ffef7ecd..6546429c 100644 --- a/zipline/finance/performance/tracker.py +++ b/zipline/finance/performance/tracker.py @@ -98,7 +98,6 @@ class PerformanceTracker(object): self.emission_rate = sim_params.emission_rate self.position_tracker = PositionTracker( - asset_finder=env.asset_finder, data_frequency=self.sim_params.data_frequency ) @@ -141,7 +140,6 @@ class PerformanceTracker(object): keep_orders=False, # don't serialize positions for cumulative period serialize_positions=False, - asset_finder=self.asset_finder, name="Cumulative" ) self.cumulative_performance.position_tracker = self.position_tracker @@ -157,7 +155,6 @@ class PerformanceTracker(object): keep_transactions=True, keep_orders=True, serialize_positions=True, - asset_finder=self.asset_finder, name="Daily" ) self.todays_performance.position_tracker = self.position_tracker @@ -275,10 +272,10 @@ class PerformanceTracker(object): self.todays_performance.record_order(event) def process_commission(self, commission): - sid = commission['sid'] + asset = commission['asset'] cost = commission['cost'] - self.position_tracker.handle_commission(sid, cost) + self.position_tracker.handle_commission(asset, cost) self.cumulative_performance.handle_commission(cost) self.todays_performance.handle_commission(cost) diff --git a/zipline/finance/transaction.py b/zipline/finance/transaction.py index dcd2fce2..94b03300 100644 --- a/zipline/finance/transaction.py +++ b/zipline/finance/transaction.py @@ -18,14 +18,13 @@ from copy import copy from zipline.assets import Asset from zipline.protocol import DATASOURCE_TYPE +from zipline.utils.input_validation import expect_types class Transaction(object): - - def __init__(self, sid, amount, dt, price, order_id, commission=None): - assert isinstance(sid, Asset) - - self.sid = sid + @expect_types(asset=Asset) + def __init__(self, asset, amount, dt, price, order_id, commission=None): + self.asset = asset self.amount = amount self.dt = dt self.price = price @@ -39,6 +38,11 @@ class Transaction(object): def to_dict(self): py = copy(self.__dict__) del py['type'] + del py['asset'] + + # Adding 'sid' for backwards compatibility with downstrean consumers. + py['sid'] = self.asset + return py @@ -53,7 +57,7 @@ def create_transaction(order, dt, price, amount): raise Exception("Transaction magnitude must be at least 1.") transaction = Transaction( - sid=order.sid, + asset=order.asset, amount=int(amount), dt=dt, price=price, diff --git a/zipline/protocol.py b/zipline/protocol.py index 8013251e..07f814e4 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -16,6 +16,8 @@ from warnings import warn import pandas as pd +from zipline.assets import Asset +from zipline.utils.input_validation import expect_types from .utils.enum import enum from zipline._protocol import BarData # noqa @@ -225,14 +227,19 @@ class Account(object): class Position(object): - - def __init__(self, sid): - self.sid = sid + @expect_types(asset=Asset) + def __init__(self, asset): + self.asset = asset self.amount = 0 self.cost_basis = 0.0 # per share self.last_sale_price = 0.0 self.last_sale_date = None + @property + def sid(self): + # for backwards compatibility + return self.asset + def __repr__(self): return "Position({0})".format(self.__dict__) diff --git a/zipline/testing/core.py b/zipline/testing/core.py index abfe110b..47eee138 100644 --- a/zipline/testing/core.py +++ b/zipline/testing/core.py @@ -1512,10 +1512,8 @@ def ensure_doctest(f, name=None): class RecordBatchBlotter(Blotter): """Blotter that tracks how its batch_order method was called. """ - def __init__(self, data_frequency, asset_finder): - super(RecordBatchBlotter, self).__init__( - data_frequency, asset_finder, - ) + def __init__(self, data_frequency): + super(RecordBatchBlotter, self).__init__(data_frequency) self.order_batch_called = [] def batch_order(self, *args, **kwargs): diff --git a/zipline/utils/factory.py b/zipline/utils/factory.py index 805213f6..a529ed06 100644 --- a/zipline/utils/factory.py +++ b/zipline/utils/factory.py @@ -21,6 +21,8 @@ import pandas as pd import numpy as np from datetime import timedelta, datetime +from zipline.assets import Asset +from zipline.finance.transaction import Transaction from zipline.protocol import Event, DATASOURCE_TYPE from zipline.sources import SpecificEquityTrades from zipline.finance.trading import SimulationParameters @@ -30,7 +32,7 @@ from zipline.data.loader import ( # For backwards compatibility load_bars_from_yahoo, ) from zipline.utils.calendars import get_calendar - +from zipline.utils.input_validation import expect_types __all__ = ['load_from_yahoo', 'load_bars_from_yahoo'] @@ -150,27 +152,27 @@ def create_split(sid, ratio, date): }) -def create_txn(sid, price, amount, datetime): - txn = Event({ - 'sid': sid, - 'amount': amount, - 'dt': datetime, - 'price': price, - 'type': DATASOURCE_TYPE.TRANSACTION, - 'source_id': 'MockTransactionSource' - }) - return txn +@expect_types(asset=Asset) +def create_txn(asset, price, amount, datetime, order_id): + return Transaction( + asset=asset, + price=price, + amount=amount, + dt=datetime, + order_id=order_id, + ) -def create_txn_history(sid, priceList, amtList, interval, sim_params, +@expect_types(asset=Asset) +def create_txn_history(asset, priceList, amtList, interval, sim_params, trading_calendar): txns = [] current = sim_params.first_open for price, amount in zip(priceList, amtList): - current = get_next_trading_dt(current, interval, trading_calendar) + dt = get_next_trading_dt(current, interval, trading_calendar) - txns.append(create_txn(sid, price, amount, current)) + txns.append(create_txn(asset, price, amount, dt, None)) current = current + interval return txns