Skip to content

Commit 4ed5724

Browse files
author
Ana Ruelas
committed
ENH: New variable input num_strikes in cone plotting function
1 parent 7078629 commit 4ed5724

File tree

1 file changed

+38
-62
lines changed

1 file changed

+38
-62
lines changed

pyfolio/plotting.py

+38-62
Original file line numberDiff line numberDiff line change
@@ -1664,52 +1664,14 @@ def plot_prob_profit_trade(round_trips, ax=None):
16641664
return ax
16651665

16661666

1667-
def plot_cone(aggregate_returns, bounds, ax,
1668-
cone_std=(1, 1.5, 2.),
1669-
color='green',):
1670-
"""
1671-
Plots the upper and lower bounds of an n standard deviation
1672-
cone of forecasted cumulative returns.
1673-
1674-
Parameters
1675-
----------
1676-
aggregate_returns : pandas.core.frame.DataFrame
1677-
Cumulative out-of-sample returns.
1678-
bounds : pandas.core.frame.DataFrame
1679-
Contains upper and lower cone boundaries. Column names are
1680-
strings corresponding to the number of standard devations
1681-
above (positive) or below (negative) the projected mean
1682-
cumulative returns.
1683-
ax : matplotlib.Axes, optional
1684-
Axes upon which to plot.
1685-
cone_std : int, float, or list of int/float
1686-
Number of standard devations to use in the boundaries of
1687-
the cone. If multiple values are passed, cone bounds will
1688-
be generated for each value.
1689-
color : str, optional
1690-
Any matplotlib color
1691-
1692-
Returns
1693-
-------
1694-
ax : matplotlib.Axes
1695-
The axes that were plotted on.
1696-
"""
1697-
for std in cone_std:
1698-
ax.fill_between(aggregate_returns.index,
1699-
bounds[float(std)].iloc[:len(aggregate_returns)],
1700-
bounds[float(-std)].iloc[:len(aggregate_returns)],
1701-
color=color, alpha=0.5)
1702-
return ax
1703-
1704-
17051667
def plot_multistrike_cones(is_returns, oos_returns, num_samples=1000,
17061668
name=None, ax=None, cone_std=(1., 1.5, 2.),
1707-
random_seed=None):
1669+
random_seed=None, num_strikes=0):
17081670
"""
17091671
Plots the upper and lower bounds of an n standard deviation
17101672
cone of forecasted cumulative returns. This cone is non-parametric,
17111673
meaning it does not assume that returns are normally distributed. Redraws
1712-
a new cone when returns fall outside of last cone drawn
1674+
a new cone when returns fall outside of last cone drawn.
17131675
17141676
Parameters
17151677
----------
@@ -1726,17 +1688,26 @@ def plot_multistrike_cones(is_returns, oos_returns, num_samples=1000,
17261688
Plot title
17271689
ax : matplotlib.Axes, optional
17281690
Axes upon which to plot.
1729-
cone_std : int, float, or list of int/float
1691+
cone_std : list of int/float
17301692
Number of standard devations to use in the boundaries of
17311693
the cone. If multiple values are passed, cone bounds will
17321694
be generated for each value.
17331695
random_seed : int
17341696
Seed for the pseudorandom number generator used by the pandas
17351697
sample method.
1698+
num_strikes : int
1699+
Upper limit for number of cones drawn. Can be anything from 0 to 3.
17361700
17371701
17381702
Returns
17391703
-------
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+
17401711
ax : matplotlib.Axes
17411712
The axes that were plotted on.
17421713
fig : matplotlib.figure
@@ -1749,36 +1720,41 @@ def plot_multistrike_cones(is_returns, oos_returns, num_samples=1000,
17491720
else:
17501721
axes = ax
17511722

1752-
aggregate_returns = timeseries.cum_returns(oos_returns, starting_value=1.)
1723+
returns = timeseries.cum_returns(oos_returns, starting_value=1.)
17531724
bounds = timeseries.forecast_cone_bootstrap(is_returns,
17541725
len(oos_returns),
17551726
cone_std=cone_std,
17561727
num_samples=num_samples,
17571728
random_seed=random_seed)
17581729
bounds.index = oos_returns.index
1759-
plot_cone(aggregate_returns, bounds, axes, cone_std=cone_std)
1760-
1761-
bounds_cur = bounds.copy()
1762-
cone_start = aggregate_returns.index[0]
1763-
1764-
aggregate_returns.plot(ax=axes,
1765-
lw=3.,
1766-
color='k',
1767-
label='Cumulative returns = {:.2f}%'.format(
1768-
(aggregate_returns.iloc[-1] - 1) * 100))
1769-
# Draw additional cones if returns fall outside of the previous cone
1770-
for c in ['orange', 'orangered', 'darkred']:
1771-
tmp = aggregate_returns.loc[cone_start:]
1772-
crossing = (tmp < bounds_cur[float(-2.)].iloc[:len(tmp)])
1773-
if crossing.sum() > 0:
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
17741741
cone_start = crossing.loc[crossing].index[0]
1775-
plot_cone(oos_returns.loc[cone_start:],
1776-
(bounds - (1 - aggregate_returns.loc[cone_start])),
1777-
axes, color=c)
1778-
bounds_cur = (bounds - (1 - aggregate_returns.loc[cone_start]))
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))
17791755
if name is not None:
17801756
axes.set_title(name)
1781-
axes.axhline(1, color='k', alpha=0.2)
1757+
axes.axhline(1, color='black', alpha=0.2)
17821758
axes.legend()
17831759

17841760
if ax is None:

0 commit comments

Comments
 (0)