mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-01 08:17:33 +08:00
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:
@@ -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")
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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