MAINT: Futures cash adjustment on change and calc.

In preparation for the incoming changes which no longer push every bar
through the tradesimulation, remove the adjustment of the period's cash on
every pricing change of a held futures asset.

Instead hold the last sale price for each held future either:

- At the end of each peformance period update the last sale prices of
  all held futures, so that the pnl for the next period uses values
  derived from the cash difference between the end of the two periods.

- When a transaction is processed for the Future, so that the correct
  amount is applied to each cash adjustment. (i.e. the cash adjustment
  is reset on every change of amount of the Future being held, so that
  multiple size and prices do not need to be tracked for the same asset.)

Also, remove now unused dict of payout calculation modifier, since new
calculation reads the value directly off of the asset.

Remove update_last_sale test, since the method no longer returns a cash
value.
This commit is contained in:
Eddie Hebert
2016-01-04 16:52:37 -05:00
parent 0b588219af
commit 962347318d
4 changed files with 58 additions and 38 deletions
-20
View File
@@ -2227,26 +2227,6 @@ class TestPositionTracker(unittest.TestCase):
self.assertEquals(val, 0)
self.assertNotIsInstance(val, (bool, np.bool_))
def test_update_last_sale(self):
pt = perf.PositionTracker(self.env.asset_finder)
dt = pd.Timestamp("1984/03/06 3:00PM")
pos1 = perf.Position(1, amount=np.float64(100.0),
last_sale_date=dt, last_sale_price=10)
pos3 = perf.Position(3, amount=np.float64(100.0),
last_sale_date=dt, last_sale_price=10)
pt.update_positions({1: pos1, 3: pos3})
event1 = Event({'sid': 1,
'price': 11,
'dt': dt})
event3 = Event({'sid': 3,
'price': 11,
'dt': dt})
# Check cash-adjustment return value
self.assertEqual(0, pt.update_last_sale(event1))
self.assertEqual(100000, pt.update_last_sale(event3))
def test_position_values_and_exposures(self):
pt = perf.PositionTracker(self.env.asset_finder)
dt = pd.Timestamp("1984/03/06 3:00PM")
+56 -1
View File
@@ -149,6 +149,15 @@ class PerformancePeriod(object):
self.pnl = 0.0
self.ending_cash = starting_cash
# Keyed by asset, the previous last sale price of positions with
# payouts on price differences, e.g. Futures.
#
# This dt is not the previous minute to the minute for which the
# calculation is done, but the last sale price either before the period
# start, or when the price at execution.
self._payout_last_sale_prices = {}
# rollover initializes a number of self's attributes:
self.rollover()
self.keep_transactions = keep_transactions
@@ -189,6 +198,15 @@ class PerformancePeriod(object):
self.orders_by_modified = {}
self.orders_by_id = OrderedDict()
payout_assets = self._payout_last_sale_prices.keys()
for asset in payout_assets:
if asset in self._payout_last_sale_prices:
self._payout_last_sale_prices[asset] = \
self.position_tracker.positions[asset].last_sale_price
else:
del self._payout_last_sale_prices[asset]
def handle_dividends_paid(self, net_cash_payment):
if net_cash_payment:
self.handle_cash_payment(net_cash_payment)
@@ -207,15 +225,34 @@ class PerformancePeriod(object):
def adjust_field(self, field, value):
setattr(self, field, value)
def _get_payout_total(self, positions):
payouts = []
for asset, old_price in iteritems(self._payout_last_sale_prices):
pos = positions[asset]
price = pos.last_sale_price
payout = (
(price - old_price)
*
asset.contract_multiplier
*
pos.amount
)
payouts.append(payout)
return sum(payouts)
def calculate_performance(self):
pt = self.position_tracker
pos_stats = pt.stats()
self.ending_value = pos_stats.net_value
self.ending_exposure = pos_stats.net_exposure
payout = self._get_payout_total(pt.positions)
total_at_start = self.starting_cash + self.starting_value
self.ending_cash = self.starting_cash + self.period_cash_flow
total_at_end = self.ending_cash + self.ending_value
total_at_end = self.ending_cash + self.ending_value + payout
self.pnl = total_at_end - total_at_start
if total_at_start != 0:
@@ -242,6 +279,22 @@ class PerformancePeriod(object):
def handle_execution(self, txn):
self.period_cash_flow += self._calculate_execution_cash_flow(txn)
asset = self.asset_finder.retrieve_asset(txn.sid)
if isinstance(asset, Future):
try:
old_price = self._payout_last_sale_prices[asset]
amount = self.position_tracker.positions[asset].amount
price = txn.price
cash_adj = (price - old_price) * asset.contract_multiplier * \
amount
self.adjust_cash(cash_adj)
if amount + txn.amount == 0:
del self._payout_last_sale_prices[asset]
else:
self._payout_last_sale_prices[asset] = price
except KeyError:
self._payout_last_sale_prices[asset] = txn.price
if self.keep_transactions:
try:
self.processed_transactions[txn.dt].append(txn)
@@ -442,6 +495,8 @@ class PerformancePeriod(object):
dict(self.orders_by_id)
state_dict['orders_by_modified'] = \
dict(self.orders_by_modified)
state_dict['_payout_last_sale_prices'] = \
self._payout_last_sale_prices
STATE_VERSION = 3
state_dict[VERSION_LABEL] = STATE_VERSION
@@ -131,7 +131,6 @@ class PositionTracker(object):
# Arrays for quick calculations of positions value
self._position_value_multipliers = OrderedDict()
self._position_exposure_multipliers = OrderedDict()
self._position_payout_multipliers = OrderedDict()
self._unpaid_dividends = pd.DataFrame(
columns=zp.DIVIDEND_PAYMENT_FIELDS,
)
@@ -145,7 +144,6 @@ class PositionTracker(object):
try:
self._position_value_multipliers[sid]
self._position_exposure_multipliers[sid]
self._position_payout_multipliers[sid]
except KeyError:
# Check if there is an AssetFinder
if self.asset_finder is None:
@@ -156,13 +154,10 @@ class PositionTracker(object):
if isinstance(asset, Equity):
self._position_value_multipliers[sid] = 1
self._position_exposure_multipliers[sid] = 1
self._position_payout_multipliers[sid] = 0
if isinstance(asset, Future):
self._position_value_multipliers[sid] = 0
self._position_exposure_multipliers[sid] = \
asset.contract_multiplier
self._position_payout_multipliers[sid] = \
asset.contract_multiplier
# Futures auto-close timing is controlled by the Future's
# auto_close_date property
self._insert_auto_close_position_date(
@@ -234,14 +229,9 @@ class PositionTracker(object):
return 0
pos = self.positions[sid]
old_price = pos.last_sale_price
pos.last_sale_date = event.dt
pos.last_sale_price = price
# Calculate cash adjustment on assets with multipliers
return ((price - old_price) * self._position_payout_multipliers[sid]
* pos.amount)
def update_positions(self, positions):
# update positions in batch
self.positions.update(positions)
@@ -483,7 +473,6 @@ class PositionTracker(object):
# Arrays for quick calculations of positions value
self._position_value_multipliers = OrderedDict()
self._position_exposure_multipliers = OrderedDict()
self._position_payout_multipliers = OrderedDict()
# Update positions is called without a finder
self.update_positions(state['positions'])
+2 -6
View File
@@ -284,11 +284,7 @@ class PerformanceTracker(object):
return _dict
def _handle_event_price(self, event):
# updates last sale, and pays out a cash adjustment if applicable
cash_adjustment = self.position_tracker.update_last_sale(event)
if cash_adjustment != 0:
self.cumulative_performance.handle_cash_payment(cash_adjustment)
self.todays_performance.handle_cash_payment(cash_adjustment)
self.position_tracker.update_last_sale(event)
def process_trade(self, event):
self._handle_event_price(event)
@@ -296,9 +292,9 @@ class PerformanceTracker(object):
def process_transaction(self, event):
self._handle_event_price(event)
self.txn_count += 1
self.position_tracker.execute_transaction(event)
self.cumulative_performance.handle_execution(event)
self.todays_performance.handle_execution(event)
self.position_tracker.execute_transaction(event)
def process_dividend(self, dividend):