MAINT: Refactor application of capital changes

Previously, on the dt of a capital change, we use the un-updated
prices to find the ending performance of the previous subperiod and
then got the new prices to determine the portfolio value used to
calculate the delta, without actually updating the performance
before applying the capital change. This logic is confusing and
unintuitive. Instead, save the ending performance as we do previously,
but have temp values for the starting current subperiod value.
Update those temp values after processing the capital change
This commit is contained in:
Andrew Liang
2016-07-28 14:52:29 -04:00
parent a937d6e6b1
commit 98f3fc9326
5 changed files with 42 additions and 35 deletions
+3 -4
View File
@@ -2264,12 +2264,11 @@ shares in position"
pt.execute_transaction(txn)
pp.handle_execution(txn)
# sync prices and calculate performance before we introduce a capital
# change
# sync prices before we introduce a capital change
pt.sync_last_sale_prices(trades[2].dt, False, data_portal)
pp.calculate_performance()
pp.subdivide_period(1000.0)
pp.initialize_subperiod_divider()
pp.set_current_subperiod_starting_values(1000.0)
pt.sync_last_sale_prices(trades[-1].dt, False, data_portal)
pp.calculate_performance()
+4 -17
View File
@@ -821,25 +821,12 @@ class TradingAlgorithm(object):
self.data_portal
)
# Calculate performance before we sync prices price for the current dt
self.perf_tracker.cumulative_performance.calculate_performance()
self.perf_tracker.todays_performance.calculate_performance()
self.perf_tracker.prepare_capital_change(is_interday)
if capital_change['type'] == 'target':
# Get an updated portfolio value as of this dt, but do it in a way
# so that the performance is not recalculated. This is done so
# that `process_capital_change` can find the performance values
# for the end of the subperiod, which is the previous dt
self.perf_tracker.position_tracker.sync_last_sale_prices(
dt,
self._in_before_trading_start,
self.data_portal
)
portfolio_value = \
self.perf_tracker.position_tracker.stats().net_value + \
self.perf_tracker.cumulative_performance.ending_cash
capital_change_amount = capital_change['value'] - portfolio_value
capital_change_amount = capital_change['value'] - \
self.updated_portfolio().portfolio_value
self.portfolio_needs_update = True
log.info('Processing capital change to target %s at %s. Capital '
'change delta is %s' % (capital_change['value'], dt,
+19 -7
View File
@@ -241,14 +241,12 @@ class PerformancePeriod(object):
else:
del self._payout_last_sale_prices[asset]
def subdivide_period(self, capital_change):
# Apply the capital change to the ending cash
self.ending_cash += capital_change
def initialize_subperiod_divider(self):
self.calculate_performance()
# Increment the total capital change occurred within the period
self._total_intraperiod_capital_change += capital_change
# Divide the period into subperiods
# Initialize a subperiod divider to stash the current performance
# values. Current period starting values are set to equal ending values
# of the previous subperiod
self.subperiod_divider = SubPeriodDivider(
prev_returns=self.returns,
prev_pnl=self.pnl,
@@ -257,6 +255,20 @@ class PerformancePeriod(object):
curr_starting_cash=self.ending_cash
)
def set_current_subperiod_starting_values(self, capital_change):
# Apply the capital change to the ending cash
self.ending_cash += capital_change
# Increment the total capital change occurred within the period
self._total_intraperiod_capital_change += capital_change
# Update the current subperiod starting cash to reflect the capital
# change
starting_value = self.subperiod_divider.curr_subperiod.starting_value
self.subperiod_divider.curr_subperiod = CurrSubPeriodStats(
starting_value=starting_value,
starting_cash=self.ending_cash)
def handle_dividends_paid(self, net_cash_payment):
if net_cash_payment:
self.handle_cash_payment(net_cash_payment)
+11 -2
View File
@@ -238,8 +238,16 @@ class PerformanceTracker(object):
return _dict
def prepare_capital_change(self, is_interday):
self.cumulative_performance.initialize_subperiod_divider()
if not is_interday:
# Change comes in the middle of day
self.todays_performance.initialize_subperiod_divider()
def process_capital_change(self, capital_change_amount, is_interday):
self.cumulative_performance.subdivide_period(capital_change_amount)
self.cumulative_performance.set_current_subperiod_starting_values(
capital_change_amount)
if is_interday:
# Change comes between days
@@ -247,7 +255,8 @@ class PerformanceTracker(object):
capital_change_amount)
else:
# Change comes in the middle of day
self.todays_performance.subdivide_period(capital_change_amount)
self.todays_performance.set_current_subperiod_starting_values(
capital_change_amount)
def process_transaction(self, transaction):
self.txn_count += 1
+5 -5
View File
@@ -100,11 +100,11 @@ class AlgorithmSimulator(object):
def every_bar(dt_to_use, current_data=self.current_data,
handle_data=algo.event_manager.handle_data):
# called every tick (minute or day).
algo.on_dt_changed(dt_to_use)
calculate_minute_capital_changes(dt_to_use)
self.simulation_dt = dt_to_use
algo.on_dt_changed(dt_to_use)
blotter = algo.blotter
perf_tracker = algo.perf_tracker
@@ -149,10 +149,6 @@ class AlgorithmSimulator(object):
perf_tracker = algo.perf_tracker
# process any capital changes that came overnight
algo.calculate_capital_changes(
midnight_dt, emission_rate=emission_rate, is_interday=True)
# Get the positions before updating the date so that prices are
# fetched for trading close instead of midnight
positions = algo.perf_tracker.position_tracker.positions
@@ -162,6 +158,10 @@ class AlgorithmSimulator(object):
self.simulation_dt = midnight_dt
algo.on_dt_changed(midnight_dt)
# process any capital changes that came overnight
algo.calculate_capital_changes(
midnight_dt, emission_rate=emission_rate, is_interday=True)
# we want to wait until the clock rolls over to the next day
# before cleaning up expired assets.
self._cleanup_expired_assets(midnight_dt, position_assets)