Skip to content

Commit aaa964b

Browse files
author
Vikram Narayan
authored
Merge pull request #442 from vikram-narayan/summary_table
Summary table
2 parents 4633cdf + b1476d8 commit aaa964b

File tree

4 files changed

+62
-29
lines changed

4 files changed

+62
-29
lines changed

pyfolio/bayesian.py

+22-16
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from empyrical import cum_returns
3333

3434

35-
def model_returns_t_alpha_beta(data, bmark, samples=2000):
35+
def model_returns_t_alpha_beta(data, bmark, samples=2000, progressbar=True):
3636
"""
3737
Run Bayesian alpha-beta-model with T distributed returns.
3838
@@ -86,12 +86,12 @@ def model_returns_t_alpha_beta(data, bmark, samples=2000):
8686
mu=mu_reg,
8787
sd=sigma,
8888
observed=y)
89-
trace = pm.sample(samples)
89+
trace = pm.sample(samples, progressbar=progressbar)
9090

9191
return model, trace
9292

9393

94-
def model_returns_normal(data, samples=500):
94+
def model_returns_normal(data, samples=500, progressbar=True):
9595
"""
9696
Run Bayesian model assuming returns are normally distributed.
9797
@@ -125,11 +125,11 @@ def model_returns_normal(data, samples=500):
125125
returns.distribution.variance**.5 *
126126
np.sqrt(252))
127127

128-
trace = pm.sample(samples)
128+
trace = pm.sample(samples, progressbar=progressbar)
129129
return model, trace
130130

131131

132-
def model_returns_t(data, samples=500):
132+
def model_returns_t(data, samples=500, progressbar=True):
133133
"""
134134
Run Bayesian model assuming returns are Student-T distributed.
135135
@@ -167,11 +167,11 @@ def model_returns_t(data, samples=500):
167167
returns.distribution.variance**.5 *
168168
np.sqrt(252))
169169

170-
trace = pm.sample(samples)
170+
trace = pm.sample(samples, progressbar=progressbar)
171171
return model, trace
172172

173173

174-
def model_best(y1, y2, samples=1000):
174+
def model_best(y1, y2, samples=1000, progressbar=True):
175175
"""
176176
Bayesian Estimation Supersedes the T-Test
177177
@@ -252,7 +252,7 @@ def model_best(y1, y2, samples=1000):
252252
returns_group2.distribution.variance**.5 *
253253
np.sqrt(252))
254254

255-
trace = pm.sample(samples)
255+
trace = pm.sample(samples, progressbar=progressbar)
256256
return model, trace
257257

258258

@@ -347,7 +347,7 @@ def distplot_w_perc(trace, ax):
347347
ylabel='Belief', yticklabels=[])
348348

349349

350-
def model_stoch_vol(data, samples=2000):
350+
def model_stoch_vol(data, samples=2000, progressbar=True):
351351
"""
352352
Run stochastic volatility model.
353353
@@ -385,7 +385,7 @@ def model_stoch_vol(data, samples=2000):
385385
pm.math.exp(-2 * s))
386386
StudentT('r', nu, lam=volatility_process, observed=data)
387387

388-
trace = pm.sample(samples)
388+
trace = pm.sample(samples, progressbar=progressbar)
389389

390390
return model, trace
391391

@@ -525,7 +525,7 @@ def _plot_bayes_cone(returns_train, returns_test,
525525

526526

527527
def run_model(model, returns_train, returns_test=None,
528-
bmark=None, samples=500, ppc=False):
528+
bmark=None, samples=500, ppc=False, progressbar=True):
529529
"""
530530
Run one of the Bayesian models.
531531
@@ -563,21 +563,27 @@ def run_model(model, returns_train, returns_test=None,
563563

564564
if model == 'alpha_beta':
565565
model, trace = model_returns_t_alpha_beta(returns_train,
566-
bmark, samples)
566+
bmark, samples,
567+
progressbar=progressbar)
567568
elif model == 't':
568-
model, trace = model_returns_t(returns_train, samples)
569+
model, trace = model_returns_t(returns_train, samples,
570+
progressbar=progressbar)
569571
elif model == 'normal':
570-
model, trace = model_returns_normal(returns_train, samples)
572+
model, trace = model_returns_normal(returns_train, samples,
573+
progressbar=progressbar)
571574
elif model == 'best':
572-
model, trace = model_best(returns_train, returns_test, samples=samples)
575+
model, trace = model_best(returns_train, returns_test,
576+
samples=samples,
577+
progressbar=progressbar)
573578
else:
574579
raise NotImplementedError(
575580
'Model {} not found.'
576581
'Use alpha_beta, t, normal, or best.'.format(model))
577582

