From 9a0d9d868c217c4db22fc49c9ccf76035c83b8a2 Mon Sep 17 00:00:00 2001 From: Jean Bredeche Date: Sat, 22 Apr 2017 14:01:57 -0400 Subject: [PATCH] REF: Remove asset_finder and multipliers from PositionTracker --- tests/finance/test_blotter.py | 7 +- tests/test_perf_tracking.py | 147 +++++++++--------- zipline/errors.py | 12 -- .../finance/performance/position_tracker.py | 111 ++++--------- zipline/finance/performance/tracker.py | 1 - 5 files changed, 103 insertions(+), 175 deletions(-) diff --git a/tests/finance/test_blotter.py b/tests/finance/test_blotter.py index 95786f7f..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()) @@ -261,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, []) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 94acc3d0..23214016 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, @@ -1106,8 +1104,7 @@ 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) + pt = perf.PositionTracker(self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, self.sim_params.data_frequency) pp.position_tracker = pt @@ -1199,8 +1196,7 @@ 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) + pt = perf.PositionTracker(self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, self.sim_params.data_frequency) pp.position_tracker = pt @@ -1291,8 +1287,7 @@ 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) + 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, @@ -1410,8 +1405,7 @@ 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) + pt = perf.PositionTracker(self.sim_params.data_frequency) pp = perf.PerformancePeriod( 1000.0, self.env.asset_finder, self.sim_params.data_frequency) @@ -1530,8 +1524,7 @@ 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) + ptTotal = perf.PositionTracker(self.sim_params.data_frequency) ppTotal = perf.PerformancePeriod(1000.0, self.env.asset_finder, self.sim_params.data_frequency) ppTotal.position_tracker = pt @@ -1638,8 +1631,7 @@ 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) + pt = perf.PositionTracker(self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.env.asset_finder, self.sim_params.data_frequency) pp.position_tracker = pt @@ -1725,8 +1717,7 @@ 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, @@ -1792,8 +1783,7 @@ 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) + pt3 = perf.PositionTracker(self.sim_params.data_frequency) pp3 = perf.PerformancePeriod(1000.0, self.env.asset_finder, self.sim_params.data_frequency) pp3.position_tracker = pt3 @@ -1845,8 +1835,7 @@ shares in position" transactions = factory.create_txn_history(*history_args) - 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) pp.position_tracker = pt @@ -1885,8 +1874,7 @@ 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) + 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, @@ -1929,8 +1917,7 @@ 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) + 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, @@ -2003,8 +1990,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 = [ @@ -2025,17 +2011,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(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.FUTURE3, amount=np.float64(30.0), - last_sale_date=dt, last_sale_price=10) - 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}) + 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() @@ -2092,24 +2088,35 @@ class TestPositionTracker(WithTradingEnvironment, self.assertEqual(10.5, future_pos.cost_basis) def test_update_positions(self): - pt = perf.PositionTracker(self.env.asset_finder, None) + 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) + # 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) - # 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({self.EQUITY1: pos1, self.FUTURE5: pos3}) - pt.update_positions({self.EQUITY2: pos2}) + 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 @@ -2132,28 +2139,18 @@ class TestPositionTracker(WithTradingEnvironment, self.assertEqual(100 + 150000 - 200, pos_stats.net_exposure) def test_close_position(self): - pt = perf.PositionTracker(self.env.asset_finder, None) + pt = perf.PositionTracker(None) dt = pd.Timestamp('2017/01/04 3:00PM') - pos1 = perf.Position( - asset=self.FUTURE5, - amount=np.float64(30.0), - last_sale_date=dt, - last_sale_price=100, - ) - pos2 = perf.Position( - asset=self.EQUITY1, - 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({self.FUTURE5: pos1, self.EQUITY1: pos2}) + pt.update_position( + asset=self.EQUITY1, amount=np.float64(10.0), + last_sale_date=dt, last_sale_price=10 + ) txn = create_txn(self.FUTURE5, dt, 100, -30) pt.execute_transaction(txn) 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/performance/position_tracker.py b/zipline/finance/performance/position_tracker.py index 0f8edc25..dc4f971b 100644 --- a/zipline/finance/performance/position_tracker.py +++ b/zipline/finance/performance/position_tracker.py @@ -20,11 +20,6 @@ import numpy as np from collections import namedtuple from math import isnan -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 @@ -32,11 +27,9 @@ 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, Asset ) -from zipline.errors import PositionTrackerMissingAssetFinder from . position import positiondict log = logbook.Logger('Performance') @@ -55,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) + else: + values.append(position.last_sale_price * position.amount) + + return values def calc_net(values): @@ -74,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): @@ -122,44 +114,15 @@ def calc_gross_value(long_value, short_value): class PositionTracker(object): - def __init__(self, asset_finder, data_frequency): - self.asset_finder = asset_finder - + 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, asset): - try: - 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 assets - asset = self.asset_finder.retrieve_asset(asset) - if isinstance(asset, Equity): - self._position_value_multipliers[asset] = 1 - self._position_exposure_multipliers[asset] = 1 - if isinstance(asset, Future): - 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 asset, pos in iteritems(positions): - self._update_asset(asset) - @expect_types(asset=Asset) def update_position(self, asset, amount=None, last_sale_price=None, last_sale_date=None, cost_basis=None): @@ -171,7 +134,6 @@ class PositionTracker(object): if amount is not None: position.amount = amount - 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: @@ -193,11 +155,7 @@ class PositionTracker(object): position.update(txn) if position.amount == 0: - # if this position now has 0 shares, remove it from our internal - # bookkeeping. 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, @@ -205,8 +163,6 @@ class PositionTracker(object): del self._positions_store[asset] except KeyError: pass - else: - self._update_asset(asset) @expect_types(asset=Asset) def handle_commission(self, asset, cost): @@ -221,8 +177,7 @@ class PositionTracker(object): Parameters ---------- splits: list - A list of splits. Each split is a tuple of (sid, ratio), where - sid is an integer. + A list of splits. Each split is a tuple of (asset, ratio). Returns ------- @@ -232,14 +187,12 @@ class PositionTracker(object): total_leftover_cash = 0 for split in splits: - sid = split[0] - asset = self.asset_finder.retrieve_asset(sid) + asset = split[0] 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[asset] leftover_cash = position.handle_split(asset, split[1]) - self._update_asset(split[0]) total_leftover_cash += leftover_cash return total_leftover_cash @@ -316,7 +269,6 @@ class PositionTracker(object): Position(payment_asset) position.amount += share_count - self._update_asset(payment_asset) return net_cash_payment @@ -408,16 +360,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 c7db49ee..d9b5b79d 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 )