mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-27 11:20:45 +08:00
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:
@@ -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")
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user