Merge pull request #938 from quantopian/futures-payouts-without-every-bar-update

MAINT: Futures cash adjustment on change and calc.
This commit is contained in:
Eddie Hebert
2016-01-06 10:25:29 -05:00
4 changed files with 60 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")
+58 -1
View File
@@ -126,6 +126,10 @@ def calc_period_stats(pos_stats, ending_cash):
net_leverage=net_leverage)
def calc_payout(contract_multiplier, amount, old_price, price):
return (price - old_price) * contract_multiplier * amount
class PerformancePeriod(object):
def __init__(
@@ -149,6 +153,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 +202,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,14 +229,30 @@ 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]
amount = pos.amount
payout = calc_payout(
asset.contract_multiplier,
amount,
old_price,
pos.last_sale_price)
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
self.ending_cash = self.starting_cash + self.period_cash_flow + payout
total_at_end = self.ending_cash + self.ending_value
self.pnl = total_at_end - total_at_start
@@ -242,6 +280,23 @@ 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]
pos = self.position_tracker.positions[asset]
amount = pos.amount
price = txn.price
cash_adj = calc_payout(
asset.contract_multiplier, amount, old_price, price)
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 +497,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):