Skip to content

Commit 4633cdf

Browse files
author
Vikram Narayan
authored
Merge pull request #441 from vikram-narayan/perf_attrib_tearsheets
Perf attrib tearsheets
2 parents 79d7b16 + 657be93 commit 4633cdf

File tree

3 files changed

+114
-37
lines changed

3 files changed

+114
-37
lines changed

pyfolio/perf_attrib.py

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import matplotlib.pyplot as plt
2121
from pyfolio.pos import get_percent_alloc
22-
from pyfolio.utils import print_table, set_legend_location, COLORS
22+
from pyfolio.utils import print_table, set_legend_location
2323

2424

2525
def perf_attrib(returns, positions, factor_returns, factor_loadings,
@@ -151,35 +151,38 @@ def create_perf_attrib_stats(perf_attrib):
151151

152152

153153
def show_perf_attrib_stats(returns, positions, factor_returns,
154-
factor_loadings):
154+
factor_loadings, pos_in_dollars=True):
155155
"""
156156
Calls `perf_attrib` using inputs, and displays outputs using
157157
`utils.print_table`.
158158
"""
159-
risk_exposures, perf_attrib_data = perf_attrib(returns,
160-
positions,
161-
factor_returns,
162-
factor_loadings)
159+
risk_exposures, perf_attrib_data = perf_attrib(
160+
returns,
161+
positions,
162+
factor_returns,
163+
factor_loadings,
164+
pos_in_dollars=pos_in_dollars,
165+
)
163166

164167
perf_attrib_stats = create_perf_attrib_stats(perf_attrib_data)
165168
print_table(perf_attrib_stats)
166169
print_table(risk_exposures)
167170

168171

169-
def plot_returns(returns, specific_returns, common_returns, ax=None):
172+
def plot_returns(perf_attrib_data, ax=None):
170173
"""
171174
Plot total, specific, and common returns.
172175
173176
Parameters
174177
----------
175-
returns : pd.Series
176-
total returns, indexed by datetime
177-
178-
specific_returns : pd.Series
179-
specific returns, indexed by datetime
180-
181-
commons_returns : pd.Series
182-
common returns, indexed by datetime
178+
perf_attrib_data : pd.DataFrame
179+
df with factors, common returns, and specific returns as columns,
180+
and datetimes as index
181+
- Example:
182+
momentum reversal common_returns specific_returns
183+
dt
184+
2017-01-01 0.249087 0.935925 1.185012 1.185012
185+
2017-01-02 -0.003194 -0.400786 -0.403980 -0.403980
183186
184187
ax : matplotlib.axes.Axes
185188
axes on which plots are made. if None, current axes will be used
@@ -191,13 +194,17 @@ def plot_returns(returns, specific_returns, common_returns, ax=None):
191194
if ax is None:
192195
ax = plt.gca()
193196

197+
returns = perf_attrib_data['total_returns']
198+
specific_returns = perf_attrib_data['specific_returns']
199+
common_returns = perf_attrib_data['common_returns']
200+
194201
ax.plot(ep.cum_returns(returns), color='g', label='Total returns')
195202
ax.plot(ep.cum_returns(specific_returns), color='b',
196203
label='Cumulative specific returns')
197204
ax.plot(ep.cum_returns(common_returns), color='r',
198205
label='Cumulative common returns')
199206

200-
ax.set_title('Time Series of cumulative returns')
207+
ax.set_title('Time series of cumulative returns')
201208
ax.set_ylabel('Returns')
202209

203210
set_legend_location(ax)
@@ -235,20 +242,12 @@ def plot_alpha_returns(alpha_returns, ax=None):
235242
return ax
236243

237244

238-
def plot_factor_contribution_to_perf(exposures, perf_attrib_data, ax=None):
245+
def plot_factor_contribution_to_perf(perf_attrib_data, ax=None):
239246
"""
240247
Plot each factor's contribution to performance.
241248
242249
Parameters
243250
----------
244-
exposures : pd.DataFrame
245-
df indexed by datetime, with factors as columns
246-
- Example:
247-
momentum reversal
248-
dt
249-
2017-01-01 -0.238655 0.077123
250-
2017-01-02 0.821872 1.520515
251-
252251
perf_attrib_data : pd.DataFrame
253252
df with factors, common returns, and specific returns as columns,
254253
and datetimes as index
@@ -271,12 +270,8 @@ def plot_factor_contribution_to_perf(exposures, perf_attrib_data, ax=None):
271270
factors_and_specific = perf_attrib_data.drop(
272271
['total_returns', 'common_returns'], axis='columns')
273272

274-
ax.stackplot(
275-
factors_and_specific.index,
276-
[factors_and_specific[s] for s in factors_and_specific],
277-
labels=factors_and_specific.columns,
278-
colors=COLORS
279-
)
273+
for col in factors_and_specific:
274+
ax.plot(factors_and_specific[col])
280275

281276
ax.axhline(0, color='k')
282277
set_legend_location(ax)
@@ -309,10 +304,8 @@ def plot_risk_exposures(exposures, ax=None):
309304
if ax is None:
310305
ax = plt.gca()
311306

312-
ax.stackplot(exposures.index,
313-
[exposures[s] for s in exposures],
314-
labels=exposures.columns,
315-
colors=COLORS)
307+
for col in exposures:
308+
ax.plot(exposures[col])
316309

