mirror of
https://github.com/wassname/catalyst.git
synced 2026-09-10 11:50:32 +08:00
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:
+69
-1
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user