Skip to content

Commit 38b8d1e

Browse files
gusgordontwiecki
authored andcommitted
Fix cone, add back old holdings plot, refresh docs (#355)
* Use new version of empyrical * Show both holdings plots * Show both holdings plots * Fix cone * Use numpy to sample * Rerun zipline example * Refreshed readme
1 parent 4fc8ce4 commit 38b8d1e

15 files changed

+518
-385
lines changed

README.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,12 @@ financial portfolios developed by
1212

1313
At the core of pyfolio is a so-called tear sheet that consists of
1414
various individual plots that provide a comprehensive image of the
15-
performance of a trading algorithm. Here's an example tear sheet, which comes from the Zipline algorithm sample notebook:
15+
performance of a trading algorithm. Here's an example tear sheet analyzing returns, which comes from the Zipline algorithm sample notebook:
1616

1717
![example tear 0](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_0.png "Example tear sheet created from a Zipline algo")
1818
![example tear 1](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_1.png "Example tear sheet created from a Zipline algo")
19-
![example tear 2](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_2.png)
20-
![example tear 3](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_3.png)
21-
![example tear 4](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_4.png)
22-
![example tear 6](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_6.png)
23-
![example tear 7](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_7.png)
24-
![example tear 5](https://github.com/quantopian/pyfolio/raw/master/docs/example_tear_5.png)
2519

26-
27-
See also [slides of a recent talk about pyfolio.](http://nbviewer.ipython.org/format/slides/github/quantopian/pyfolio/blob/master/pyfolio/examples/overview_slides.ipynb#/)
20+
See also [slides of a talk about pyfolio.](http://nbviewer.ipython.org/format/slides/github/quantopian/pyfolio/blob/master/pyfolio/examples/overview_slides.ipynb#/)
2821

2922
## Installation
3023

docs/example_returns.png

-415 KB
Binary file not shown.

docs/example_tear_0.png

-12.6 KB
Loading

docs/example_tear_1.png

71.2 KB
Loading

docs/example_tear_2.png

-292 KB
Binary file not shown.

docs/example_tear_3.png

-501 KB
Binary file not shown.

docs/example_tear_4.png

-295 KB
Binary file not shown.

docs/example_tear_5.png

-291 KB
Binary file not shown.

docs/example_tear_6.png

-93.9 KB
Binary file not shown.

docs/example_tear_7.png

-79.5 KB
Binary file not shown.

pyfolio/examples/zipline_algo_example.ipynb

Lines changed: 398 additions & 327 deletions
Large diffs are not rendered by default.

pyfolio/plotting.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,68 @@ def plot_monthly_returns_dist(returns, ax=None, **kwargs):
322322
def plot_holdings(returns, positions, legend_loc='best', ax=None, **kwargs):
323323
"""
324324
Plots total amount of stocks with an active position, either short
325-
or long.
325+
or long. Displays daily total, daily average per month, and
326+
all-time daily average.
326327
327-
Displays daily total, daily average per month, and all-time daily
328-
average.
328+
Parameters
329+
----------
330+
returns : pd.Series
331+
Daily returns of the strategy, noncumulative.
332+
- See full explanation in tears.create_full_tear_sheet.
333+
positions : pd.DataFrame, optional
334+
Daily net position values.
335+
- See full explanation in tears.create_full_tear_sheet.
336+
legend_loc : matplotlib.loc, optional
337+
The location of the legend on the plot.
338+
ax : matplotlib.Axes, optional
339+
Axes upon which to plot.
340+
**kwargs, optional
341+
Passed to plotting function.
342+
Returns
343+
-------
344+
ax : matplotlib.Axes
345+
The axes that were plotted on.
346+
"""
347+
348+
if ax is None:
349+
ax = plt.gca()
350+
351+
positions = positions.copy().drop('cash', axis='columns')
352+
df_holdings = positions.apply(lambda x: np.sum(x != 0), axis='columns')
353+
df_holdings_by_month = df_holdings.resample('1M').mean()
354+
df_holdings.plot(color='steelblue', alpha=0.6, lw=0.5, ax=ax, **kwargs)
355+
df_holdings_by_month.plot(
356+
color='orangered',
357+
alpha=0.5,
358+
lw=2,
359+
ax=ax,
360+
**kwargs)
361+
ax.axhline(
362+
df_holdings.values.mean(),
363+
color='steelblue',
364+
ls='--',
365+
lw=3,
366+
alpha=1.0)
367+
368+
ax.set_xlim((returns.index[0], returns.index[-1]))
369+
370+
ax.legend(['Daily holdings',
371+
'Average daily holdings, by month',
372+
'Average daily holdings, net'],
373+
loc=legend_loc)
374+
ax.set_title('Total holdings per pay')
375+
ax.set_ylabel('Holdings')
376+
ax.set_xlabel('')
377+
return ax
378+
379+
380+
def plot_long_short_holdings(returns, positions,
381+
legend_loc='best', ax=None, **kwargs):
382+
"""
383+
Plots total amount of stocks with an active position, breaking out
384+
short and long. Short positions will be shown below zero, while
385+
long positions will be shown above zero. Displays daily total and
386+
all-time daily average.
329387
330388
Parameters
331389
----------
@@ -378,8 +436,8 @@ def plot_holdings(returns, positions, legend_loc='best', ax=None, **kwargs):
378436
'Average daily long positions',
379437
'Average daily short positions'],
380438
loc=legend_loc)
381-
ax.set_title('Holdings per day')
382-
ax.set_ylabel('Amount of holdings per day')
439+
ax.set_title('Long and short holdings per day')
440+
ax.set_ylabel('Holdings')
383441
ax.set_xlabel('')
384442
return ax
385443

@@ -753,10 +811,9 @@ def cone(in_sample_returns (pd.Series),
753811

754812
cone_bounds = cone_bounds.set_index(oos_cum_returns.index)
755813
for std in cone_std:
756-
cone_delta = oos_cum_returns[0] - 1
757814
ax.fill_between(cone_bounds.index,
758-
cone_bounds[float(std)] + cone_delta,
759-
cone_bounds[float(-std)] + cone_delta,
815+
cone_bounds[float(std)],
816+
cone_bounds[float(-std)],
760817
color='steelblue', alpha=0.5)
761818

762819
if legend_loc is not None:
@@ -1659,7 +1716,10 @@ def plot_round_trip_lifetimes(round_trips, disp_amount=16, lsize=18, ax=None):
16591716
if ax is None:
16601717
ax = plt.subplot()
16611718

1662-
sample = round_trips.symbol.unique().sample(n=disp_amount, random_state=1)
1719+
symbols_sample = round_trips.symbol.unique()
1720+
np.random.seed(1)
1721+
sample = np.random.choice(round_trips.symbol.unique(), replace=False,
1722+
size=min(disp_amount, len(symbols_sample)))
16631723
sample_round_trips = round_trips[round_trips.symbol.isin(sample)]
16641724

16651725
symbol_idx = pd.Series(np.arange(len(sample)), index=sample)

pyfolio/tears.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -437,15 +437,16 @@ def create_position_tear_sheet(returns, positions,
437437

438438
if hide_positions:
439439
show_and_plot_top_pos = 0
440-
vertical_sections = 6 if sector_mappings is not None else 5
440+
vertical_sections = 7 if sector_mappings is not None else 6
441441

442442
fig = plt.figure(figsize=(14, vertical_sections * 6))
443443
gs = gridspec.GridSpec(vertical_sections, 3, wspace=0.5, hspace=0.5)
444444
ax_exposures = plt.subplot(gs[0, :])
445445
ax_top_positions = plt.subplot(gs[1, :], sharex=ax_exposures)
446446
ax_max_median_pos = plt.subplot(gs[2, :], sharex=ax_exposures)
447447
ax_holdings = plt.subplot(gs[3, :], sharex=ax_exposures)
448-
ax_gross_leverage = plt.subplot(gs[4, :], sharex=ax_exposures)
448+
ax_long_short_holdings = plt.subplot(gs[4, :], sharex=ax_exposures)
449+
ax_gross_leverage = plt.subplot(gs[5, :], sharex=ax_exposures)
449450

450451
positions_alloc = pos.get_percent_alloc(positions)
451452

@@ -463,6 +464,9 @@ def create_position_tear_sheet(returns, positions,
463464

464465
plotting.plot_holdings(returns, positions_alloc, ax=ax_holdings)
465466

467+
plotting.plot_long_short_holdings(returns, positions_alloc,
468+
ax=ax_long_short_holdings)
469+
466470
plotting.plot_gross_leverage(returns, positions,
467471
ax=ax_gross_leverage)
468472

@@ -472,7 +476,7 @@ def create_position_tear_sheet(returns, positions,
472476
if len(sector_exposures.columns) > 1:
473477
sector_alloc = pos.get_percent_alloc(sector_exposures)
474478
sector_alloc = sector_alloc.drop('cash', axis='columns')
475-
ax_sector_alloc = plt.subplot(gs[5, :], sharex=ax_exposures)
479+
ax_sector_alloc = plt.subplot(gs[6, :], sharex=ax_exposures)
476480
plotting.plot_sector_allocations(returns, sector_alloc,
477481
ax=ax_sector_alloc)
478482

0 commit comments

Comments
 (0)