317310
set_legend_location(ax)
318311
ax.set_ylabel('Factor exposures')

pyfolio/tears.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from . import _seaborn as sns
2828
from . import capacity
29+
from . import perf_attrib
2930
from . import plotting
3031
from . import pos
3132
from . import risk
@@ -76,7 +77,11 @@ def create_full_tear_sheet(returns,
7677
volumes=None,
7778
percentile=None,
7879
turnover_denom='AGB',
79-
set_context=True):
80+
set_context=True,
81+
factor_returns=None,
82+
factor_loadings=None,
83+
pos_in_dollars=True,
84+
return_fig=False):
8085
"""
8186
Generate a number of tear sheets that are useful
8287
for analyzing a strategy's performance.
@@ -159,6 +164,13 @@ def create_full_tear_sheet(returns,
159164
set_context : boolean, optional
160165
If True, set default plotting style context.
161166
- See plotting.context().
167+
factor_returns : pd.Dataframe, optional
168+
Returns by factor, with date as index and factors as columns
169+
factor_loadings : pd.Dataframe, optional
170+
Factor loadings for all days in the date range, with date and
171+
ticker as index, and factors as columns.
172+
pos_in_dollars : boolean, optional
173+
indicates whether positions is in dollars
162174
"""
163175

164176
if benchmark_rets is None:
@@ -218,6 +230,11 @@ def create_full_tear_sheet(returns,
218230
create_risk_tear_sheet(positions, style_factor_panel, sectors,
219231
caps, shares_held, volumes, percentile)
220232

233+
if factor_returns is not None and factor_loadings is not None:
234+
create_perf_attrib_tearsheet(returns, positions, factor_returns,
235+
factor_loadings,
236+
pos_in_dollars=pos_in_dollars)
237+
221238
if bayesian:
222239
create_bayesian_tear_sheet(returns,
223240
live_start_date=live_start_date,
@@ -1432,3 +1449,65 @@ def create_risk_tear_sheet(positions,
14321449
plt.show()
14331450
if return_fig:
14341451
return fig
1452+
1453+
1454+
@plotting.customize
1455+
def create_perf_attrib_tearsheet(returns,
1456+
positions,
1457+
factor_returns,
1458+
factor_loadings,
1459+
pos_in_dollars=True,
1460+
return_fig=False):
1461+
"""
1462+
Generate plots and tables for analyzing a strategy's performance.
1463+
1464+
Parameters
1465+
----------
1466+
returns : pd.Series
1467+
Returns for each day in the date range.
1468+
1469+
positions: pd.DataFrame
1470+
Daily holdings (in dollars or percentages), indexed by date.
1471+
Will be converted to percentages if positions are in dollars.
1472+
Short positions show up as cash in the 'cash' column.
1473+
1474+
factor_returns : pd.DataFrame
1475+
Returns by factor, with date as index and factors as columns
1476+
1477+
factor_loadings : pd.DataFrame
1478+
Factor loadings for all days in the date range, with date
1479+
and ticker as index, and factors as columns.
1480+
1481+
pos_in_dollars : boolean, optional
1482+
Flag indicating whether `positions` are in dollars or percentages
1483+
If True, positions are in dollars.
1484+
1485+
return_fig : boolean, optional
1486+
If True, returns the figure that was plotted on.
1487+
"""
1488+
portfolio_exposures, perf_attrib_data = perf_attrib.perf_attrib(
1489+
returns, positions, factor_returns, factor_loadings,
1490+
pos_in_dollars=pos_in_dollars
1491+
)
1492+
1493+
# aggregate perf attrib stats and show summary table
1494+
perf_attrib.show_perf_attrib_stats(returns, positions, factor_returns,
1495+
factor_loadings)
1496+
1497+
vertical_sections = 3
1498+
fig = plt.figure(figsize=[14, vertical_sections * 6])
1499+
gs = gridspec.GridSpec(vertical_sections, 1, wspace=0.5, hspace=0.5)
1500+
1501+
perf_attrib.plot_returns(perf_attrib_data, ax=plt.subplot(gs[0]))
1502+
1503+
perf_attrib.plot_factor_contribution_to_perf(perf_attrib_data,
1504+
ax=plt.subplot(gs[1]))
1505+
1506+
perf_attrib.plot_risk_exposures(portfolio_exposures,
1507+
ax=plt.subplot(gs[2]))
1508+
1509+
gs.tight_layout(fig)
1510+
1511+
plt.show()
1512+
if return_fig:
1513+
return fig

pyfolio/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ def get_symbol_rets(symbol, start=None, end=None):
612612
end=end)
613613

614614

615-
def set_legend_location(ax):
615+
def set_legend_location(ax, autofmt_xdate=True):
616616
"""
617617
Put legend in right of plot instead of overlapping with it.
618618
"""
@@ -622,3 +622,8 @@ def set_legend_location(ax):
622622

623623
ax.legend(frameon=True, framealpha=0.5, loc='upper left',
624624
bbox_to_anchor=(1.05, 1))
625+
626+
ax.set_prop_cycle('color', COLORS)
627+
628+
if autofmt_xdate:
629+
ax.figure.autofmt_xdate()

0 commit comments

Comments
 (0)