578583
if ppc:
579584
ppc_samples = pm.sample_ppc(trace, samples=samples,
580-
model=model, size=len(returns_test))
585+
model=model, size=len(returns_test),
586+
progressbar=progressbar)
581587
return trace, ppc_samples['returns']
582588

583589
return trace

pyfolio/perf_attrib.py

+31-9
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
# limitations under the License.
1515
from __future__ import division
1616

17+
from collections import OrderedDict
1718
import empyrical as ep
1819
import pandas as pd
19-
2020
import matplotlib.pyplot as plt
21+
2122
from pyfolio.pos import get_percent_alloc
2223
from pyfolio.utils import print_table, set_legend_location
2324

@@ -124,12 +125,12 @@ def perf_attrib(returns, positions, factor_returns, factor_loadings,
124125
pd.concat([perf_attrib_by_factor, returns_df], axis='columns'))
125126

126127

127-
def create_perf_attrib_stats(perf_attrib):
128+
def create_perf_attrib_stats(perf_attrib, risk_exposures):
128129
"""
129130
Takes perf attribution data over a period of time and computes annualized
130131
multifactor alpha, multifactor sharpe, risk exposures.
131132
"""
132-
summary = {}
133+
summary = OrderedDict()
133134
specific_returns = perf_attrib['specific_returns']
134135
common_returns = perf_attrib['common_returns']
135136

@@ -139,6 +140,8 @@ def create_perf_attrib_stats(perf_attrib):
139140
summary['Multi-factor sharpe'] =\
140141
ep.sharpe_ratio(specific_returns)
141142

143+
# empty line between common/specific/total returns
144+
summary[' '] = ' '
142145
summary['Cumulative specific returns'] =\
143146
ep.cum_returns_final(specific_returns)
144147
summary['Cumulative common returns'] =\
@@ -147,7 +150,9 @@ def create_perf_attrib_stats(perf_attrib):
147150
ep.cum_returns_final(perf_attrib['total_returns'])
148151

149152
summary = pd.Series(summary)
150-
return summary
153+
154+
risk_exposure_summary = risk_exposures.sum(axis='rows')
155+
return summary, risk_exposure_summary
151156

152157

