Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multistrike cones #310

Merged
merged 1 commit into from
Jun 16, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions pyfolio/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
import matplotlib.lines as mlines
from matplotlib import figure
from matplotlib.backends.backend_agg import FigureCanvasAgg

from sklearn import preprocessing

Expand Down Expand Up @@ -1660,3 +1662,102 @@ def plot_prob_profit_trade(round_trips, ax=None):
xlim=(lower_plot, upper_plot), ylim=(0, y.max() + 1.))

return ax


def plot_multistrike_cones(is_returns, oos_returns, num_samples=1000,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it currently stands, this function can plot up to 3 strikes, depending on how the out-of-sample returns behave - we should either update the function name and/or docstring to make it clear that it's a fixed number of strikes, or allow the user to pass in a parameter to determine how many strikes they care about. OK with either, maybe the former is better in terms of limiting scope?

name=None, ax=None, cone_std=(1., 1.5, 2.),
random_seed=None, num_strikes=0):
"""
Plots the upper and lower bounds of an n standard deviation
cone of forecasted cumulative returns. This cone is non-parametric,
meaning it does not assume that returns are normally distributed. Redraws
a new cone when returns fall outside of last cone drawn.

Parameters
----------
is_returns : pandas.core.frame.DataFrame
Non-cumulative in-sample returns.
oos_returns : pandas.core.frame.DataFrame
Non-cumulative out-of-sample returns.
num_samples : int
Number of samples to draw from the in-sample daily returns.
Each sample will be an array with length num_days.
A higher number of samples will generate a more accurate
bootstrap cone.
name : str, optional
Plot title
ax : matplotlib.Axes, optional
Axes upon which to plot.
cone_std : list of int/float
Number of standard devations to use in the boundaries of
the cone. If multiple values are passed, cone bounds will
be generated for each value.
random_seed : int
Seed for the pseudorandom number generator used by the pandas
sample method.
num_strikes : int
Upper limit for number of cones drawn. Can be anything from 0 to 3.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit on the docstring here - if num_strikes is 0, then we'll still have one cone drawn(even though we say in the docstring that the upper limit is 0). What do you think of rephrasing to something like:

num_strikes : int
  Number of times to re-draw a cone if we fall outside the bounds of the previous one. Can be any int from 0 to 3.



Returns
-------
Returns are either an ax or fig option, but not both. If a
matplotlib.Axes instance is passed in as ax, then it will be modified
and returned. This allows for users to plot interactively in jupyter
notebook. When no ax object is passed in, a matplotlib.figure instance
is generated and returned. This figure can then be used to save
the plot as an image without viewing it.

ax : matplotlib.Axes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confused me a bit, because it feels like we're returning both ax and fig, whereas what we're actually doing is returning axes if the caller passed them in, and if not, we're returning a new figure. Could we find a way to make that clearer in the docstring?

The axes that were plotted on.
fig : matplotlib.figure
The figure instance which contains all the plot elements.
"""
if ax is None:
fig = figure.Figure(figsize=(10, 8))
FigureCanvasAgg(fig)
axes = fig.add_subplot(111)
else:
axes = ax

returns = timeseries.cum_returns(oos_returns, starting_value=1.)
bounds = timeseries.forecast_cone_bootstrap(is_returns,
len(oos_returns),
cone_std=cone_std,
num_samples=num_samples,
random_seed=random_seed)
bounds.index = oos_returns.index
bounds_tmp = bounds.copy()
returns_tmp = returns.copy()
cone_start = returns.index[0]
colors = ["green", "orange", "orangered", "darkred"]

for c in range(num_strikes+1):
if c > 0:
tmp = returns.loc[cone_start:]
crossing = (tmp < bounds_tmp[float(-2.)].iloc[:len(tmp)])
if crossing.sum() <= 0:
break
cone_start = crossing.loc[crossing].index[0]
returns_tmp = oos_returns.loc[cone_start:]
bounds_tmp = (bounds - (1 - returns.loc[cone_start]))
for std in cone_std:
x = returns_tmp.index
y1 = bounds_tmp[float(std)].iloc[:len(returns_tmp)]
y2 = bounds_tmp[float(-std)].iloc[:len(returns_tmp)]
ax.fill_between(x, y1, y2, color=colors[c], alpha=0.5)
# Plot returns line graph
returns.plot(ax=axes,
lw=3.,
color='black',
label='Cumulative returns = {:.2f}%'.format(
(returns.iloc[-1] - 1) * 100))
if name is not None:
axes.set_title(name)
axes.axhline(1, color='black', alpha=0.2)
axes.legend()

if ax is None:
return fig
else:
return axes