diff --git a/tests/finance/test_blotter.py b/tests/finance/test_blotter.py index 2a031c81..adfc5eee 100644 --- a/tests/finance/test_blotter.py +++ b/tests/finance/test_blotter.py @@ -154,7 +154,7 @@ class BlotterTestCase(WithCreateBarData, blotter = Blotter('minute', self.asset_finder, 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()) @@ -343,7 +343,7 @@ class BlotterTestCase(WithCreateBarData, other_order = Order( dt=blotter.current_dt, - sid=self.asset_25, + asset=self.asset_25, amount=1 ) @@ -406,7 +406,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 +416,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..cb98d12a 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -3802,7 +3802,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 +4067,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 +4173,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 +4202,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 +4217,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 +4312,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 +4388,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 +4417,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 +4432,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_finance.py b/tests/test_finance.py index 5b72449f..f176d608 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, @@ -305,7 +305,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 +333,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,15 +351,19 @@ 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, @@ -376,23 +380,24 @@ class FinanceTestCase(WithLogger, blotter.process_splits([(2, 0.3333)]) for sid in [1, 2]: - order_lists = blotter.open_orders[sid] + order_lists = \ + blotter.open_orders[blotter.asset_finder.retrieve_asset(sid)] 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() + aapl_order = blotter.open_orders[1][0] + fls_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']) + self.assertEqual(100, aapl_order.amount) + self.assertEqual(10, aapl_order.limit) + self.assertEqual(1, aapl_order.asset) # make sure the fls 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, fls_order.amount) + self.assertEqual(3.33, fls_order.limit) + self.assertEqual(2, fls_order.asset) class TradingEnvironmentTestCase(WithLogger, diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index f76f9ee5..f0ac8af6 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -347,7 +347,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']) @@ -1326,8 +1326,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( @@ -1437,8 +1437,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 +1494,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" ) @@ -1556,8 +1556,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" ) @@ -1834,7 +1834,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, @@ -1973,6 +1973,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( @@ -2017,13 +2027,13 @@ class TestPositionTracker(WithTradingEnvironment, def test_position_values_and_exposures(self): pt = perf.PositionTracker(self.env.asset_finder, None) dt = pd.Timestamp("1984/03/06 3:00PM") - pos1 = perf.Position(1, amount=np.float64(10.0), + pos1 = perf.Position(self.EQUITY1, amount=np.float64(10.0), last_sale_date=dt, last_sale_price=10) - pos2 = perf.Position(2, amount=np.float64(-20.0), + pos2 = perf.Position(self.EQUITY2, amount=np.float64(-20.0), last_sale_date=dt, last_sale_price=10) - pos3 = perf.Position(3, amount=np.float64(30.0), + pos3 = perf.Position(self.FUTURE3, amount=np.float64(30.0), last_sale_date=dt, last_sale_price=10) - pos4 = perf.Position(4, amount=np.float64(-40.0), + pos4 = perf.Position(self.FUTURE4, amount=np.float64(-40.0), last_sale_date=dt, last_sale_price=10) pt.update_positions({1: pos1, 2: pos2, 3: pos3, 4: pos4}) @@ -2049,11 +2059,11 @@ class TestPositionTracker(WithTradingEnvironment, 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), + pos1 = perf.Position(self.EQUITY1, amount=np.float64(10.0), last_sale_date=dt, last_sale_price=10) - pos2 = perf.Position(2, amount=np.float64(-20.0), + pos2 = perf.Position(self.EQUITY2, amount=np.float64(-20.0), last_sale_date=dt, last_sale_price=10) - pos3 = perf.Position(1032201401, amount=np.float64(30.0), + pos3 = perf.Position(self.FUTURE5, amount=np.float64(30.0), last_sale_date=dt, last_sale_price=100) # Call update_positions twice. When the second call is made, @@ -2063,8 +2073,8 @@ class TestPositionTracker(WithTradingEnvironment, # 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}) + pt.update_positions({self.EQUITY1: pos1, self.FUTURE5: pos3}) + pt.update_positions({self.EQUITY2: pos2}) pos_stats = pt.stats() # Test long-only methods @@ -2087,19 +2097,17 @@ 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) dt = pd.Timestamp('2017/01/04 3:00PM') pos1 = perf.Position( - sid=future_sid, + asset=self.FUTURE5, amount=np.float64(30.0), last_sale_date=dt, last_sale_price=100, ) pos2 = perf.Position( - sid=equity_sid, + asset=self.EQUITY1, amount=np.float64(10.0), last_sale_date=dt, last_sale_price=10, @@ -2110,10 +2118,9 @@ class TestPositionTracker(WithTradingEnvironment, # 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_positions({self.FUTURE5: pos1, self.EQUITY1: pos2}) - 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/finance/blotter.py b/zipline/finance/blotter.py index 9a61e06a..9ca934b6 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,6 +27,7 @@ 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') @@ -36,15 +37,12 @@ class Blotter(object): def __init__(self, data_frequency, asset_finder, 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. @@ -88,7 +86,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 +113,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 +135,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 +176,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 +216,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 +230,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 +241,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 +267,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 +305,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: + for asset, ratio in splits: + if asset not in self.open_orders: return - 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 +342,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 +354,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 +364,7 @@ class Blotter(object): if additional_commission > 0: commissions.append({ - "sid": order.sid, + "asset": order.asset, "order": order, "cost": additional_commission }) @@ -401,15 +395,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..91b3edae 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,16 @@ class Order(object): if self.broker_order_id is None: del dct['broker_order_id'] + 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 +223,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..1a0d8d70 100644 --- a/zipline/finance/performance/period.py +++ b/zipline/finance/performance/period.py @@ -185,8 +185,8 @@ 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 + # This dict contains the known cash flow multipliers for assets and is + # keyed on asset self._execution_cash_flow_multipliers = {} _position_tracker = None @@ -362,7 +362,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 = self.asset_finder.retrieve_asset(txn.asset) if isinstance(asset, Future): try: old_price = self._payout_last_sale_prices[asset] @@ -392,15 +392,15 @@ class PerformancePeriod(object): # 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] + multiplier = self._execution_cash_flow_multipliers[txn.asset] except KeyError: - asset = self.asset_finder.retrieve_asset(txn.sid) + asset = self.asset_finder.retrieve_asset(txn.asset) # Futures experience no cash flow on transactions if isinstance(asset, Future): multiplier = 0 else: multiplier = 1 - self._execution_cash_flow_multipliers[txn.sid] = multiplier + self._execution_cash_flow_multipliers[txn.asset] = multiplier # Calculate and return the cash flow given the multiplier return -1 * txn.price * txn.amount * multiplier diff --git a/zipline/finance/performance/position.py b/zipline/finance/performance/position.py index 7c818d59..24bc5bb4 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 @@ -171,10 +177,10 @@ class Position(object): 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 +192,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..0f8edc25 100644 --- a/zipline/finance/performance/position_tracker.py +++ b/zipline/finance/performance/position_tracker.py @@ -19,8 +19,6 @@ 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 @@ -29,9 +27,14 @@ 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 + Equity, + Future, + Asset ) from zipline.errors import PositionTrackerMissingAssetFinder from . position import positiondict @@ -122,7 +125,7 @@ class PositionTracker(object): def __init__(self, asset_finder, data_frequency): self.asset_finder = asset_finder - # sid => position object + # asset => position object self.positions = positiondict() # Arrays for quick calculations of positions value self._position_value_multipliers = OrderedDict() @@ -133,41 +136,42 @@ class PositionTracker(object): self.data_frequency = data_frequency - def _update_asset(self, sid): + def _update_asset(self, asset): try: - self._position_value_multipliers[sid] - self._position_exposure_multipliers[sid] + self._position_value_multipliers[asset] + self._position_exposure_multipliers[asset] 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) + # Collect the value multipliers from applicable assets + asset = self.asset_finder.retrieve_asset(asset) if isinstance(asset, Equity): - self._position_value_multipliers[sid] = 1 - self._position_exposure_multipliers[sid] = 1 + self._position_value_multipliers[asset] = 1 + self._position_exposure_multipliers[asset] = 1 if isinstance(asset, Future): - self._position_value_multipliers[sid] = 0 - self._position_exposure_multipliers[sid] = asset.multiplier + self._position_value_multipliers[asset] = 0 + self._position_exposure_multipliers[asset] = 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) + for asset, pos in iteritems(positions): + self._update_asset(asset) - 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) + self._update_asset(asset=asset) if last_sale_price is not None: position.last_sale_price = last_sale_price if last_sale_date is not None: @@ -178,36 +182,37 @@ 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] + del self._position_value_multipliers[asset] + del self._position_exposure_multipliers[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) + self._update_asset(asset) - 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 +221,8 @@ 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 (sid, ratio), where + sid is an integer. Returns ------- @@ -227,11 +233,12 @@ class PositionTracker(object): for split in splits: sid = split[0] - if sid in self.positions: + asset = self.asset_finder.retrieve_asset(sid) + 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]) + position = self.positions[asset] + leftover_cash = position.handle_split(asset, split[1]) self._update_asset(split[0]) total_leftover_cash += leftover_cash @@ -326,7 +333,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 +346,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 +367,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 diff --git a/zipline/finance/performance/tracker.py b/zipline/finance/performance/tracker.py index ffef7ecd..c7db49ee 100644 --- a/zipline/finance/performance/tracker.py +++ b/zipline/finance/performance/tracker.py @@ -275,10 +275,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..55814238 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,9 @@ class Transaction(object): def to_dict(self): py = copy(self.__dict__) del py['type'] + del py['asset'] + py['sid'] = self.asset + return py @@ -53,7 +55,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..fd78a710 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,17 @@ 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 + # for backwards compatibility + self.sid = asset + def __repr__(self): return "Position({0})".format(self.__dict__) 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