153158
def show_perf_attrib_stats(returns, positions, factor_returns,
@@ -164,46 +169,63 @@ def show_perf_attrib_stats(returns, positions, factor_returns,
164169
pos_in_dollars=pos_in_dollars,
165170
)
166171

167-
perf_attrib_stats = create_perf_attrib_stats(perf_attrib_data)
172+
perf_attrib_stats, risk_exposure_stats =\
173+
create_perf_attrib_stats(perf_attrib_data, risk_exposures)
174+
168175
print_table(perf_attrib_stats)
169-
print_table(risk_exposures)
176+
print_table(risk_exposure_stats)
170177

171178

172-
def plot_returns(perf_attrib_data, ax=None):
179+
def plot_returns(perf_attrib_data, cost=None, ax=None):
173180
"""
174181
Plot total, specific, and common returns.
175182
176183
Parameters
177184
----------
178185
perf_attrib_data : pd.DataFrame
179186
df with factors, common returns, and specific returns as columns,
180-
and datetimes as index
187+
and datetimes as index. Assumes the `total_returns` column is NOT
188+
cost adjusted.
181189
- Example:
182190
momentum reversal common_returns specific_returns
183191
dt
184192
2017-01-01 0.249087 0.935925 1.185012 1.185012
185193
2017-01-02 -0.003194 -0.400786 -0.403980 -0.403980
186194
195+
cost : pd.Series, optional
196+
if present, gets subtracted from `perf_attrib_data['total_returns']`,
197+
and gets plotted separately
198+
187199
ax : matplotlib.axes.Axes
188200
axes on which plots are made. if None, current axes will be used
189201
190202
Returns
191203
-------
192204
ax : matplotlib.axes.Axes
193205
"""
206+
194207
if ax is None:
195208
ax = plt.gca()
196209

197210
returns = perf_attrib_data['total_returns']
211+
total_returns_label = 'Total returns'
212+
213+
if cost is not None:
214+
returns = returns - cost
215+
total_returns_label += ' (adjusted)'
216+
198217
specific_returns = perf_attrib_data['specific_returns']
199218
common_returns = perf_attrib_data['common_returns']
200219

201-
ax.plot(ep.cum_returns(returns), color='g', label='Total returns')
220+
ax.plot(ep.cum_returns(returns), color='g', label=total_returns_label)
202221
ax.plot(ep.cum_returns(specific_returns), color='b',
203222
label='Cumulative specific returns')
204223
ax.plot(ep.cum_returns(common_returns), color='r',
205224
label='Cumulative common returns')
206225

226+
if cost is not None:
227+
ax.plot(cost, color='p', label='Cost')
228+
207229
ax.set_title('Time series of cumulative returns')
208230
ax.set_ylabel('Returns')
209231

pyfolio/tears.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,8 @@ def create_capacity_tear_sheet(returns, positions, transactions,
10651065
@plotting.customize
10661066
def create_bayesian_tear_sheet(returns, benchmark_rets=None,
10671067
live_start_date=None, samples=2000,
1068-
return_fig=False, stoch_vol=False):
1068+
return_fig=False, stoch_vol=False,
1069+
progressbar=True):
10691070
"""
10701071
Generate a number of Bayesian distributions and a Bayesian
10711072
cone plot of returns.
@@ -1134,14 +1135,16 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
11341135

11351136
trace_t, ppc_t = bayesian.run_model('t', df_train,
11361137
returns_test=df_test,
1137-
samples=samples, ppc=True)
1138+
samples=samples, ppc=True,
1139+
progressbar=progressbar)
11381140
previous_time = timer("T model", previous_time)
11391141

11401142
# Compute BEST model
11411143
print("\nRunning BEST model")
11421144
trace_best = bayesian.run_model('best', df_train,
11431145
returns_test=df_test,
1144-
samples=samples)
1146+
samples=samples,
1147+
progressbar=progressbar)
11451148
previous_time = timer("BEST model", previous_time)
11461149

11471150
# Plot results
@@ -1213,7 +1216,8 @@ def create_bayesian_tear_sheet(returns, benchmark_rets=None,
12131216
benchmark_rets = benchmark_rets.loc[df_train.index]
12141217
trace_alpha_beta = bayesian.run_model('alpha_beta', df_train,
12151218
bmark=benchmark_rets,
1216-
samples=samples)
1219+
samples=samples,
1220+
progressbar=progressbar)
12171221
previous_time = timer("running alpha beta model", previous_time)
12181222

12191223
# Plot alpha and beta

pyfolio/tests/test_tears.py

+1
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,5 @@ def test_create_bayesian_tear_sheet_breakdown(self, kwargs):
128128
create_bayesian_tear_sheet(
129129
self.test_returns,
130130
live_start_date=self.test_returns.index[-20],
131+
progressbar=False,
131132
**kwargs)

0 commit comments

Comments
 (0)