diff --git a/zipline/finance/performance/tracker.py b/zipline/finance/performance/tracker.py index a584116c..7fb8f5df 100644 --- a/zipline/finance/performance/tracker.py +++ b/zipline/finance/performance/tracker.py @@ -397,14 +397,16 @@ class PerformanceTracker(object): # returns for the bench and the algo self.intraday_risk_metrics.update(dt, minute_returns, - self.all_benchmark_returns[dt]) + self.all_benchmark_returns[dt], + self.get_account(True)) bench_since_open = \ self.intraday_risk_metrics.benchmark_cumulative_returns[dt] self.cumulative_risk_metrics.update(todays_date, self.todays_performance.returns, - bench_since_open) + bench_since_open, + self.get_account(True)) # if this is the close, save the returns objects for cumulative risk # calculations and update dividends for the next day. @@ -416,7 +418,6 @@ class PerformanceTracker(object): Function called at market close only when emitting at minutely frequency. """ - # update_performance should have been called in handle_minute_close # so it is not repeated here. self.intraday_risk_metrics = \ @@ -438,7 +439,8 @@ class PerformanceTracker(object): self.cumulative_risk_metrics.update( completed_date, self.todays_performance.returns, - self.all_benchmark_returns[completed_date]) + self.all_benchmark_returns[completed_date], + self.get_account(True)) # increment the day counter before we move markers forward. self.day_count += 1.0 @@ -481,10 +483,12 @@ class PerformanceTracker(object): bms = self.cumulative_risk_metrics.benchmark_returns ars = self.cumulative_risk_metrics.algorithm_returns + acl = self.cumulative_risk_metrics.algorithm_cumulative_leverages self.risk_report = risk.RiskReport( ars, self.sim_params, - benchmark_returns=bms) + benchmark_returns=bms, + algorithm_leverages=acl) risk_dict = self.risk_report.to_dict() return risk_dict diff --git a/zipline/finance/risk/cumulative.py b/zipline/finance/risk/cumulative.py index 03bf7d94..1de9ba22 100644 --- a/zipline/finance/risk/cumulative.py +++ b/zipline/finance/risk/cumulative.py @@ -93,7 +93,8 @@ class RiskMetricsCumulative(object): def __init__(self, sim_params, returns_frequency=None, - create_first_day_stats=False): + create_first_day_stats=False, + account=None): """ - @returns_frequency allows for configuration of the whether the benchmark and algorithm returns are in units of minutes or days, @@ -143,6 +144,7 @@ class RiskMetricsCumulative(object): self.algorithm_returns_cont = pd.Series(index=cont_index) self.benchmark_returns_cont = pd.Series(index=cont_index) + self.algorithm_cumulative_leverages_cont = pd.Series(index=cont_index) self.mean_returns_cont = pd.Series(index=cont_index) self.annualized_mean_returns_cont = pd.Series(index=cont_index) self.mean_benchmark_returns_cont = pd.Series(index=cont_index) @@ -160,6 +162,7 @@ class RiskMetricsCumulative(object): self.algorithm_cumulative_returns = pd.Series(index=cont_index) self.benchmark_cumulative_returns = pd.Series(index=cont_index) + self.algorithm_cumulative_leverages = pd.Series(index=cont_index) self.excess_returns = pd.Series(index=cont_index) self.latest_dt = cont_index[0] @@ -171,6 +174,8 @@ class RiskMetricsCumulative(object): self.drawdowns = pd.Series(index=cont_index) self.max_drawdowns = pd.Series(index=cont_index) self.max_drawdown = 0 + self.max_leverages = pd.Series(index=cont_index) + self.max_leverage = 0 self.current_max = -np.inf self.daily_treasury = pd.Series(index=self.trading_days) self.treasury_period_return = np.nan @@ -195,7 +200,7 @@ class RiskMetricsCumulative(object): def get_daily_index(self): return self.trading_days - def update(self, dt, algorithm_returns, benchmark_returns): + def update(self, dt, algorithm_returns, benchmark_returns, account): # Keep track of latest dt for use in to_dict and other methods # that report current state. self.latest_dt = dt @@ -261,6 +266,15 @@ class RiskMetricsCumulative(object): self.annualized_mean_benchmark_returns = \ self.annualized_mean_benchmark_returns_cont[:dt] + self.algorithm_cumulative_leverages_cont[dt] = account['leverage'] + self.algorithm_cumulative_leverages = self.algorithm_cumulative_leverages_cont[:dt] + + if self.create_first_day_stats: + if len(self.algorithm_cumulative_leverages) == 1: + self.algorithm_cumulative_leverages = pd.Series( + {self.day_before_start: 0.0}).append( + self.algorithm_cumulative_leverages) + if not self.algorithm_returns.index.equals( self.benchmark_returns.index ): @@ -305,6 +319,8 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" self.metrics.information[dt] = self.calculate_information() self.max_drawdown = self.calculate_max_drawdown() self.max_drawdowns[dt] = self.max_drawdown + self.max_leverage = self.calculate_max_leverage() + self.max_leverages[dt] = self.max_leverage def to_dict(self): """ @@ -331,6 +347,7 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" 'information': self.metrics.information[dt], 'excess_return': self.excess_returns[dt], 'max_drawdown': self.max_drawdown, + 'max_leverage': self.max_leverage, 'period_label': period_label } @@ -383,6 +400,17 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}" else: return self.max_drawdown + def calculate_max_leverage(self): + # The leverage is defined as: the gross_exposure/net_liquidation + # gross_exposure = long_exposure + abs(short_exposure) + # net_liquidation = ending_cash + long_exposure + short_exposure + cur_leverage = self.algorithm_cumulative_leverages[self.latest_dt] + + if cur_leverage is None or self.max_leverage > cur_leverage: + return self.max_leverage + else: + return cur_leverage + def calculate_sharpe(self): """ http://en.wikipedia.org/wiki/Sharpe_ratio diff --git a/zipline/finance/risk/period.py b/zipline/finance/risk/period.py index 07cbbbc7..ee13461d 100644 --- a/zipline/finance/risk/period.py +++ b/zipline/finance/risk/period.py @@ -48,7 +48,7 @@ choose_treasury = functools.partial(risk.choose_treasury, class RiskMetricsPeriod(object): def __init__(self, start_date, end_date, returns, - benchmark_returns=None): + benchmark_returns=None, algorithm_leverages=None): treasury_curves = trading.environment.treasury_curves if treasury_curves.index[-1] >= start_date: @@ -71,6 +71,8 @@ class RiskMetricsPeriod(object): self.algorithm_returns = self.mask_returns_to_period(returns) self.benchmark_returns = self.mask_returns_to_period(benchmark_returns) + self.algorithm_leverages = algorithm_leverages + self.calculate_metrics() def calculate_metrics(self): @@ -131,6 +133,7 @@ class RiskMetricsPeriod(object): self.excess_return = self.algorithm_period_returns - \ self.treasury_period_return self.max_drawdown = self.calculate_max_drawdown() + self.max_leverage = self.calculate_max_leverage() def to_dict(self): """ @@ -152,6 +155,7 @@ class RiskMetricsPeriod(object): 'alpha': self.alpha, 'excess_return': self.excess_return, 'max_drawdown': self.max_drawdown, + 'max_leverage': self.max_leverage, 'period_label': period_label } @@ -175,6 +179,7 @@ class RiskMetricsPeriod(object): "beta", "alpha", "max_drawdown", + "max_leverage", "algorithm_returns", "benchmark_returns", "condition_number", @@ -284,12 +289,13 @@ class RiskMetricsPeriod(object): for r in self.algorithm_returns: try: cur_return += math.log(1.0 + r) - # this is a guard for a single day returning -100% + # this is a guard for a single day returning -100%, if returns are + # greater than -1.0 it will throw an error because you cannot take + # the log of a negative number except ValueError: log.debug("{cur} return, zeroing the returns".format( cur=cur_return)) cur_return = 0.0 - # BUG? Shouldn't this be set to log(1.0 + 0) ? compounded_returns.append(cur_return) cur_max = None @@ -307,6 +313,9 @@ class RiskMetricsPeriod(object): return 1.0 - math.exp(max_drawdown) + def calculate_max_leverage(self): + return max(self.algorithm_leverages.values) + def __getstate__(self): state_dict = \ {k: v for k, v in iteritems(self.__dict__) if diff --git a/zipline/finance/risk/report.py b/zipline/finance/risk/report.py index 8a43d613..3b1925e9 100644 --- a/zipline/finance/risk/report.py +++ b/zipline/finance/risk/report.py @@ -51,7 +51,9 @@ Risk Report | | for the portfolio returns between self.start_date | | | and self.end_date. | +-----------------+----------------------------------------------------+ - + | max_leverage | The largest gross leverage between self.start_date | + | | and self.end_date | + +-----------------+----------------------------------------------------+ """ @@ -70,15 +72,19 @@ log = logbook.Logger('Risk Report') class RiskReport(object): - def __init__(self, algorithm_returns, sim_params, benchmark_returns=None): + def __init__(self, algorithm_returns, sim_params, benchmark_returns=None, algorithm_leverages=None): """ algorithm_returns needs to be a list of daily_return objects sorted in date ascending order + + account needs to be a list of account objects sorted in date + ascending order """ self.algorithm_returns = algorithm_returns self.sim_params = sim_params self.benchmark_returns = benchmark_returns + self.algorithm_leverages = algorithm_leverages if len(self.algorithm_returns) == 0: start_date = self.sim_params.period_start @@ -136,7 +142,8 @@ class RiskReport(object): start_date=cur_start, end_date=cur_end, returns=self.algorithm_returns, - benchmark_returns=self.benchmark_returns + benchmark_returns=self.benchmark_returns, + algorithm_leverages = self.algorithm_leverages, ) ends.append(cur_period_metrics) diff --git a/zipline/finance/risk/risk.py b/zipline/finance/risk/risk.py index 4deed4e7..e44c787b 100644 --- a/zipline/finance/risk/risk.py +++ b/zipline/finance/risk/risk.py @@ -51,6 +51,9 @@ Risk Report | | for the portfolio returns between self.start_date | | | and self.end_date. | +-----------------+----------------------------------------------------+ + | max_leverage | The largest gross leverage between self.start_date | + | | and self.end_date | + +-----------------+----------------------------------------------------+ """