Skip to content

Commit 5c54b70

Browse files
authored
Merge pull request #310 from quantopian/multistrike_cones
Multistrike cones
2 parents a851a1d + 20d22f9 commit 5c54b70

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

pyfolio/plotting.py

+101
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import matplotlib.pyplot as plt
2424
from matplotlib.ticker import FuncFormatter
2525
import matplotlib.lines as mlines
26+
from matplotlib import figure
27+
from matplotlib.backends.backend_agg import FigureCanvasAgg
2628

2729
from sklearn import preprocessing
2830

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

16621664
return ax
1665+
1666+
1667+
def plot_multistrike_cones(is_returns, oos_returns, num_samples=1000,
1668+
name=None, ax=None, cone_std=(1., 1.5, 2.),
1669+
random_seed=None, num_strikes=0):
1670+
"""
1671+
Plots the upper and lower bounds of an n standard deviation
1672+
cone of forecasted cumulative returns. This cone is non-parametric,
1673+
meaning it does not assume that returns are normally distributed. Redraws
1674+
a new cone when returns fall outside of last cone drawn.
1675+
1676+
Parameters
1677+
----------
1678+
is_returns : pandas.core.frame.DataFrame
1679+
Non-cumulative in-sample returns.
1680+
oos_returns : pandas.core.frame.DataFrame
1681+
Non-cumulative out-of-sample returns.
1682+
num_samples : int
1683+
Number of samples to draw from the in-sample daily returns.
1684+
Each sample will be an array with length num_days.
1685+
A higher number of samples will generate a more accurate
1686+
bootstrap cone.
1687+
name : str, optional
1688+
Plot title
1689+
ax : matplotlib.Axes, optional
1690+
Axes upon which to plot.
1691+
cone_std : list of int/float
1692+
Number of standard devations to use in the boundaries of
1693+
the cone. If multiple values are passed, cone bounds will
1694+
be generated for each value.
1695+
random_seed : int
1696+
Seed for the pseudorandom number generator used by the pandas
1697+
sample method.
1698+
num_strikes : int
1699+
Upper limit for number of cones drawn. Can be anything from 0 to 3.
1700+
1701+
1702+
Returns
1703+
-------
1704+
Returns are either an ax or fig option, but not both. If a
1705+
matplotlib.Axes instance is passed in as ax, then it will be modified
1706+
and returned. This allows for users to plot interactively in jupyter
1707+
notebook. When no ax object is passed in, a matplotlib.figure instance
1708+
is generated and returned. This figure can then be used to save
1709+
the plot as an image without viewing it.
1710+
1711+
ax : matplotlib.Axes
1712+
The axes that were plotted on.
1713+
fig : matplotlib.figure
1714+
The figure instance which contains all the plot elements.
1715+
"""
1716+
if ax is None:
1717+
fig = figure.Figure(figsize=(10, 8))
1718+
FigureCanvasAgg(fig)
1719+
axes = fig.add_subplot(111)
1720+
else:
1721+
axes = ax
1722+
1723+
returns = timeseries.cum_returns(oos_returns, starting_value=1.)
1724+
bounds = timeseries.forecast_cone_bootstrap(is_returns,
1725+
len(oos_returns),
1726+
cone_std=cone_std,
1727+
num_samples=num_samples,
1728+
random_seed=random_seed)
1729+
bounds.index = oos_returns.index
1730+
bounds_tmp = bounds.copy()
1731+
returns_tmp = returns.copy()
1732+
cone_start = returns.index[0]
1733+
colors = ["green", "orange", "orangered", "darkred"]
1734+
1735+
for c in range(num_strikes+1):
1736+
if c > 0:
1737+
tmp = returns.loc[cone_start:]
1738+
crossing = (tmp < bounds_tmp[float(-2.)].iloc[:len(tmp)])
1739+
if crossing.sum() <= 0:
1740+
break
1741+
cone_start = crossing.loc[crossing].index[0]
1742+
returns_tmp = oos_returns.loc[cone_start:]
1743+
bounds_tmp = (bounds - (1 - returns.loc[cone_start]))
1744+
for std in cone_std:
1745+
x = returns_tmp.index
1746+
y1 = bounds_tmp[float(std)].iloc[:len(returns_tmp)]
1747+
y2 = bounds_tmp[float(-std)].iloc[:len(returns_tmp)]
1748+
ax.fill_between(x, y1, y2, color=colors[c], alpha=0.5)
1749+
# Plot returns line graph
1750+
returns.plot(ax=axes,
1751+
lw=3.,
1752+
color='black',
1753+
label='Cumulative returns = {:.2f}%'.format(
1754+
(returns.iloc[-1] - 1) * 100))
1755+
if name is not None:
1756+
axes.set_title(name)
1757+
axes.axhline(1, color='black', alpha=0.2)
1758+
axes.legend()
1759+
1760+
if ax is None:
1761+
return fig
1762+
else:
1763+
return axes

0 commit comments

Comments
 (0)