MAINT: Refactor checking, calculation and processing of capital changes

AlgorithmSimulator will no longer check for capital changes.
Instead, TradingAlgorithm find and calculate the changes, and
PerformanceTracker will apply the changes
This commit is contained in:
Andrew Liang
2016-07-25 10:05:47 -04:00
parent f146d6d8c1
commit a9d698018a
4 changed files with 80 additions and 48 deletions
+69 -1
View File
@@ -408,9 +408,13 @@ class TradingAlgorithm(object):
self.benchmark_sid = kwargs.pop('benchmark_sid', None)
# A dictionary of capital changes keyed by timestamp
# A dictionary of capital changes, keyed by timestamp, indicating the
# target/delta of the capital changes, along with values
self.capital_changes = kwargs.pop('capital_changes', {})
# A dictionary of the actual capital change deltas, keyed by timestamp
self.capital_change_deltas = {}
def init_engine(self, get_loader):
"""
Construct and store a PipelineEngine from loader.
@@ -785,6 +789,70 @@ class TradingAlgorithm(object):
return daily_stats
def calculate_capital_changes(self, dt, emission_rate, is_interday):
"""
If there is a capital change for a given dt, this means the the change
occurs before `handle_data` on the given dt. In the case of the
change being a target value, the change will be computed on the
portfolio value according to prices at the given dt
"""
try:
capital_change = self.capital_changes[dt]
except KeyError:
return
if emission_rate == 'daily':
# If we are running daily emission, prices won't
# necessarily be synced at the end of every minute, and we
# need the up-to-date prices for capital change
# calculations. We want to sync the prices as of the
# last market minute, and this is okay from a data portal
# perspective as we have technically not "advanced" to the
# current dt yet.
self.perf_tracker.position_tracker.sync_last_sale_prices(
self.trading_calendar.previous_minute(
dt
),
False,
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()
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
log.info('Processing capital change to target %s at %s. Capital '
'change delta is %s' % (capital_change['value'], dt,
capital_change_amount))
elif capital_change['type'] == 'delta':
capital_change_amount = capital_change['value']
log.info('Processing capital change of delta %s at %s'
% (capital_change_amount, dt))
else:
log.error("Capital change %s does not indicate a valid type "
"('target' or 'delta')" % capital_change)
return
self.capital_change_deltas.update({dt: capital_change_amount})
self.perf_tracker.process_capital_change(capital_change_amount,
is_interday)
@api_method
def get_environment(self, field='platform'):
"""Query the execution environment.
-2
View File
@@ -242,8 +242,6 @@ class PerformancePeriod(object):
del self._payout_last_sale_prices[asset]
def subdivide_period(self, capital_change):
self.calculate_performance()
# Apply the capital change to the ending cash
self.ending_cash += capital_change
+1 -16
View File
@@ -238,22 +238,7 @@ class PerformanceTracker(object):
return _dict
def process_capital_changes(self, capital_change, dt, is_interday):
if capital_change['type'] == 'target':
capital_change_amount = capital_change['value'] - \
self.cumulative_performance.as_portfolio().portfolio_value
log.info('Processing capital change to target %s at %s. Capital '
'change delta is %s' % (capital_change['value'], dt,
capital_change_amount))
elif capital_change['type'] == 'delta':
capital_change_amount = capital_change['delta']
log.info('Processing capital change of delta %s at %s'
% (capital_change_amount, dt))
else:
log.error("Capital change %s does not indicate a valid type "
"('target' or 'delta')" % capital_change)
return
def process_capital_change(self, capital_change_amount, is_interday):
self.cumulative_performance.subdivide_period(capital_change_amount)
if is_interday:
+10 -29
View File
@@ -95,13 +95,13 @@ class AlgorithmSimulator(object):
Main generator work loop.
"""
algo = self.algo
emission_rate = algo.perf_tracker.emission_rate
def every_bar(dt_to_use, current_data=self.current_data,
handle_data=algo.event_manager.handle_data):
# called every tick (minute or day).
if dt_to_use in algo.capital_changes:
process_minute_capital_changes(dt_to_use)
calculate_minute_capital_changes(dt_to_use)
self.simulation_dt = dt_to_use
algo.on_dt_changed(dt_to_use)
@@ -149,11 +149,9 @@ class AlgorithmSimulator(object):
perf_tracker = algo.perf_tracker
if midnight_dt in algo.capital_changes:
# process any capital changes that came overnight
change = algo.capital_changes[midnight_dt]
perf_tracker.process_capital_changes(change, dt,
is_interday=True)
# 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
@@ -203,33 +201,16 @@ class AlgorithmSimulator(object):
def execute_order_cancellation_policy():
algo.blotter.execute_cancel_policy(DAY_END)
def process_minute_capital_changes(dt):
# If we are running daily emission, prices won't
# necessarily be synced at the end of every minute, and we
# need the up-to-date prices for capital change
# calculations. We want to sync the prices as of the
# last market minute, and this is okay from a data portal
# perspective as we have technically not "advanced" to the
# current dt yet.
algo.perf_tracker.position_tracker.sync_last_sale_prices(
self.algo.trading_calendar.previous_minute(dt),
False,
self.data_portal
)
def calculate_minute_capital_changes(dt):
# process any capital changes that came between the last
# and current minutes
change = algo.capital_changes[dt]
algo.perf_tracker.process_capital_changes(
change,
dt,
is_interday=False
)
algo.calculate_capital_changes(
dt, emission_rate=emission_rate, is_interday=False)
else:
def execute_order_cancellation_policy():
pass
def process_minute_capital_changes(dt):
def calculate_minute_capital_changes(dt):
pass
for dt, action in self.clock:
@@ -239,7 +220,7 @@ class AlgorithmSimulator(object):
once_a_day(dt)
elif action == DAY_END:
# End of the day.
if algo.perf_tracker.emission_rate == 'daily':
if emission_rate == 'daily':
handle_benchmark(normalize_date(dt))
execute_order_cancellation_policy()