diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 3735d2ab..c1a473e4 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -1448,9 +1448,10 @@ class TestBeforeTradingStart(WithDataPortal, assert (context.hd_portfolio.__dict__[k] == bts_portfolio.__dict__[k]) record(pos_value=bts_portfolio.positions_value) - record(pos_amount=bts_portfolio.positions[sid(3)]['amount']) - record(last_sale_price=bts_portfolio.positions[sid(3)] - ['last_sale_price']) + record(pos_amount=bts_portfolio.positions[sid(3)].amount) + record( + last_sale_price=bts_portfolio.positions[sid(3)].last_sale_price + ) def handle_data(context, data): if not context.ordered: order(sid(3), 1) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 077705dd..3d83593e 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -111,26 +111,26 @@ def check_account(account, # so net and gross leverage are equal. np.testing.assert_allclose(settled_cash, - account['settled_cash'], rtol=1e-3) + account.settled_cash, rtol=1e-3) np.testing.assert_allclose(equity_with_loan, - account['equity_with_loan'], rtol=1e-3) + account.equity_with_loan, rtol=1e-3) np.testing.assert_allclose(total_positions_value, - account['total_positions_value'], rtol=1e-3) + account.total_positions_value, rtol=1e-3) np.testing.assert_allclose(total_positions_exposure, - account['total_positions_exposure'], rtol=1e-3) + account.total_positions_exposure, rtol=1e-3) np.testing.assert_allclose(regt_equity, - account['regt_equity'], rtol=1e-3) + account.regt_equity, rtol=1e-3) np.testing.assert_allclose(available_funds, - account['available_funds'], rtol=1e-3) + account.available_funds, rtol=1e-3) np.testing.assert_allclose(excess_liquidity, - account['excess_liquidity'], rtol=1e-3) + account.excess_liquidity, rtol=1e-3) np.testing.assert_allclose(cushion, - account['cushion'], rtol=1e-3) - np.testing.assert_allclose(leverage, account['leverage'], rtol=1e-3) + account.cushion, rtol=1e-3) + np.testing.assert_allclose(leverage, account.leverage, rtol=1e-3) np.testing.assert_allclose(net_leverage, - account['net_leverage'], rtol=1e-3) + account.net_leverage, rtol=1e-3) np.testing.assert_allclose(net_liquidation, - account['net_liquidation'], rtol=1e-3) + account.net_liquidation, rtol=1e-3) def create_txn(asset, dt, price, amount): @@ -368,28 +368,28 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase): # Validate that the account attributes were updated. account = results[1]['account'] - self.assertEqual(float('inf'), account['day_trades_remaining']) + self.assertEqual(float('inf'), account.day_trades_remaining) # this is a long only portfolio that is only partially invested # so net and gross leverage are equal. - np.testing.assert_allclose(0.198, account['leverage'], rtol=1e-3) - np.testing.assert_allclose(0.198, account['net_leverage'], rtol=1e-3) - np.testing.assert_allclose(8020, account['regt_equity'], rtol=1e-3) - self.assertEqual(float('inf'), account['regt_margin']) - np.testing.assert_allclose(8020, account['available_funds'], rtol=1e-3) - self.assertEqual(0, account['maintenance_margin_requirement']) + np.testing.assert_allclose(0.198, account.leverage, rtol=1e-3) + np.testing.assert_allclose(0.198, account.net_leverage, rtol=1e-3) + np.testing.assert_allclose(8020, account.regt_equity, rtol=1e-3) + self.assertEqual(float('inf'), account.regt_margin) + np.testing.assert_allclose(8020, account.available_funds, rtol=1e-3) + self.assertEqual(0, account.maintenance_margin_requirement) np.testing.assert_allclose(10000, - account['equity_with_loan'], rtol=1e-3) - self.assertEqual(float('inf'), account['buying_power']) - self.assertEqual(0, account['initial_margin_requirement']) - np.testing.assert_allclose(8020, account['excess_liquidity'], + account.equity_with_loan, rtol=1e-3) + self.assertEqual(float('inf'), account.buying_power) + self.assertEqual(0, account.initial_margin_requirement) + np.testing.assert_allclose(8020, account.excess_liquidity, rtol=1e-3) - np.testing.assert_allclose(8020, account['settled_cash'], rtol=1e-3) - np.testing.assert_allclose(10000, account['net_liquidation'], + np.testing.assert_allclose(8020, account.settled_cash, rtol=1e-3) + np.testing.assert_allclose(10000, account.net_liquidation, rtol=1e-3) - np.testing.assert_allclose(0.802, account['cushion'], rtol=1e-3) - np.testing.assert_allclose(1980, account['total_positions_value'], + np.testing.assert_allclose(0.802, account.cushion, rtol=1e-3) + np.testing.assert_allclose(1980, account.total_positions_value, rtol=1e-3) - self.assertEqual(0, account['accrued_interest']) + self.assertEqual(0, account.accrued_interest) for i, result in enumerate(results): for perf_kind in ('daily_perf', 'cumulative_perf'): @@ -1346,10 +1346,10 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars, self.assertEqual( pp.positions[1].last_sale_price, - trades[-1]['price'], + trades[-1].price, "last sale should be same as last trade. \ expected {exp} actual {act}".format( - exp=trades[-1]['price'], + exp=trades[-1].price, act=pp.positions[1].last_sale_price) ) @@ -1456,7 +1456,7 @@ single short-sale transaction""" self.assertEqual( pp.positions[1].last_sale_price, - trades_1[-1]['price'], + trades_1[-1].price, "last sale should be price of last trade" ) diff --git a/zipline/finance/controls.py b/zipline/finance/controls.py index 15f36816..b1c46de8 100644 --- a/zipline/finance/controls.py +++ b/zipline/finance/controls.py @@ -394,5 +394,5 @@ class MaxLeverage(AccountControl): """ Fail if the leverage is greater than the allowed leverage. """ - if _account['leverage'] > self.max_leverage: + if _account.leverage > self.max_leverage: self.fail() diff --git a/zipline/protocol.py b/zipline/protocol.py index eb704b58..bfdc04f1 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -1,5 +1,5 @@ # -# Copyright 2013 Quantopian, Inc. +# Copyright 2016 Quantopian, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,10 +12,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from warnings import warn + import pandas as pd from .utils.enum import enum - from zipline._protocol import BarData # noqa @@ -61,16 +62,7 @@ class Event(object): def __init__(self, initial_values=None): if initial_values: - self.__dict__ = initial_values - - def __getitem__(self, name): - return getattr(self, name) - - def __setitem__(self, name, value): - setattr(self, name, value) - - def __delitem__(self, name): - delattr(self, name) + self.__dict__.update(initial_values) def keys(self): return self.__dict__.keys() @@ -88,8 +80,52 @@ class Event(object): return pd.Series(self.__dict__, index=index) +def _deprecated_getitem_method(name, attrs): + """Create a deprecated ``__getitem__`` method that tells users to use + getattr instead. + + Parameters + ---------- + name : str + The name of the object in the warning message. + attrs : iterable[str] + The set of allowed attributes. + + Returns + ------- + __getitem__ : callable[any, str] + The ``__getitem__`` method to put in the class dict. + """ + attrs = frozenset(attrs) + msg = "'{0}[attr]' is deprecated, please use '{0}.attr' instead".format( + name, + ) + + def __getitem__(self, key): + """``__getitem__`` is deprecated, please use attribute access instead. + """ + warn(msg, DeprecationWarning, stacklevel=1) + if key in attrs: + return self.__dict__[key] + raise KeyError(key) + + return __getitem__ + + class Order(Event): - pass + # If you are adding new attributes, don't update this set. This method + # is deprecated to normal attribute access so we don't want to encourage + # new usages. + __getitem__ = _deprecated_getitem_method( + 'order', { + 'dt', + 'sid', + 'amount', + 'stop', + 'limit', + 'id', + }, + ) class Portfolio(object): @@ -105,12 +141,26 @@ class Portfolio(object): self.start_date = None self.positions_value = 0.0 - def __getitem__(self, key): - return self.__dict__[key] - def __repr__(self): return "Portfolio({0})".format(self.__dict__) + # If you are adding new attributes, don't update this set. This method + # is deprecated to normal attribute access so we don't want to encourage + # new usages. + __getitem__ = _deprecated_getitem_method( + 'portfolio', { + 'capital_used', + 'starting_cash', + 'portfolio_value', + 'pnl', + 'returns', + 'cash', + 'positions', + 'start_date', + 'positions_value', + }, + ) + class Account(object): ''' @@ -139,12 +189,34 @@ class Account(object): self.net_leverage = 0.0 self.net_liquidation = 0.0 - def __getitem__(self, key): - return self.__dict__[key] - def __repr__(self): return "Account({0})".format(self.__dict__) + # If you are adding new attributes, don't update this set. This method + # is deprecated to normal attribute access so we don't want to encourage + # new usages. + __getitem__ = _deprecated_getitem_method( + 'account', { + 'settled_cash', + 'accrued_interest', + 'buying_power', + 'equity_with_loan', + 'total_positions_value', + 'total_positions_exposure', + 'regt_equity', + 'regt_margin', + 'initial_margin_requirement', + 'maintenance_margin_requirement', + 'available_funds', + 'excess_liquidity', + 'cushion', + 'day_trades_remaining', + 'leverage', + 'net_leverage', + 'net_liquidation', + }, + ) + class Position(object): @@ -155,12 +227,22 @@ class Position(object): self.last_sale_price = 0.0 self.last_sale_date = None - def __getitem__(self, key): - return self.__dict__[key] - def __repr__(self): return "Position({0})".format(self.__dict__) + # If you are adding new attributes, don't update this set. This method + # is deprecated to normal attribute access so we don't want to encourage + # new usages. + __getitem__ = _deprecated_getitem_method( + 'position', { + 'sid', + 'amount', + 'cost_basis', + 'last_sale_price', + 'last_sale_date', + }, + ) + class Positions(dict): diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index 5282b4b7..d857245a 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -62,7 +62,7 @@ The algorithm must expose methods: algorithm can then check position information with the Portfolio object:: - self.Portfolio[sid(133)]['cost_basis'] + self.Portfolio[sid(133)].cost_basis - set_transact_setter: method that accepts a callable. Will be set as the value of the set_transact_setter method of @@ -261,9 +261,9 @@ class TestOrderAlgorithm(TradingAlgorithm): if self.incr == 0: assert 0 not in self.portfolio.positions else: - assert self.portfolio.positions[0]['amount'] == \ + assert self.portfolio.positions[0].amount == \ self.incr, "Orders not filled immediately." - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." self.incr += 1 @@ -279,9 +279,9 @@ class TestOrderInstantAlgorithm(TradingAlgorithm): if self.incr == 0: assert 0 not in self.portfolio.positions else: - assert self.portfolio.positions[0]['amount'] == \ + assert self.portfolio.positions[0].amount == \ self.incr, "Orders not filled immediately." - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ self.last_price, "Orders was not filled at last price." self.incr += 1 self.order_value(self.sid(0), data.current(sid(0), "price")) @@ -330,9 +330,9 @@ class TestOrderValueAlgorithm(TradingAlgorithm): if self.incr == 0: assert 0 not in self.portfolio.positions else: - assert self.portfolio.positions[0]['amount'] == \ + assert self.portfolio.positions[0].amount == \ self.incr, "Orders not filled immediately." - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." self.incr += 2 @@ -357,9 +357,9 @@ class TestTargetAlgorithm(TradingAlgorithm): if self.target_shares == 0: assert 0 not in self.portfolio.positions else: - assert self.portfolio.positions[0]['amount'] == \ + assert self.portfolio.positions[0].amount == \ self.target_shares, "Orders not filled immediately." - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." self.target_shares = 10 @@ -380,9 +380,9 @@ class TestOrderPercentAlgorithm(TradingAlgorithm): return else: - assert self.portfolio.positions[0]['amount'] == \ + assert self.portfolio.positions[0].amount == \ self.target_shares, "Orders not filled immediately." - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." @@ -418,13 +418,13 @@ class TestTargetPercentAlgorithm(TradingAlgorithm): # no more than a share's value away from our current # holdings. target_value = self.portfolio.portfolio_value * 0.002 - position_value = self.portfolio.positions[0]['amount'] * \ + position_value = self.portfolio.positions[0].amount * \ self.sale_price assert abs(target_value - position_value) <= self.sale_price, \ "Orders not filled correctly" - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." @@ -446,9 +446,9 @@ class TestTargetValueAlgorithm(TradingAlgorithm): self.target_shares = 10 return else: - assert self.portfolio.positions[0]['amount'] == \ + assert self.portfolio.positions[0].amount == \ self.target_shares, "Orders not filled immediately." - assert self.portfolio.positions[0]['last_sale_price'] == \ + assert self.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." @@ -763,9 +763,9 @@ def handle_data_api(context, data): if context.incr == 0: assert 0 not in context.portfolio.positions else: - assert context.portfolio.positions[0]['amount'] == \ + assert context.portfolio.positions[0].amount == \ context.incr, "Orders not filled immediately." - assert context.portfolio.positions[0]['last_sale_price'] == \ + assert context.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." context.incr += 1 @@ -805,9 +805,9 @@ def handle_data(context, data): if context.incr == 0: assert 0 not in context.portfolio.positions else: - assert context.portfolio.positions[0]['amount'] == \ + assert context.portfolio.positions[0].amount == \ context.incr, "Orders not filled immediately." - assert context.portfolio.positions[0]['last_sale_price'] == \ + assert context.portfolio.positions[0].last_sale_price == \ data.current(sid(0), "price"), \ "Orders not filled at current price." context.incr += 1 diff --git a/zipline/testing/core.py b/zipline/testing/core.py index 2e6941c3..7beae458 100644 --- a/zipline/testing/core.py +++ b/zipline/testing/core.py @@ -591,11 +591,11 @@ def trades_by_sid_to_dfs(trades_by_sid, index): closes = [] volumes = [] for trade in trades: - opens.append(trade["open_price"]) - highs.append(trade["high"]) - lows.append(trade["low"]) - closes.append(trade["close_price"]) - volumes.append(trade["volume"]) + opens.append(trade.open_price) + highs.append(trade.high) + lows.append(trade.low) + closes.append(trade.close_price) + volumes.append(trade.volume) yield sidint, pd.DataFrame( {